Skip to content

0013. The editor draws the box of a form control on its editable element

Status: Accepted; the disabled state is superseded by 0020 (2026-09-19)

Date: 2026-09-19

Context

The widget replaces a textarea. An admin theme draws a textarea as a bordered box with a background, a radius and a height of the rows the widget asked for, and it draws every other field on the form the same way. The editor drew none of that. Measured on a node form in Claro at commit 300e1aa, the Title field computed border: 1px solid #919297, border-radius: 2px, background: #ffffff and a height of 48 pixels, and a five-row textarea rendered beside it computed the same border and 144 pixels; the editor's wrapper and BlockNote's container computed no border and a transparent background, and the editable computed a white background, a radius of 8 pixels that nothing made visible, no border, no block padding and no minimum height. A field holding one short paragraph was 60 pixels of text with nothing around it, and an empty one was a placeholder floating in the page.

Nothing decided that. 0003 put js/src/theme-isolation.css in the bundle to take an admin theme's rules for bare elements back off the document the editor renders, and 0011 added a focus ring to it for a theme that draws none. Neither considered the chrome, because the probe measured what the themes did to the editor and not what they did for every field beside it.

Core's editor does not rely on the theme for the box either. CKEditor 5 draws its own and exposes the color as a custom property, and each admin theme points that property at its own form element border: Claro's whole ckeditor5.pcss.css is .ck { --ck-color-base-border: var(--input-border-color) }, and Gin sets --ck-color-base-border, --ck-color-base-background and --ck-border-radius from --gin-border-color-form-element, --gin-bg-input and --gin-border-m. A module whose editor is not core's gets no such stylesheet from either theme, so it draws the box itself.

Three elements could carry it. .blocknote-editor is the module's own wrapper, created in attach() after the textarea, and is what carries data-drupal-ck-style-fence. .bn-container is BlockNote's, is also .bn-root, carries data-color-scheme and declares every --bn-* custom property, and is the portal the menus render into. .bn-editor is the contenteditable: it is the element the author clicks into, the element domAttributes.editor names, the element upstream already paints --bn-colors-editor-background and --bn-border-radius-large on, and the element Claro and Gin draw their focus ring on, as 0011 recorded.

BlockNote gives the editable padding-inline: 54px, which is the gutter the side menu and the drag handle are positioned in, and gives block content padding: 3px 0. Text already sits 54 pixels in, so inline padding of the module's own would indent it twice. The Mantine kit's baseline sets box-sizing: border-box on everything inside .bn-mantine, so a minimum height on the editable is the height of the whole box and does not depend on a theme declaring the same thing.

A read-only widget renders the stored document through its text format and attaches no editor (0012), so no element of the editor's is on the page at all. A widget disabled through #states keeps its editor, which marks the editable aria-readonly and contenteditable="false".

Decision

The box goes on the editable. js/src/theme-isolation.css gives .blocknote-editor .bn-editor a border, a radius, block padding and a minimum height. The wrapper and the container are left drawing nothing.

The editable is the element that carries it because it is the element the theme rings. A box on the container or the wrapper would need a :focus-within rule of its own to answer focus the way core's editors do, and in Claro and Gin that ring would stand outside the ring the theme already draws on the editable, which is two rings for one field. On the editable there is no rule to write: the box and the ring share an edge, and the ring an author sees is the theme's, unchanged. Measured in Claro, a focused editor and a focused Title field compute the same box-shadow. The editable is also the whole editing surface, so the part of a box taller than the document is clicked into rather than being dead space below the text, and it is where upstream already paints the background, so only the border, the block padding and the height are new.

The values are custom properties declared beside the palette's. --blocknote-editor-border-width, --blocknote-editor-radius, --blocknote-editor-padding-block and --blocknote-editor-min-height are declared once on .blocknote-editor .bn-root, and --blocknote-editor-border-color per color scheme on .blocknote-editor .bn-root[data-color-scheme="light"] and [data-color-scheme="dark"], which is where the palette overrides and the refusal color already are and is the element the scheme attribute is on. Amended 2026-09-25. The two single-paragraph values the table below records are not these properties redeclared. css/blocknote.widget-chrome.css, a plain stylesheet the blocknote/editor library loads after the bundle's, declares --blocknote-editor-single-paragraph-min-height and --blocknote-editor-single-paragraph-padding-inline on .blocknote-editor--single-paragraph .bn-root and sets min-height and padding-inline from them on .blocknote-editor--single-paragraph .bn-editor, a class js/src/index.tsx puts on the wrapper beside blocknote-editor when the format's policy restricts it to a single paragraph. That selector has the same two classes as .blocknote-editor .bn-editor and wins by coming later, so redeclaring --blocknote-editor-min-height does not reach a single-paragraph editor; a theme resizing one redeclares the single-paragraph property instead.

Property Value Where it comes from
Border width 1px Claro's --input-border-size and Gin's form element border, both a pixel
Border color, light #919297 Claro's --input-border-color, 3.11:1 against the editor's #ffffff
Border color, dark #76777b The color Gin draws a form element with in dark mode, 3.69:1 against the editor's #1f1f1f
Radius 2px Claro's --input-border-radius-size
Background none added The kit already paints --bn-colors-editor-background on the same element, #ffffff and #1f1f1f
Block padding 8px Above the 3 pixels block content carries, it puts the first line 12 pixels below the border, where Claro puts a textarea's
Inline padding none added BlockNote's padding-inline: 54px is the side menu's gutter. Amended 2026-09-24. A format restricted to a single paragraph (0030) sets it to 8px, because such a format offers no side menu and no drag handle, so the gutter is empty space either side of a caption. Amended 2026-09-25. Only while the format's side menu is off, as the shipped snippet editor has it: a single-paragraph format that turns the side menu back on keeps the 54-pixel gutter its drag handle sits in
Minimum height 9rem The 144 pixels Claro's five-row textarea measures, five rows being the widget's default. Amended 2026-09-24. A format restricted to a single paragraph (0030) sets it to 3rem, because a document-sized box reads as a mistake beside a field made for one line

