Migrating 0.7 to 0.8
Machine-readable migration graph: MIGRATIONS.json —
it has no entry for this pair, because nothing was renamed or removed. Every
class, token, attribute, and export valid in 0.7 is still valid.
0.8.0 comes from auditing one real consumer's stylesheet in full. It repairs an accessibility floor, adds three surfaces that consumer had to hand-write, and changes what a colorway does. Only the last of those needs a decision from you.
1. Decide what your skins should look like — the one breaking change
Only affects you if you set data-bronto-skin. If you don't, skip to §2.
Until 0.7, a colorway moved --accent and nothing else; the neutral canvas
stayed grey, and ADR-0001 step 4 said so. From 0.8 a colorway also re-points ten
canvas tokens per theme:
--bg --bg-elevated --panel --panel-strong --panel-soft
--line --line-strong --text --text-soft --text-dim
So "Amber CRT" now actually renders amber. Nothing in your source changes and no checker will flag anything — the break is purely visual, which is why it is worth reading rather than discovering.
Contrast is not a regression risk. Each neutral keeps the core token's OKLCH
lightness exactly and moves only hue plus a small chroma, and check-contrast
re-measures all 21 gated pairings per skin per theme. Status colours
(--success / --warning / --danger / --info) are untouched by design: a
warning must look like a warning in every skin.
If you already hand-wrote a canvas for a skin
Delete it. That workaround is what motivated this change, and a hand-rolled version is almost certainly not contrast-gated, probably covers one theme, and probably misses whichever skin nobody opened.
If you want the 0.7 look back
Re-declare the ten tokens after the skin import. They are ordinary custom
properties on a :root[data-bronto-skin=…] selector inside @layer bronto, so
un-layered app CSS wins without a specificity fight:
@import '@ponchia/ui';
@import '@ponchia/ui/css/skins.css';
/* Keep the neutral canvas grey under every colorway. */
:root[data-bronto-skin] {
--bg: #f4f4f2;
--bg-elevated: #fbfbfa;
--panel: #ffffff;
--panel-strong: #ffffff;
--panel-soft: #ececea;
--line: #d8d8d4;
--line-strong: #a8a8a2;
--text: #0a0a0a;
--text-soft: #353533;
--text-dim: #686863;
}
(Those are the 0.7 light values; take the dark set from tokens/resolved.json
and repeat under :root[data-theme='dark'][data-bronto-skin].)
2. Delete your tap-target workaround
The coarse-pointer floor was written as a bare 2.9rem, and css/base.css sets
html { font-size: 0.9375rem } — so it resolved to 43.5px, half a pixel
under the 44 that WCAG 2.5.5 and both platform HIGs require, and less than that
under any host with a smaller root. If you noticed and declared your own 44px
floor, you can now drop it:
-:root { --touch-target: 44px; }
-
-@media (pointer: coarse) {
- .my-control { min-block-size: var(--touch-target); }
-}
Bronto's controls float to var(--tap-target) — max(44px, 2.9rem) — on their
own. For your own controls, consume the token rather than a literal:
@media (pointer: coarse) {
.my-control {
min-block-size: var(--tap-target); /* 44px floor, WCAG 2.5.5 */
}
}
--tap-target-min is the WCAG 2.5.8 AA 24px floor, for controls that only have
to clear the smaller bar. Both clamp in px on purpose: keep the clamp if you
override them. A bare rem is how a 44px floor quietly becomes 43.5px.
3. Delete your safe-area declarations
0.7 had no env() awareness at all. If you declared your own insets, drop them —
the same four names now ship:
-:root {
- --safe-area-top: env(safe-area-inset-top, 0px);
- --safe-area-bottom: env(safe-area-inset-bottom, 0px);
-}
Eight viewport-anchored surfaces now read them: the app rail and topbar, a
sticky site header, the skip link, both toast stacks, the drawer modal, and the
lightbox. Every rule uses max(<authored>, var(--safe-area-*)), so desktop
rendering is unchanged.
They are indirected through custom properties rather than calling env() at the
point of use, which matters twice: a desktop test runner cannot emulate env()
but can override a property, and a host running inside its own chrome (an
embedded webview, a kiosk frame) can declare the real insets. Follow the same
convention for your own floating chrome:
.my-floating-bar {
inset-block-end: max(1rem, var(--safe-area-bottom));
}
4. Two new opt-in ergonomics
Neither is required; both replace a common workaround.
ui-button__label — an icon button can keep its words for the accessible
name and for text-based test selectors while giving back the pixels. One markup
shape serves both forms, and no aria-label can drift out of sync with the
visible wording:
<button class="ui-button ui-button--icon">
<span class="ui-icon" style="--icon-mask: …"></span>
<span class="ui-button__label">Delete</span>
</button>
Drop --icon and the same markup renders glyph + word. The slot ellipsises
rather than wrapping, so a labelled button in a tight bar shrinks instead of
pushing its neighbours out.
ui-button--dense — for bars whose height is the constraint: a pane title
bar, a packed toolbar, a table row's actions. It lowers only the visual floor,
to --tap-target-min. The coarse-pointer block still floats it to the full
--tap-target, so a control you shrink for a mouse is never shrunk for a
finger. ui.button({ size: 'dense' }) in the recipe API.
5. Retire your own severity vocabulary
Nothing forces this, but it is the reason most likely to have produced
divergent code. Bronto shipped the tones without the scale, so consumers
invented tier names — and inside one app they drift, because each surface was
written on a different day. If you have more than one, css/state.css now
publishes the canonical ladder:
critical › error › warning › notice › ok (+ unknown, outside the order)
import { severity, SEVERITY_LEVELS } from '@ponchia/ui/classes';
severity('critical'); // { class: 'ui-severity', 'data-level': 'critical' }
severity('warning', { part: 'row' }); // { class: 'ui-severity-row', … }
SEVERITY_LEVELS; // sort and filter from this, not a local copy
The level travels on data-level — one attribute name — so a chip, a row, a
dot, and your own element all read the same selector, and your own element can
take var(--severity-tone) without copying a colour table.
Map your existing tiers onto it rather than keeping both. unknown is the
landing spot for anything unmeasured or stale; do not map it to ok, which
is an assertion of health and is how a dead collector reads as a healthy system.
6. Other surfaces you may be hand-rolling
Each of these replaced something a real consumer had built locally. None is required.
.ui-pane— a grab header, an in-place rename input, and an actions slot that scrolls rather than pushing its last control past the clipped edge. If you have a node/window/panel with a draggable title bar, this is it..ui-panelis still just a padded card..ui-toolstrip--pane— a control bar belonging to one pane rather than to the app: no frame of its own, and it refuses to wrap so a second row cannot resize live content underneath. Mark the shrinking element with.ui-toolstrip__fill..ui-selectionbar--anchored(and the same on.ui-toolstrip) — viewport anchoring for a floating bar, safe-area aware. Use--anchor-block-startfor the bar that must not sit under the thumb..ui-empty-state__glyph/__lead/__hintand--invite— the three parts every empty surface re-invents, plus the distinction between reporting absence and offering the next action.
7. If you read tokens.dtcg.json
Six new scale tokens are deliberately absent from it, listed in the root
extension's omittedCssVariables: the two tap-target floors are max()
comparisons and the four safe-area insets are env() reads, and neither has a
conforming DTCG shape. Emitting one arm of a clamp, or the 0px fallback of an
env(), would publish a value that is wrong everywhere it matters. Read
tokens.json for the authored CSS. This is the same treatment --shadow and
the em trackings already get.
Nothing else changed
No class was renamed or removed. No export moved. bronto-ui-check will not
report anything new for a 0.7-clean consumer — which is worth stating plainly,
because the one breaking change in this release is invisible to it.