A new look for the whole editor, and one source for it.
The chrome used to be painted colour by colour from script — the same blue written in dozens of places, each hover handler choosing its own grey. It now draws its colours, radii and shadows from one set of design tokens, in the style the comment panel introduced: white cards on a light stage, one accent colour, hairline dividers. Changing the look, or a host rebranding it, is now a matter of changing a value in one place.
The editor has a new look. Menu bar, toolbar, drawers, parameter bar, page bar, layer list, dialogs, popups and colour panels are restyled as one family: raised white cards, rounded corners, a single accent colour for whatever is active or selected, and a light grey stage behind the page.
⚠️ Nothing moved and nothing was renamed. Every control is where it was, and every element id and existing class name is unchanged. Only the paint is new.
The right-hand drawers are the same width and swap in place. The parameter bar is now 320px, like the comment rail. Switching between the two is an instant swap; opening one slides it in over the canvas and narrows the canvas only once it has arrived, so there is no white strip while it opens or while one replaces the other.
The parameter bar is laid out in two columns. Fields sit side by side with their labels under them; wide fields span both. Every row lines up on the same insets, every field box is the same height, and section headers with a switch put the switch on the fields' right edge. The panels with a header and a row of cells — rotate, shadow, corners, text wrap — spread their cells across the bar.
New edge tabs. The two tabs on the right edge are white cards with a thin chevron that turns the accent colour while its drawer is open.
Drawer titles are cards. Every drawer has the same 36px title card. In Layers, Manage is a round icon button beside the title.
Find and Replace, Preferences and the viewer's floating toolbar use the same tokens: fields, buttons and the swatch picker match the rest of the editor.
On/off icons follow the accent colour. The link locks (corner radius, page scale, bleed) and the column-balance buttons were painted in a fixed older blue from script; they now use the accent colour, so a host's brand colour reaches them too.
A locked element looks locked in one place. Its position and size block is greyed as a unit, instead of every label and input on the page being greyed and restored one by one.
Popups open where they belong when the editor sits under a host's navbar. A popup opened from a hidden part of the interface — the page colour's tint slider, among others — was placed as though the editor started at the top of the window, so in a host with a navbar it opened 64px too low. Its position is now measured from where it will actually appear.
The colour panel stays on screen. Opened from a field near the right edge, it could hang up to 90px out of the window, because it was only pulled back once its middle had left the screen. It now keeps its right edge inside the window, and the vertical correction knows the editor starts under the host's navbar.
Dropping a layer no longer shifts the list. The drop indicator was a border that pushed every row below it down by 2px mid-drag. It now draws without changing a row's height.
The selected layer is readable. Its name was dark text on a bright blue fill; the selected row now uses a light accent tint behind the normal text colour.
Images and clips show the whole picture. The grid tiles lost their grey frame and no longer crop the image.
Everything here applies the moment a host loads the new bundle. There is no API change:
header_space, --typograph-header-height, the menu, element and document events all work as
before.
⚠️ Every host that embeds editor/latest changes appearance on deploy. That is the intended
result, but tell your users before it happens rather than after.
⚠️ Rebrand with tokens, on :root. Set --typograph-color-primary (and its -hover, -active
and -soft variants) and the rest of the tokens you want on your own :root. Your value always
wins, whatever order the stylesheets load in — the defaults are declared with no specificity for
exactly that reason. See Theming in Embedding.md.
⚠️ If your page links stylesheets/style.css itself, link the one from this release. The new
script relies on classes that only the new stylesheet defines; the old stylesheet with the new
script shows controls without styling. A page that does not link it gets the right one automatically.
A customised copy of an older style.css should become token overrides instead.
⚠️ Check any CSS you wrote against the old chrome. It still matches — ids and class names are
unchanged — but a rule that recoloured the old look may now paint over the new one. Host CSS with a
selector more specific than the editor's own (on #dialoglayer, for instance) still wins.
⚠️ Selection handles stay the old blue for now. They, the guides and the page bar's active badge are drawn by the canvas library, which moves to the tokens in a follow-up. The two blues are close. A few older corners of the chrome — tooltips, the start screen, the text-style samples, some image-manager and clip controls — also keep fixed colours until then.
The tokens are where a chrome colour is defined. They are at the top of style.css
under :where(:root): on :root rather than .typograph because dialogs and popups mount outside
the wrapper, and inside :where() so a host's plain :root override wins regardless of order.
The names are roles, not colours — surface, surface-sunken, line, border,
text-muted, stage. Near-identical greys were collapsed by role; a distinction survived only
where something needed it. A dark palette later overrides values, never names.
⚠️ TypeScript no longer writes chrome colours. It toggles is-open, is-active,
is-selected and on, and CSS decides what those look like. The six --typograph-comment-*
aliases are gone, so a host's primary now reaches the comment pins and mode switcher too.
⚠️ npm run check:chrome guards it, and build:all runs it first. tools/check-chrome.mjs
fails on any inline background*, color, border*, boxShadow, fill, stroke or outline
write in src/ — whether assigned, set through setProperty or style[…], or buried in an
Object.assign(el.style, …) or a cssText string — outside its allowlist, each entry of which says why it is allowed: geometry, document data (a swatch is its
colour), the JSON code view, Arrow.setColor. style.removeProperty(...) is always allowed — it
is how the editor strips the canvas's own inline chrome from the page bar rail. Add to the
allowlist with a reason, never to get a build through.
layer_box() measures a hidden layer where it will be. It used to estimate the origin of a
display: none layer from the editor root, which is wrong whenever the layers live in a
position: fixed container under a host's navbar — portal-ui's case, off by the navbar's 64px. It
now shows the layer invisibly for one synchronous layout and measures it; the root-based estimate
remains the fallback. A layer whose ancestor is hidden is measured from the nearest laid-out
ancestor..panel border is 1px where placement assumes 2px; comment pins cast a lighter shadow than the
cards; on-primary and surface-raised used as text fills will need an inverse token when dark
mode arrives; the clip skeleton is 127px tall. None is visible enough to hold the release.