Skip to content

docs: add a Containers concept page and a render-props migration guide - #3022

Open
christianhg wants to merge 3 commits into
mainfrom
docs-containers-page
Open

docs: add a Containers concept page and a render-props migration guide#3022
christianhg wants to merge 3 commits into
mainfrom
docs-containers-page

Conversation

@christianhg

@christianhg christianhg commented Jul 24, 2026

Copy link
Copy Markdown
Member

Initial pass at docs for the new Container API: https://portable-text-editor-documentation-git-docs-containers-page.sanity.build/editor/concepts/containers/

Screenshot 2026-07-24 at 12 06 19

Containers make the editor understand how to render and traverse into nested editable structures. But there's more to it than that. The Container API provides full control of the rendering, which doesn't really compose with the legacy render callbacks. Therefore, there's also a migration guide that explains the new node registration concept in more detail and in comparison to the legacy render callbacks.

The migration guide is called out inside the Containers page like this:

Screenshot 2026-07-24 at 12 09 42

@changeset-bot

changeset-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 7c9c9b2

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
portable-text-editor-documentation Ready Ready Preview, Comment Jul 24, 2026 9:55am
portable-text-example-basic Ready Ready Preview, Comment Jul 24, 2026 9:55am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
portable-text-playground Skipped Skipped Jul 24, 2026 9:55am

Request Review

@github-actions

github-actions Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

📦 Bundle Stats — @portabletext/editor

Compared against main (c713c214)

@portabletext/editor

Metric Value vs main (c713c21)
Internal (raw) 807.5 KB -
Internal (gzip) 155.1 KB -
Bundled (raw) 1.42 MB -
Bundled (gzip) 319.4 KB -
Import time 98ms -3ms, -2.7%

@portabletext/editor/behaviors

Metric Value vs main (c713c21)
Internal (raw) 467 B -
Internal (gzip) 207 B -
Bundled (raw) 424 B -
Bundled (gzip) 171 B -
Import time 2ms -0ms, -0.9%

@portabletext/editor/plugins

Metric Value vs main (c713c21)
Internal (raw) 2.7 KB -
Internal (gzip) 894 B -
Bundled (raw) 2.5 KB -
Bundled (gzip) 827 B -
Import time 7ms -0ms, -2.5%

@portabletext/editor/selectors

Metric Value vs main (c713c21)
Internal (raw) 82.7 KB -
Internal (gzip) 15.4 KB -
Bundled (raw) 78.4 KB -
Bundled (gzip) 14.3 KB -
Import time 8ms +0ms, +0.2%

@portabletext/editor/traversal

Metric Value vs main (c713c21)
Internal (raw) 28.1 KB -
Internal (gzip) 5.6 KB -
Bundled (raw) 28.1 KB -
Bundled (gzip) 5.5 KB -
Import time 6ms -0ms, -3.2%

@portabletext/editor/utils

Metric Value vs main (c713c21)
Internal (raw) 30.6 KB -
Internal (gzip) 6.4 KB -
Bundled (raw) 28.2 KB -
Bundled (gzip) 6.1 KB -
Import time 6ms -0ms, -0.9%

🗺️ . · ./behaviors · ./plugins · ./selectors · ./traversal · ./utils · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

📦 Bundle Stats — @portabletext/markdown

Compared against main (c713c214)

Metric Value vs main (c713c21)
Internal (raw) 53.8 KB -
Internal (gzip) 9.8 KB -
Bundled (raw) 348.9 KB -
Bundled (gzip) 96.3 KB -
Import time 38ms -2ms, -4.5%

🗺️ View treemap · Artifacts

Details
  • Import time regressions over 10% are flagged with ⚠️
  • Sizes shown as raw / gzip 🗜️. Internal bytes = own code only. Total bytes = with all dependencies. Import time = Node.js cold-start median.

