the rich text editor offers three heading levels

One button that toggled H2, so a document could hold exactly one weight of heading — and the levels the
site's sanitiser already accepted were unreachable from the editor that writes for it.

Three buttons rather than a dropdown: they are toggles like everything else on the bar, the active one
answers "what is this line", and a select would hide that behind a click.

A level is a rank in the outline and never a size. What each draws at is the site's — three of the four
type roles, in the prose rules — so the Layout section stays the one place type is sized.

`[open]` H1 is offered because it was asked for, and a page that already has a title now has two. That
is an outline a search engine reads as two documents, and it wants a rule about where H1 belongs before
this gets used much.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-04 16:55:48 +01:00
co-authored by Claude Opus 5
parent 44a907f3b1
commit 179507fb7b
+37 -4
View File
@@ -9,7 +9,9 @@ import {
AlignLeft, AlignLeft,
AlignRight, AlignRight,
Bold, Bold,
Heading1,
Heading2, Heading2,
Heading3,
Italic, Italic,
Link2, Link2,
List, List,
@@ -44,6 +46,24 @@ import {
* somewhere that is not typing — see the effect below. * somewhere that is not typing — see the effect below.
*/ */
/**
* The heading levels the bar offers, in the order they nest.
*
* A level is a rank in the document's outline, and the SIZE it draws at is the site's — see the prose
* rules in styles.css, where each maps to one of the four type roles. So this list says what a heading
* means and never what it looks like, which is the only way the Layout section can go on being the
* place type is sized.
*
* `[open]` H1 is offered because it was asked for, and a page that already has a title now has two —
* which is an outline a search engine reads as two documents. Worth a rule about where H1 belongs
* before this is used much.
*/
const HEADINGS = [
{ level: 1 as const, Icon: Heading1, label: 'Heading 1' },
{ level: 2 as const, Icon: Heading2, label: 'Heading 2' },
{ level: 3 as const, Icon: Heading3, label: 'Heading 3' },
];
const Tool = ({ const Tool = ({
onClick, onClick,
active, active,
@@ -107,13 +127,26 @@ const Toolbar = ({ editor }: { editor: Editor }) => (
<span className="mx-1 h-4 w-px bg-border" /> <span className="mx-1 h-4 w-px bg-border" />
{/*
Three levels, not one.
It was a single button that toggled H2, so a document could hold exactly one weight of heading —
and the two levels the site's sanitiser already allowed were unreachable from the editor that
writes for it.
Three buttons rather than a dropdown: they are toggles like everything else on this bar, the
active one is the answer to "what is this line", and a select would hide that behind a click.
*/}
{HEADINGS.map(({ level, Icon, label }) => (
<Tool <Tool
label="Heading" key={level}
onClick={() => editor.chain().focus().toggleHeading({ level: 2 }).run()} label={label}
active={editor.isActive('heading', { level: 2 })} onClick={() => editor.chain().focus().toggleHeading({ level }).run()}
active={editor.isActive('heading', { level })}
> >
<Heading2 className="size-3.5" /> <Icon className="size-3.5" />
</Tool> </Tool>
))}
<Tool <Tool
label="Bulleted list" label="Bulleted list"
onClick={() => editor.chain().focus().toggleBulletList().run()} onClick={() => editor.chain().focus().toggleBulletList().run()}