Both border colors are measured against the background the editor paints behind them rather than against the theme's page, because the editor paints its own, and both clear the 3:1 WCAG 2.1 asks of the boundary of a user interface component under 1.4.11.

Amended 2026-09-16. The radius row no longer holds as written: --blocknote-editor-radius is declared as var(--gin-border-m, var(--input-border-radius-size, 2px)), so the box takes the radius Gin gives a form element, --gin-border-m, eight pixels, the token Gin also gives CKEditor 5's --ck-border-radius, or the radius Claro gives a text field, --input-border-radius-size, two pixels, and is two pixels where a theme declares neither. Measured 2026-09-16: 8 in Gin, light and dark, and 2 in Claro, each equal to the Title field beside it.

The specificity stays at two classes. .blocknote-editor .bn-editor is the shape the rest of the stylesheet uses, it requires the module's own wrapper as an ancestor so nothing reaches outside the editor, and a theme overrides it by declaring the same properties on the same selector or by redeclaring the custom properties. The fence is untouched: everything added is inside the subtree data-drupal-ck-style-fence exempts from the off-canvas reset, and nothing was added to the wrapper that carries the attribute.

Alternatives considered

The box on .blocknote-editor, the module's own wrapper. Rejected. The wrapper is the parent of .bn-root, so the per-scheme custom properties declared there do not reach it and it does not know which color scheme the editor is in without a :has() selector. It would also need its own :focus-within rule, which is the two-ring problem above, and it would mix painting into the element whose job is the fence.

The box on .bn-container. Rejected for the same focus reason. It is the right element for the tokens, and they are declared on it, but the ring lands one element further in, so a box there stands outside the ring rather than under it.

Reading the theme's own tokens, such as var(--input-border-color). Rejected. It is the mirror image of what the fence exists to stop: an admin theme's value deciding what the editor looks like, under a name only Claro uses. The module declares its own properties instead, which a theme may point wherever it likes, which is the same shape as --ck-color-base-border. Amended 2026-09-16. Narrowed to the colors. The radius reads both themes' tokens with the module's value as the last fallback: a radius that differs from the fields beside it shows on every form, and a missing token falls through to the module's own value rather than to nothing.

Matching Gin's 8 pixel radius instead of Claro's 2. Not taken. Claro is the tested theme, the reported case and core's admin theme, and Claro's focus ring is a box-shadow, which follows the radius: an 8 pixel box would give the editor a visibly rounder ring than every other field on a Claro form. Amended 2026-09-16. Moot: the box takes each theme's own radius, and the ring, which follows it, matches the theme's fields in both.

A minimum height that follows the widget's rows setting. Rejected. The setting's own description says it is the height of the fallback textarea, which is what an author sees before the editor loads or with JavaScript off, and reading it into CSS would mean the client writing a custom property per editor.

A disabled appearance for an editor whose textarea is disabled through #states. Not taken for 1.0. Claro grays a disabled textarea's background, border and text, and the editor keeps the box it draws when it is editable. See the consequences. Amended 2026-09-19. Taken up by 0020.

Consequences

An empty editor is 144 pixels tall where it was 60, so a form with several document fields is longer. That is the height the textarea it replaces would have taken.

In Gin the box is squarer than the fields around it, and in dark mode its background, #1f1f1f, is darker than the #3b3b3f Gin fills an input with and close to the #1b1b1d of the page behind it. The border is what makes it read as a control there. A site that wants the exact match declares border-radius and the background on .blocknote-editor .bn-editor in its own stylesheet. Amended 2026-09-16. The radius matches in Gin; the darker background remains.

An editor whose textarea is disabled through #states draws the same box as an editable one. It is marked aria-readonly and its content is not editable, so nothing is lost to a screen reader, but a sighted author sees a control that looks editable. A read-only widget is not affected, because it renders the document and attaches no editor. Amended 2026-09-19. No longer true: 0020 gives such an editor the theme's own disabled background, border color and text color.

The editor now paints a border where it painted none, which a front-end theme sees as well. Layout Builder renders the widget in the site's front-end theme, and this box is the admin form's box in a theme that was never measured for it. Support for Layout Builder is not claimed for 1.0.

js/src/theme-isolation.css is no longer only isolation. Its file comment says so, and the two sections that give the editor something rather than take something back, the fallback focus ring and this box, are at the end of the file. Amended 2026-09-19. Three since 0020: the disabled box, 0020's own, joins them at the end of the file too.

References

  • Claro 11.4.6: core/themes/claro/css/components/ckeditor5.pcss.css, css/components/form--text.pcss.css, css/base/variables.pcss.css.
  • Gin 5.0.15: dist/css/components/ckeditor5.css, dist/css/base/gin.css (.form-element) and dist/css/theme/variables.css.
  • BlockNote 0.54.2: @blocknote/core/src/editor/editor.css, @blocknote/react/src/editor/styles.css and BlockNoteView.tsx, @blocknote/mantine/src/mantineStyles.css.
  • WCAG 2.1: 1.4.11 Non-text Contrast, 2.4.7 Focus Visible.
  • js/src/theme-isolation.css, js/src/index.tsx.
  • Tests: tests/src/FunctionalJavascript/AdminThemeIsolationTest.php.
  • 0003, 0011, 0012.