@christianhg
christianhg force-pushed the docs-containers-page branch from cab06af to 90b1bcb Compare July 24, 2026 07:20
@vercel
vercel Bot temporarily deployed to Preview – portable-text-playground July 24, 2026 07:20 Inactive
@christianhg
christianhg force-pushed the docs-containers-page branch from 90b1bcb to 77a88e3 Compare July 24, 2026 07:31
@vercel
vercel Bot temporarily deployed to Preview – portable-text-playground July 24, 2026 07:31 Inactive
@christianhg
christianhg force-pushed the docs-containers-page branch 2 times, most recently from 60779b9 to cb9381b Compare July 24, 2026 07:43
@vercel
vercel Bot temporarily deployed to Preview – portable-text-playground July 24, 2026 07:43 Inactive
@christianhg
christianhg force-pushed the docs-containers-page branch from cb9381b to 06a3cdb Compare July 24, 2026 07:48
@vercel
vercel Bot temporarily deployed to Preview – portable-text-playground July 24, 2026 07:48 Inactive
@vercel
vercel Bot temporarily deployed to Preview – portable-text-playground July 24, 2026 07:52 Inactive
@christianhg christianhg changed the title docs: add a Containers concept page docs: add a Containers concept page and a render-props migration guide Jul 24, 2026
@christianhg
christianhg force-pushed the docs-containers-page branch from 1cef1d5 to 9d8cc0a Compare July 24, 2026 07:57
@vercel
vercel Bot temporarily deployed to Preview – portable-text-playground July 24, 2026 07:57 Inactive
@christianhg
christianhg force-pushed the docs-containers-page branch from 9d8cc0a to 5baa3db Compare July 24, 2026 08:01
@christianhg
christianhg force-pushed the docs-containers-page branch from 5984bb0 to e9eae7b Compare July 24, 2026 09:03
@vercel
vercel Bot temporarily deployed to Preview – portable-text-playground July 24, 2026 09:03 Inactive
@christianhg
christianhg force-pushed the docs-containers-page branch from e9eae7b to 7e42ead Compare July 24, 2026 09:08
@vercel
vercel Bot temporarily deployed to Preview – portable-text-playground July 24, 2026 09:08 Inactive
@christianhg
christianhg force-pushed the docs-containers-page branch from 7e42ead to d6083a8 Compare July 24, 2026 09:11
@vercel
vercel Bot temporarily deployed to Preview – portable-text-playground July 24, 2026 09:11 Inactive
@christianhg
christianhg force-pushed the docs-containers-page branch from d6083a8 to f5d9ec8 Compare July 24, 2026 09:16
@vercel
vercel Bot temporarily deployed to Preview – portable-text-playground July 24, 2026 09:16 Inactive
@christianhg
christianhg force-pushed the docs-containers-page branch from f5d9ec8 to 961db7a Compare July 24, 2026 09:17
@vercel
vercel Bot temporarily deployed to Preview – portable-text-playground July 24, 2026 09:17 Inactive
@christianhg
christianhg force-pushed the docs-containers-page branch from 961db7a to aacca05 Compare July 24, 2026 09:18
@vercel
vercel Bot temporarily deployed to Preview – portable-text-playground July 24, 2026 09:19 Inactive
@christianhg
christianhg force-pushed the docs-containers-page branch from aacca05 to f262c68 Compare July 24, 2026 09:24
Containers (block objects with editable child arrays: callouts, code
blocks, tables) had no page on the docs site despite being the third
core editor concept next to the schema and Behaviors, and despite
shipping consumers (`plugin-table`, the playground's callout/code-block/
fact-box/table examples).

Add `editor/concepts/containers` covering: the value model (a container
is plain Portable Text, no editor-specific format), declaring the schema
with a nested `{type: 'block'}` member and the three sub-schema
resolution rules (declared / declared-empty / absent), registering with
`defineContainer` + `NodePlugin` (render contract: spread `attributes`,
render `children`, `contentEditable={false}` chrome, `renderDefault`
fallback, stable `nodes` identity), and nesting through `of` with the
table example from `defineContainer`'s own jsdoc.

A render-pipeline section covers what registering opts you into: your
render owns the outer wrapper (legacy block-level render props don't
compose; span-level props and range decorations keep working), with a
combined example registering the kinds through one `NodePlugin`
(`defineContainer`, `defineTextBlock` with `type: 'block'`, which is
the text block type at every nesting level, and `defineBlockObject`,
which must render its `children`). The consequences get their remedies:
list wrapping and numbering via `plugin-list-index`, drop indicators
via `plugin-dnd`, with a worked text-block render composing both hooks.
A migration subsection routes the legacy props into registrations:
catch-all `defineBlockObject`/`defineInlineObject` registrations
(`type: '*'`) as the direct equivalents of the generic
`renderBlock`/`renderChild` callbacks, and a `type: 'block'` text-block
registration reproducing the legacy nesting order (style innermost,
list item, block outermost) as read from `render.text-block.tsx`.

