Hosted onnoosphere.hyper.mediavia theHypermedia Protocol

Column Layouts in the EditorUsers can't place content side by side in the editor. We're adding a /Columns command that creates horizontal layouts by extending the existing block system — no new block types, no backend changes.

Problem

Our editor forces all content into a single vertical stack. Every block — paragraphs, headings, images, code — sits directly above or below the next one. There is no way for users to place content side by side.

This is a significant limitation. Users regularly need multi-column layouts for:

  • Comparisons: two approaches, before/after, pros/cons

  • Mixed media: text alongside an image or embed

  • Structured sections: splitting a page into logically distinct areas

  • Information density: making better use of wide screens instead of leaving margins empty

Competing editors (Notion, Google Docs, Coda) all support columns or similar horizontal layout. Users who come from these tools expect it. Without columns, users resort to workarounds like embedding tables (that de do not support either) or creating images, both of which break the editing experience.

Solution

Documents are trees of BlockNode { block, children }. Blocks stack vertically — no way to place blocks side-by-side. We want two layout features: Fixed Columns (explicit column containers) and Flow Grid (items wrap into N columns). This document analyzes 3 solutions and recommends one.

The Deciding Constraint: ProseMirror Node Hierarchy

doc → blockChildren → blockNode+ → (block, blockChildren?)
  • block nodes can ONLY contain inline* or ''cannot contain other blocks

  • blockChildren is the only nesting mechanism, has a listType attribute

  • All editor commands (Enter, Backspace, Tab, split, merge) assume this exact hierarchy

  • New block types created via createReactBlockSpec are block group nodes — same constraint

This means dedicated column node types outside the hierarchy (Solution B2) require refactoring 15-20+ editor files. And flat attribute markers (Solution C) fight ProseMirror's model entirely.

Three Solutions Evaluated

Solution A: childrenType: 'Columns' — RECOMMENDED

Add 'Columns' to HMBlockChildrenType. Renders children as side-by-side columns.

blockNode (container, childrenType: "Columns")
  block:paragraph ("")                    ← empty, invisible
  blockChildren [listType='Columns']      ← render as flex row
    blockNode (column 1)
      block:paragraph ("")                ← empty, invisible
      blockChildren [listType='Group']    ← column 1 content
        blockNode → paragraph ("Left text")
        blockNode → image (...)
    blockNode (column 2)
      block:paragraph ("")
      blockChildren [listType='Group']    ← column 2 content
        blockNode → paragraph ("Right text")

DimensionRatingPM schema fitPerfect — no new node typesCRDT safetyHigh — each column is an independent RGA sublistEditor effortModerate — keyboard guards on ~6-8 handlersBackwards compatModerate — old clients stack blocks vertically

Solution B: Dedicated Columns + Column Block Types

Two new block types with explicit semantics (like Notion's column_list/column).

  • B1 (within existing hierarchy): Functionally identical to A in ProseMirror — the dedicated type is just a semantic label on the block content node. Same effort as A.

  • B2 (custom PM nodes): Requires refactoring getBlockInfoFromPos, getGroupInfoFromPos, nodeToBlock/blockToNode, all keyboard shortcuts, normalizeFragment, splitBlock/mergeBlocks/nestBlock/unnestBlock, side menu detection. 15-20+ files, high risk.

Old clients show "Unsupported Block" for the column wrappers.

Solution C: Flat Attribute Markers (columnGroupId, columnIndex)

No structural changes — sibling blocks get column attributes, rendering groups them visually.

  • CRDT fragile: concurrent edits interleave blocks, break group contiguity

  • Editor: requires complex ProseMirror decoration plugin, fights the document model

  • Best backwards compat but worst reliability

Comparison

A: childrenTypeB1: Dedicated (same PM)B2: Dedicated (new PM)C: FlatPM schema changesNoneNoneMajor (2 nodes)NoneEditor effort~6-8 guardsSame as A15-20+ filesPlugin rewriteCRDT safetyHighHighHighLowBackwards compatModerateModeratePoorBestRiskLowLowHighVery High

Recommendation: Solution A

Solution A wins across all dimensions. Same tree shape as Notion's model, expressed within our existing ProseMirror hierarchy. No new node types, no hierarchy changes. Can optionally layer B1 semantic naming on top later.

Two Layout Features (Both Wanted)

1. Fixed Columns (childrenType: 'Columns')

  • Each child IS a column container with its own content blocks

  • Column count = number of children (users add/remove explicitly)

  • Optional columnWidths attribute for proportions

  • Use case: side-by-side content sections

2. Flow Grid (childrenType: 'Grid')

  • Items wrap into N columns automatically (like Query block card view)

  • columnCount attribute defines max columns, items stack/wrap

  • Single flat list of children, CSS grid handles flow

  • Use case: card grids, gallery layouts

Both use the same pattern: new childrenType values + CSS layout in BlockNodeList.

Mobile view concerns

For the Column layout we have two options:

  1. keep columns and add a horizontal scroll so readers can see all columns

  2. stack columns vertically so users don't have to scroll horizontally to see all the content.

On the other hand, for Grid Layout, we will always stack items one of top of the other, and we will do exactly what happens with the grid view in query blocks.

For columns, I prefer to just stack them vertically, I don't think its a bad expectation for readers. adding an extra horizontal scroll adds a bit more complexity to the layout that I prefer not to add.

Notion Reference

Notion uses column_list + column (Solution B). Rules: min 2 columns, min 1 child per column, width_ratio (0-1, sum to 1), no nested columns. They can do this because they built their editor from scratch — no ProseMirror constraint.

Photo by Jesse Bauer on Unsplash

Do you like what you are reading?. Subscribe to receive updates.

Unsubscribe anytime