Better Rich Text Buy licence

Skins

On this page
  1. Choosing a skin
  2. Overriding tokens
  3. Writing a custom skin

A skin sets the look of the editor chrome (toolbar, menus, popovers, dialogs, find panel, status bar, source view, character picker, selection toolbar) and the content area. Skins live in @better-rich-text/skins. Each is a stylesheet scoped by [data-brt-skin="<id>"] on .brt-editor and .brt-popover-layer, inside the brt cascade layer, so your own styles always win.

Choosing a skin

TypeScript
import { createRichTextEditor } from '@better-rich-text/core';

const editor = createRichTextEditor('#editor', { skin: 'galley', theme: 'auto' });
editor.setSkin('bevel');
editor.setTheme('dark');

skin defaults to studio. theme (light, dark or auto) picks the colour scheme for skins that have both; single-scheme skins ignore it. EditorUi takes the same skin and theme options and has setSkin() and setTheme(). A skin's CSS is added to the document (or shadow root) the first time it is used. Switching skins never recreates the editor.

SkinSchemesDescription
studio light, dark Default. Black ink, white stock and a vermilion signal. Square, high contrast, toggled buttons invert, and the selection toolbar is printed in reverse.
galley light, dark Warm proof paper, ink and blue pencil, with a book serif for the writing area.
native light, dark System colours, fonts and form controls. Follows the OS accent colour and appearance.
meadow light Glossy blue frame, cream panels and a green primary button, after the 2001-era desktop.
bevel light Grey faces, two-pixel bevels and navy highlights, after the 1995-era desktop.
midnight dark Blue-black ground with sodium-lamp amber.
sorbet dark Warm charcoal with strawberry, tangerine, lemon and lime, after fruit-toned code editor palettes.
sepia light, dark Cream paper, brown-black ink and an oxblood accent, with a book serif for long reading.
phosphor dark Green phosphor terminal: reverse video, double-line frames, one fixed-pitch face.
contrast light, dark WCAG AAA high contrast: 7:1 text, heavy rules, 3px focus rings.

skins, skinIds and getSkin(id) describe each skin: label, description, reference, colorSchemes and the token values in tokens. Use them to build a skin picker.

Overriding tokens

Every colour, font and size comes from a --brt-* custom property. Set the ones you want on the editor. Rules outside the brt layer always beat skin rules, and inline styles are copied to the popover layer:

CSS
.my-app .brt-editor, .my-app .brt-popover-layer {
  --brt-accent: #0b6e4f;
  --brt-focus: #0b6e4f;
  --brt-content-font: "Iowan Old Style", Georgia, serif;
}

Colour tokens: canvas, surface, surface-raised, surface-inset, border, border-strong, text, text-muted, accent, accent-soft, accent-contrast, danger, warning, success, focus, selection, selection-text, insertion, deletion, comment, placeholder, mark, search-match, search-current, search-current-text, pressed, pressed-text, pressed-border, hover, menu-active, menu-active-text, menu-active-bar.

Other tokens: ui-font, content-font, code-font, radius, ui-size, content-size, focus-width, control (minimum control size: 32px, or 44px on coarse pointers; leave it to the editor).

Optional tokens have defaults in the base theme, so a skin only sets them to change them:

TokenDefaultUse
--brt-caret var(--brt-text) Caret colour in the writing area. Studio uses the accent.
--brt-measure none (46rem in fullscreen) Maximum line length of the writing area. The content is centred when it is narrower than the editor.
--brt-elevation a short, tight shadow box-shadow for popovers, menus and the table bar. Set none for flat skins.
--brt-duration 120ms Length of hover, press and popover transitions. Reduced motion turns them off.
CSS
.my-app .brt-editor { --brt-measure: 40rem; }

Writing a custom skin

Use defineSkin and registerSkin, then pass the id:

TypeScript
import { defineSkin, registerSkin, skins } from '@better-rich-text/skins';

registerSkin(
  defineSkin({
    id: 'harbour',
    label: 'Harbour',
    description: 'Navy and brass.',
    reference: 'Ship chandlery signage: navy enamel and brass.',
    base: { ...skins.studio.tokens.base, radius: '4px' },
    light: { ...skins.studio.tokens.light!, accent: '#1d3f6e', focus: '#1d3f6e', pressed: '#1d3f6e', 'pressed-border': '#1d3f6e', 'menu-active': '#1d3f6e' },
    components: (s, editor, layer) => `
      ${s} .brt-toolbar { border-block-end: 2px solid #b08d3c; }
      ${editor} { border-radius: 8px; }
    `,
  }),
);

createRichTextEditor('#editor', { skin: 'harbour' });

A skin must define every colour token for each scheme it supports and every base token. Prefix component selectors with s (editor and popover layer), editor or layer. The dark palette only emits tokens that differ from the light one.

You can also ship plain CSS for an id that is not registered. The editor sets data-brt-skin="harbour" and leaves the styling to you:

CSS
@layer brt {
  :is(.brt-editor, .brt-popover-layer)[data-brt-skin="harbour"] { --brt-accent: #1d3f6e; }
}

Keep these when you change a skin:

  • Text at 4.5:1 against every surface it sits on; focus rings and input borders at 3:1.

  • Pressed, mixed and disabled states that differ in shape or value, not hue alone. The pressed-border token, or a fill at 3:1 against the toolbar, covers pressed.

  • Logical properties (inline-start, block-end) so right-to-left layouts mirror.

  • No --brt-control below 32px. Forced-colours and reduced-motion handling comes from the base theme; if you draw state with box-shadow or gradients (which forced colours removes), add a @media (forced-colors: active) fallback.

The skin tests in packages/skins/test check scoping, required tokens and contrast for every built-in skin.