Skip to content

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

BlockTypeDescription
groupContainerWrapper with padding, background, and border-radius tokens
columns + columnContainerHorizontal multi-column grid with drag-to-resize
spacerAtomVertical whitespace sized by height token
dividerAtomHorizontal rule with style and thickness tokens
buttonAtomCTA 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

tsx
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:

typescript
import {
  EdituGroup,
  EdituColumns,
  EdituColumn,
  EdituSpacer,
  EdituDivider,
  EdituButton,
} from '@editu/core';

Via Plugin System

typescript
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

AttributeTypeDefaultValues
paddingEdituGroupPaddingToken"none""none" "xs" "sm" "md" "lg" "xl"
backgroundEdituGroupBackgroundToken"none""none" "muted" "subtle" "accent" "warning"
borderRadiusEdituGroupRadiusToken"none""none" "sm" "md" "lg" "xl" "full"

Commands

typescript
// 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

AttributeTypeDefaultValues
gapEdituColumnsGapToken"md""none" "xs" "sm" "md" "lg"

Column Attributes

AttributeTypeDefaultConstraints
widthnumber0.5Min: 0.1, Max: 0.9. Sibling widths should sum to ~1.

Commands

typescript
// 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

AttributeTypeDefaultValues
heightEdituSpacerHeightToken"md""xs" "sm" "md" "lg" "xl"

Commands

typescript
// 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

AttributeTypeDefaultValues
styleEdituDividerStyle"solid""solid" "dashed" "dotted"
thicknessEdituDividerThickness"regular""thin" "regular" "thick"

Commands

typescript
// 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

AttributeTypeDefaultValues
labelstring"Button"Any text
hrefstring"#"Any URL
variantEdituButtonVariant"primary""primary" "secondary" "ghost"
targetEdituButtonTarget"_self""_self" "_blank"

Commands

typescript
// 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:

RegistryToken TypeExample Mapping
EDITU_SPACING_TOKENSPadding/height"md""p-4"
EDITU_BACKGROUND_TOKENSBackground"muted""bg-neutral-100"
EDITU_RADIUS_TOKENSBorder radius"lg""rounded-lg"
EDITU_HEIGHT_TOKENSSpacer height"lg""h-16"
EDITU_DIVIDER_STYLE_TOKENSDivider style"dashed""border-dashed"
EDITU_DIVIDER_THICKNESS_TOKENSDivider thickness"thick""border-t-4"
EDITU_BUTTON_VARIANT_TOKENSButton variant"primary""bg-blue-600 text-white hover:bg-blue-700"

Resolving Tokens

Each registry exports a resolver function:

typescript
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:

html
<!-- 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

Released under the MIT License.