Toggle
A collapsible section with a clickable chevron. The toggle header is a full rich-text editor — plain text or any of three heading levels. Any block type can be nested inside as a child, including other toggles. The open/closed state syncs to collaborators in real time via Yjs.
Markdown shortcuts
| Type this | Converts to |
|---|---|
| > | Toggle |
Variants
Four variants share the same collapse mechanic but use different header typography:
| Variant | Slash command | Header style |
|---|---|---|
| Toggle | /toggle | 1rem · normal weight |
| Toggle Heading 1 | /toggle heading 1 | 2.25rem · bold · tight tracking |
| Toggle Heading 2 | /toggle heading 2 | 1.875rem · bold · tight tracking |
| Toggle Heading 3 | /toggle heading 3 | 1.5rem · semibold |
ProseMirror contenteditable element — not a wrapper div. This ensures heading sizes render correctly in the editor canvas. Earlier versions applied the class to a wrapper, which caused headings to appear at base text size.Opening and closing
The open/closed state is stored in metadata.toggleOpen and written to the Yjs document immediately on toggle. Collaborators see the state change in real time.
Thread line
When a toggle is open, its children are indented and a thin vertical line runs down the left side — the “thread line”. It visually groups the children under their parent and makes nesting depth immediately readable.
The thread line is 1px wide, border-radius: 9999px, and color-adapts: rgba(255,255,255,0.15) on dark backgrounds,#e5e7eb on light ones.
Child blocks
Any block type can be a child — paragraphs, headings, lists, to-dos, callouts, images, databases, commerce blocks, and other toggles. Children are identified by a parentToggleIdfield on the block that stores the toggle's ID.
Adding child blocks
Drag auto-open
When dragging a block, the system tracks which toggle header the cursor is hovering over. If you hold over a closed toggle for 400ms, it opens automatically so you can drop into it without manually opening it first.
If you move the cursor away before the 400ms timer fires, the timer is cancelled and the toggle stays closed. The timer also resets if you hover a different toggle. Toggles that are ancestors of the dragged block are excluded from auto-open targets — you cannot accidentally drop a toggle into its own descendant.
Extracting blocks from a toggle
Reordering children
Child blocks can be reordered by dragging within the toggle. The drop line and drop zone logic is scoped to the toggle's child list — dragging a child to the very top places it before the first sibling, not before the toggle header. Dragging a child to the bottom places it after the last sibling before the “Add block” button.
Rects for toggle drop zones are refreshed every 50ms during a drag to account for toggles that open or reflow mid-drag.
Nested toggles
Toggles can be nested inside other toggles to any depth. Each level gets its own thread line and chevron. Opening and closing is independent per toggle — collapsing a parent hides all descendants regardless of whether they are open or closed at their own level. Their individual open/closed states are preserved and restored when the parent is re-opened.
Publishing behaviour
On published pages, toggles render via the ReadOnlyToggle client component. The initial open/closed state is whatever was saved in the editor. Visitors can click the chevron to expand or collapse — toggles are fully interactive on published pages, not static.
The toggle state on the published page is local to the visitor's session — changes a visitor makes do not persist or sync back to the document.
Metadata reference
true = open, false = closed. Updated by clicking the chevron. Synced via Yjs to all collaborators.