The editing-semantics section carries a worked example (a code-block
sub-schema forbidding decorators plus a schema-lookup-configured
Markdown shortcut, showing the schema acting as the feature flag),
followed by registration validation (warn-and-skip naming the mismatch)
and pointers to `plugin-table` and the playground.

Registered in the Concepts sidebar group after Behaviors. Built with
`CHECK_LINKS=1`, all links validate.
The containers page's migration subsection was outgrowing its host: the
legacy-to-registrations story spans all four block-level render props,
two catch-all patterns, incremental strategy, and the chrome the engine
stops drawing, too much for a concept page aside, not enough anywhere
else.

Add `editor/guides/migrate-render-props`, a kind-by-kind guide: a
what-maps-to-what table; the per-`_type` dispatch fact that makes
incremental migration safe (registered types render through the new
pipeline, everything else keeps the legacy path); block objects first
(the per-type registration replacing the `schemaType.name` branch, the
`'*'` catch-all as the direct equivalent of the generic legacy
callback, and both composing); inline objects the same way; spans for
the rare `renderChild` that customized them (`defineSpan`, whose
`children` arrive with `renderDecorator`/`renderAnnotation` already
applied, as read from `render.span.tsx`); text blocks as
`type: 'block'` folding `renderStyle`/`renderListItem`/`renderBlock`
into one render in the legacy nesting order; the chrome reclaim step
(`plugin-list-index`, `plugin-dnd`); and the final mount-and-clean-up
showing which props stay (span-level: `renderDecorator`,
`renderAnnotation`, `renderPlaceholder`, range decorations).

The before-sides are the existing docs' own examples (`custom-blocks`'s
image `renderBlock` and stock-ticker `renderChild`, getting-started's
`renderStyle`), so before and after describe the same editor. The
containers concept page's migration subsection shrinks to a pointer at
the guide. Registered in the Guides sidebar group after custom
rendering. Built with `CHECK_LINKS=1`, all links validate.
…ides

The custom rendering guide claimed "Portable Text has no concept of
block nesting", written before containers existed and now contradicted
by the containers concept page one sidebar group away. The true claim
was always about lists: a list is a flat run of sibling text blocks
carrying `listItem` and `level`, with no wrapper node, and that stays
true with containers, which nest blocks through object fields. Restate
it as the list-specific fact with a pointer to the containers page.

The guides' prerequisite notes had also drifted from the released
majors: seven pages claimed `@portabletext/editor` v6.x (current: 7.x)
and two claimed `@portabletext/toolbar` v7.x (current: 8.x). Refresh
all of them; the `@portabletext/react` v6.x note on the rendering page
is still accurate and stays.

Omitting `render` falls through to the engine default, so a registration can exist purely to mark the field as editable.

Registering a container also normalizes the value: existing blocks of the registered type immediately pick up the container shape, so a `table` block without a `rows` array gains `rows: []`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude suggests that this isn't completely accurate?

rows: [] is wrong (containers.mdx, "Register the container"). Normalization doesn't leave an empty array — it seeds a cursor-ready chain. normalize-node.ts synthesizes a child, and packages/editor/tests/container-normalization.test.tsx asserts {_type: 'table', _key} becomes rows → row → cells → cell → content → block → span('') in three set patches. {[field]: []} is only reached when arrayField.of is empty. Suggest: "gains a cursor-ready rows array seeded with an empty row, cell, and text block."

@markmichon markmichon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm. left some version bump suggestions surfaced from the peerdeps list.


