Layout Blocks
Editu includes Gutenberg-style layout primitives for building structured page layouts inside the editor. These blocks let you create sections with backgrounds, multi-column grids, spacing, dividers, and call-to-action buttons — all with semantic token-based styling.
Overview
| Block | Type | Description |
|---|---|---|
group | Container | Wrapper with padding, background, and border-radius tokens |
columns + column | Container | Horizontal multi-column grid with drag-to-resize |
spacer | Atom | Vertical whitespace sized by height token |
divider | Atom | Horizontal rule with style and thickness tokens |
button | Atom | CTA link with label, href, variant, and target |
Container blocks hold child blocks (block+). Atom blocks are leaf nodes with no children.
Installation
Layout blocks are standalone Tiptap extensions — they are not part of the features config. Register them as extensions directly or through the plugin system.
Direct Registration
import { useEdituEditor } from '@editu/react';
import {
EdituGroupReact,
EdituColumnsReact,
EdituColumnReact,
EdituSpacerReact,
EdituDividerReact,
EdituButtonReact,
} from '@editu/react';
const editor = useEdituEditor({
extensions: [
EdituGroupReact,
EdituColumnsReact,
EdituColumnReact,
EdituSpacerReact,
EdituDividerReact,
EdituButtonReact,
],
});The *React variants include interactive NodeViews (resize handles, click-to-edit labels, selection borders). If you don't need React NodeViews, use the framework-agnostic extensions from @editu/core:
import {
EdituGroup,
EdituColumns,
EdituColumn,
EdituSpacer,
EdituDivider,
EdituButton,
} from '@editu/core';Via Plugin System
import type { EdituPlugin } from '@editu/core';
import {
EdituGroupReact,
EdituColumnsReact,
EdituColumnReact,
EdituSpacerReact,
EdituDividerReact,
EdituButtonReact,
} from '@editu/react';
const layoutPlugin: EdituPlugin = {
name: 'editu-layout',
version: '1.0.0',
description: 'Gutenberg-style layout primitives',
extensions: [
EdituGroupReact,
EdituColumnsReact,
EdituColumnReact,
EdituSpacerReact,
EdituDividerReact,
EdituButtonReact,
],
};See Plugins for the full plugin API.
Group
A wrapper block with token-based padding, background, and border-radius.
Attributes
| Attribute | Type | Default | Values |
|---|---|---|---|
padding | EdituGroupPaddingToken | "none" | "none" "xs" "sm" "md" "lg" "xl" |
background | EdituGroupBackgroundToken | "none" | "none" "muted" "subtle" "accent" "warning" |
borderRadius | EdituGroupRadiusToken | "none" | "none" "sm" "md" "lg" "xl" "full" |
Commands
// Wrap selection in a group
editor.commands.setGroup({ padding: 'md', background: 'subtle' });
// Toggle group wrapper
editor.commands.toggleGroup({ padding: 'lg' });
// Update attributes of current group
editor.commands.updateGroupAttributes({ background: 'accent' });
// Lift content out of group
editor.commands.unsetGroup();Columns + Column
Horizontal multi-column layout. The columns wrapper enforces a minimum of 2 child column nodes. Each column stores a width fraction (0..1) controlling its flex basis.
Columns Attributes
| Attribute | Type | Default | Values |
|---|---|---|---|
gap | EdituColumnsGapToken | "md" | "none" "xs" "sm" "md" "lg" |
Column Attributes
| Attribute | Type | Default | Constraints |
|---|---|---|---|
width | number | 0.5 | Min: 0.1, Max: 0.9. Sibling widths should sum to ~1. |
Commands
// Insert a 2-column layout
editor.commands.setColumns(2);
// Insert a 3-column layout
editor.commands.setColumns(3);
// Add a column to the current columns wrapper
editor.commands.addColumn();
// Remove the current column (no-op if < 3 columns)
editor.commands.removeColumn();
// Update a column's width at a specific document position
editor.commands.updateColumnWidth(pos, 0.6);The React NodeView (EdituColumnsNodeView) adds drag-to-resize handles between columns. Drag a handle to redistribute widths between adjacent columns.
Spacer
A vertical whitespace atom block sized by a semantic height token.
Attributes
| Attribute | Type | Default | Values |
|---|---|---|---|
height | EdituSpacerHeightToken | "md" | "xs" "sm" "md" "lg" "xl" |
Commands
// Insert a spacer with default height
editor.commands.setSpacer();
// Insert a spacer with a specific height
editor.commands.setSpacer('lg');
// Update height of current spacer
editor.commands.updateSpacerHeight('xl');Divider
A horizontal rule with style and thickness tokens. Falls back to standard --- markdown when using default attributes.
Attributes
| Attribute | Type | Default | Values |
|---|---|---|---|
style | EdituDividerStyle | "solid" | "solid" "dashed" "dotted" |
thickness | EdituDividerThickness | "regular" | "thin" "regular" "thick" |
Commands
// Insert a divider
editor.commands.setDivider();
// Insert a dashed divider
editor.commands.setDivider({ style: 'dashed', thickness: 'thin' });
// Update attributes of current divider
editor.commands.updateDividerAttributes({ style: 'dotted' });Button
A CTA link atom with label, href, variant, and target. The label is stored in the schema as an attribute (atoms are leaves with no inline content).
Attributes
| Attribute | Type | Default | Values |
|---|---|---|---|
label | string | "Button" | Any text |
href | string | "#" | Any URL |
variant | EdituButtonVariant | "primary" | "primary" "secondary" "ghost" |
target | EdituButtonTarget | "_self" | "_self" "_blank" |
Commands
// Insert a button
editor.commands.setButton({ label: 'Get Started', href: '/docs' });
// Insert with variant
editor.commands.setButton({
label: 'Learn More',
href: 'https://example.com',
variant: 'secondary',
target: '_blank',
});
// Update attributes of current button
editor.commands.updateButtonAttributes({ variant: 'ghost' });The React NodeView (EdituButtonNodeView) renders a click-to-edit label editor via updateAttributes.
Token Registry
Layout blocks use semantic tokens instead of raw CSS classes. The token registries in @editu/core/tokens map token keys to Tailwind utility classes:
| Registry | Token Type | Example Mapping |
|---|---|---|
EDITU_SPACING_TOKENS | Padding/height | "md" → "p-4" |
EDITU_BACKGROUND_TOKENS | Background | "muted" → "bg-neutral-100" |
EDITU_RADIUS_TOKENS | Border radius | "lg" → "rounded-lg" |
EDITU_HEIGHT_TOKENS | Spacer height | "lg" → "h-16" |
EDITU_DIVIDER_STYLE_TOKENS | Divider style | "dashed" → "border-dashed" |
EDITU_DIVIDER_THICKNESS_TOKENS | Divider thickness | "thick" → "border-t-4" |
EDITU_BUTTON_VARIANT_TOKENS | Button variant | "primary" → "bg-blue-600 text-white hover:bg-blue-700" |
Resolving Tokens
Each registry exports a resolver function:
import {
resolveEdituSpacing,
resolveEdituBackground,
resolveEdituButtonVariant,
} from '@editu/core/tokens';
resolveEdituSpacing('md'); // "p-4"
resolveEdituBackground('accent'); // "bg-blue-50"
resolveEdituButtonVariant('ghost'); // "bg-transparent text-blue-600 hover:bg-blue-50"The React NodeViews call these resolvers internally to apply Tailwind classes. If you build a custom NodeView or render layout blocks outside the editor, use the resolver functions to map tokens to classes.
Markdown Round-Trip
Layout blocks serialize to raw HTML with data-editu-* attributes, since they have no standard Markdown equivalent:
<!-- Group -->
<div data-editu-group data-padding="md" data-background="subtle" data-border-radius="lg">
Content inside the group...
</div>
<!-- Columns -->
<div data-editu-columns data-gap="md">
<div data-editu-column data-width="0.5">Left column</div>
<div data-editu-column data-width="0.5">Right column</div>
</div>
<!-- Spacer -->
<div data-editu-spacer data-height="lg"></div>
<!-- Divider (default: standard ---) -->
---
<!-- Divider (custom style) -->
<hr data-editu-divider data-style="dashed" data-thickness="thin" />
<!-- Button -->
<a data-editu-button href="/docs" data-variant="primary" target="_self" data-label="Get Started">Get Started</a>TIP
The divider block with default attributes (solid + regular) serializes as standard Markdown --- for maximum compatibility.
Next Steps
- Output Modes — How layout blocks export to Markdown, JSON, and HTML (Tailwind token mapping)
- Custom Blocks — Build your own block from scratch (hands-on tutorial)
- Plugins — Package layout blocks as a reusable plugin
- Theming — Customize token-to-class mappings via CSS variables
- Configuration — Editor setup and extension registration