blokhaus

TogglePlugin

Collapsible toggle block management.

Import

import { TogglePlugin } from "@blokhaus/core";

Overview

The TogglePlugin provides collapsible toggle blocks (also known as accordion or disclosure blocks). Each toggle block has a summary line that is always visible, and a body section that expands or collapses when clicked. The plugin manages the ToggleContainerNode structure and handles keyboard navigation at toggle boundaries.

Props

The TogglePlugin accepts no props. Toggle behavior is managed automatically once the plugin is mounted.

Usage

app/editor/page.tsx
"use client";

import {
  EditorRoot,
  TogglePlugin,
  InputRulePlugin,
  SlashMenu,
} from "@blokhaus/core";

export default function EditorPage() {
  return (
    <EditorRoot namespace="my-editor">
      <InputRulePlugin />
      <SlashMenu />
      <TogglePlugin />
    </EditorRoot>
  );
}

Registered commands

CommandPayloadDescription
INSERT_TOGGLE_COMMANDvoidInserts a new toggle block at the current cursor position.

Creating a toggle programmatically

import { INSERT_TOGGLE_COMMAND } from "@blokhaus/core";

editor.dispatchCommand(INSERT_TOGGLE_COMMAND, undefined);

Toggle structure

A toggle block consists of three node types:

ToggleContainerNode (root container)
  ├── ToggleTitleNode (always visible summary line)
  │     └── TextNode ("Click to expand")
  └── ToggleContentNode (collapsible body)
        └── ParagraphNode
              └── TextNode ("Hidden content here")

In JSON

{
  "type": "toggle-container",
  "open": true,
  "children": [
    {
      "type": "toggle-title",
      "children": [{ "type": "text", "text": "Click to expand" }]
    },
    {
      "type": "toggle-content",
      "children": [
        {
          "type": "paragraph",
          "children": [{ "type": "text", "text": "Hidden content here" }]
        }
      ]
    }
  ]
}

Behavior

Expanding and collapsing

  • Clicking the toggle indicator (triangle/chevron) toggles the open state on the ToggleContainerNode.
  • When collapsed, the ToggleContentNode is visually hidden but remains in the AST. This preserves content during serialization and undo/redo.
  • The toggle state is persisted in the JSON state via the open boolean property.

Keyboard navigation

KeyContextAction
EnterEnd of toggle titleMove cursor into the toggle content body
EnterEnd of last paragraph in contentCreate a new paragraph inside the content
BackspaceStart of empty toggle titleDelete the entire toggle block
Arrow DownEnd of toggle titleMove into the toggle content (if open) or skip to next sibling (if closed)
Arrow UpStart of first content paragraphMove cursor back to the toggle title
EscapeInside toggle contentMove cursor to the toggle title

Content inside toggles

The toggle content body supports any block-level content: paragraphs, headings, lists, code blocks, images, and even nested toggles. There is no restriction on what can be placed inside a toggle.

// Nested toggles
editor.update(() => {
  const outerToggle = $createToggleContainerNode(true);
  const outerTitle = $createToggleTitleNode();
  const outerContent = $createToggleContentNode();

  const innerToggle = $createToggleContainerNode(false);
  const innerTitle = $createToggleTitleNode();
  const innerContent = $createToggleContentNode();

  // Build the nested structure
  innerToggle.append(innerTitle, innerContent);
  outerContent.append(innerToggle);
  outerToggle.append(outerTitle, outerContent);
});

Slash menu integration

When TogglePlugin is mounted, a "Toggle" item appears in the slash menu. Selecting it inserts a new toggle block with the cursor positioned in the title.

Programmatic control

Toggle open/close state

import { $isToggleContainerNode } from "@blokhaus/core";

editor.update(() => {
  // Find and collapse all toggles
  const root = $getRoot();
  root.getChildren().forEach((node) => {
    if ($isToggleContainerNode(node)) {
      node.setOpen(false);
    }
  });
});

Type guards

import {
  $isToggleContainerNode,
  $isToggleTitleNode,
  $isToggleContentNode,
} from "@blokhaus/core";

When a toggle is collapsed, its content remains in the AST and is included in serialized JSON. Collapsing a toggle is purely a visual operation -- it does not delete or hide content from the document state.

Do not attempt to remove ToggleTitleNode or ToggleContentNode independently. A ToggleContainerNode must always contain exactly one title and one content node. Removing either will corrupt the toggle structure. To delete a toggle, remove the entire ToggleContainerNode.