:::note[Prerequisites]
This guide covers `@portabletext/editor` **v6.x** ([changelog](https://github.com/portabletext/editor/releases)). Requires React 18+.
This guide covers `@portabletext/editor` **v7.x** ([changelog](https://github.com/portabletext/editor/releases)). Requires React 18+.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The peerdeps in 7 use react 19

Suggested change
This guide covers `@portabletext/editor` **v7.x** ([changelog](https://github.com/portabletext/editor/releases)). Requires React 18+.
This guide covers `@portabletext/editor` **v7.x** ([changelog](https://github.com/portabletext/editor/releases)). Requires React 19.2.7+.


:::note[Prerequisites]
This guide covers `@portabletext/toolbar` **v7.x** and `@portabletext/keyboard-shortcuts` **v2.x** ([changelog](https://github.com/portabletext/editor/releases)). Requires `@portabletext/editor` v6.x and React 18+.
This guide covers `@portabletext/toolbar` **v8.x** and `@portabletext/keyboard-shortcuts` **v2.x** ([changelog](https://github.com/portabletext/editor/releases)). Requires `@portabletext/editor` v7.x and React 18+.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This guide covers `@portabletext/toolbar` **v8.x** and `@portabletext/keyboard-shortcuts` **v2.x** ([changelog](https://github.com/portabletext/editor/releases)). Requires `@portabletext/editor` v7.x and React 18+.
This guide covers `@portabletext/toolbar` **v8.x** and `@portabletext/keyboard-shortcuts` **v2.x** ([changelog](https://github.com/portabletext/editor/releases)). Requires `@portabletext/editor` v7.x and React 19.2.7+.


:::note[Prerequisites]
This guide covers `@portabletext/editor` **v6.x**, `@portabletext/toolbar` **v7.x**, and `@portabletext/keyboard-shortcuts` **v2.x**. Requires React 18+. Check the [editor changelog](https://github.com/portabletext/editor/releases) for breaking changes.
This guide covers `@portabletext/editor` **v7.x**, `@portabletext/toolbar` **v8.x**, and `@portabletext/keyboard-shortcuts` **v2.x**. Requires React 18+. Check the [editor changelog](https://github.com/portabletext/editor/releases) for breaking changes.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This guide covers `@portabletext/editor` **v7.x**, `@portabletext/toolbar` **v8.x**, and `@portabletext/keyboard-shortcuts` **v2.x**. Requires React 18+. Check the [editor changelog](https://github.com/portabletext/editor/releases) for breaking changes.
This guide covers `@portabletext/editor` **v7.x**, `@portabletext/toolbar` **v8.x**, and `@portabletext/keyboard-shortcuts` **v2.x**. Requires React 19.2.7+. Check the [editor changelog](https://github.com/portabletext/editor/releases) for breaking changes.


:::note[Prerequisites]
This guide covers `@portabletext/editor` **v6.x** and `@portabletext/toolbar` **v7.x** ([changelog](https://github.com/portabletext/editor/releases)). Requires React 18+. You should be familiar with the [getting started guide](/editor/getting-started/) and [custom rendering](/editor/guides/custom-rendering/).
This guide covers `@portabletext/editor` **v7.x** and `@portabletext/toolbar` **v8.x** ([changelog](https://github.com/portabletext/editor/releases)). Requires React 18+. You should be familiar with the [getting started guide](/editor/getting-started/) and [custom rendering](/editor/guides/custom-rendering/).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This guide covers `@portabletext/editor` **v7.x** and `@portabletext/toolbar` **v8.x** ([changelog](https://github.com/portabletext/editor/releases)). Requires React 18+. You should be familiar with the [getting started guide](/editor/getting-started/) and [custom rendering](/editor/guides/custom-rendering/).
This guide covers `@portabletext/editor` **v7.x** and `@portabletext/toolbar` **v8.x** ([changelog](https://github.com/portabletext/editor/releases)). Requires React 19.2.7+. You should be familiar with the [getting started guide](/editor/getting-started/) and [custom rendering](/editor/guides/custom-rendering/).


:::note[Prerequisites]
This guide covers `@portabletext/editor` **v6.x** ([changelog](https://github.com/portabletext/editor/releases)). Requires React 18+.
This guide covers `@portabletext/editor` **v7.x** ([changelog](https://github.com/portabletext/editor/releases)). Requires React 18+.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This guide covers `@portabletext/editor` **v7.x** ([changelog](https://github.com/portabletext/editor/releases)). Requires React 18+.
This guide covers `@portabletext/editor` **v7.x** ([changelog](https://github.com/portabletext/editor/releases)). Requires React 19.2.7+.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants