Labels and theming

Every string, colour and size, and how the canvas looks, owned by the host.

Labels

No string the editor shows is hard-coded: they all come from labels, in two sections. chrome holds the editor's own copy (placeholder, upload states, the slash menu's empty state, the counts), items the name of every toolbar and slash item by its key, open for a custom feature's own keys. Sections merge one level deep: name only what you translate.

<ContentEditorField
	labels={{
		chrome: { placeholder: "Écrivez, ou tapez / pour insérer un bloc…" },
		items: { bold: "Gras", italic: "Italique", stockQuote: "Cours de bourse" },
	}}
/>

DEFAULT_CONTENT_EDITOR_LABELS is the English set, and the type ContentEditorLabelsInput the shape a translation file conforms to. The repository's check-labels script fails on a literal string inside the module, which is what keeps it that way.

Appearance

appearance says what the canvas looks like. It changes no node and no feature, only how the same document is drawn.

AppearanceThe canvas
document (default)A page of prose: the kit's font, the prose width, a gap between blocks.
plainA mail being written, as in Gmail: the theme's font, lines stacked with no gap.
emailThe campaign card: the theme's card width, colours and font, on the theme's backdrop.
<ContentEditorField appearance="email" theme={brandTheme} />
Hello First name,
Your spot for Saturday is confirmed. Type {{ for a variable.
See you there, The team

Theme

The theme is every token the canvas is drawn with. It keeps the email block editor's token names, so a host's palette carries over as is.

TokenDefaultUsed for
color.brandvar(--color-primary)Buttons, highlighted cards, variable chips
color.inkvar(--color-card-foreground)Body text in the email card
color.mutedvar(--color-muted-foreground)Meta lines, placeholders
color.bordervar(--color-border)Card borders, dividers
color.cardvar(--color-card)The email card
color.canvasvar(--color-muted)The backdrop around the card
fontthe system sans stackplain and email text
localeen-USPrices and dates previewed on the canvas
headingFontSize{ 1: "22px", 2: "17px" }Email headings
gridGapPx16The gutter between columns
imageWidthRatio{ full: 1, contained: 0.6 }Email image widths
previewWidth{ desktop: 600, mobile: 390 }The email card width
variablessizesThe canvas's min and max height, block gap, prose width

Each section merges on its own: color: { brand: "#0f766e" } keeps the other colours. The defaults point at the kit's tokens, so the canvas follows the active theme and dark mode. Pass your email palette as literal colours when the canvas should preview exactly what a recipient receives: an email client has no CSS variables, so your renderer keeps its own.

<ContentEditorField
	appearance="email"
	theme={{
		color: { brand: "#0f766e", canvas: "#f1f5f9" },
		font: "Georgia, serif",
		locale: "fr-FR",
	}}
/>

useContentEditorTheme() gives a custom element's view the same tokens. There are no per-block styling props: a block that looks different in a campaign reads the appearance and the theme.

The two rules no utility class can say, the placeholder's position and colour, ship in @hoplallc/ui/styles/content-editor.css.