v2.0.0
The theme carried its own navigation. A dark-mode button that left its state to an
icon. A row of links that folded into a hamburger at a width typed into a stylesheet,
with a focus trap and an arrow-key walker behind it. A sidebar drawer driven by a
matchMedia listener. Three patterns, none of them this theme's business, and each one
a place where the markup and the ARIA could drift apart. They are
book-of-elementals elements now —
<switch-elemental>, <navbar-elemental> and <disclosure-elemental> — and what is
left here is the layout around them.
The switch brought its own three bugs on a phone, all of them width: it is wider than
the icon button it replaced, and the bar it sits in had no room spare.
Changed
-
The
site.linksrow is<navbar-elemental>, and the topbar is that element. The
old row folded into a hamburger below 40rem and did nothing at all above it — so a
site with five links and a search field had them overlapping at 900px, and a site with
two short ones hid both on a tablet with room to spare. Neither is a width anybody can
type: it depends on the labels, the reader's font, and whether that font has arrived
yet. The element measures the row instead. Links move into a More panel one at a
time as the room goes, and when only one is left beside More the whole row is a drawer
— 40rem is still there as themediaattribute, but now it is the floor rather than the
whole story. That last stop ismin-bar-items="2": a single link beside an overflow button
is a drawer wearing a bar's clothes. The theme writes the attribute only for a site with
more than one link insite.links, because the threshold is read against the total as well
as against how many fit — set on a one-link site it would be a drawer at every width.DOM: the header is
<header class="topbar"><navbar-elemental>wrapping everything
in the bar. The links are<nav class="rail" aria-label="Site"><ul>between the brand
and the action group — the box takes the room the bar leaves, which is what the row is
measured against, and the links sit at its far end so they read as part of the controls
on the right rather than as a second brand. They end in a<li data-navbar-more>the element fills, and the
drawer's button is<button data-navbar-toggle aria-label="Site navigation">last in
the action group — empty, because the element writes the hamburger and the X it crosses
into. Gone:.topbar-nav,ul.topbar-links#topbar-links,.menu-toggleand its svg.
The sidebar's toggle is labelled "Documentation navigation" rather than "Toggle
navigation", now that a docs header has two navigation toggles in it.CSS:
.topbaris no longer the flex row — it is the sticky frame and the banner
landmark, and.topbar > navbar-elementalis the row, carrying the 1rem inset the
header used to have. That move is load-bearing: the drawer is positioned against the
element, so padding outside it would be a drawer floating clear of both edges of the
screen. The theme takes the element's two stylesheets and re-points
--navbar-elemental-surface,--navbar-elemental-border,--navbar-elemental-hover,
--navbar-elemental-shadowand--navbar-elemental-radiusat its own tokens.
A page styling.topbar-linkswants.topbar .railinstead. The
@media (max-width: 60rem)block that grew the search field is gone — the rail is the
flexible item now, and the field keeps its 16rem.Known limit: a sticky header is a positioned ancestor, so it is the containing
block for the overflow panel and the element'sposition-try-fallbackscannot fire
against the viewport. The panel stays under its own button, which is where it wants to
be; a bar whose More button ends up hard against the right edge is the case where
that shows.Script:
setupMenu()andfocusStep()are gone fromsrc/prose.ts— some seventy
lines, and the four tests that covered them with them, because what they implemented is
now the element's and jsdom cannot exercise a row that measures itself.prose.ts
importsbook-of-elementals/navbar, so both bundles register it. -
The sidebar drawer is
<disclosure-elemental>. It was amatchMedialistener, a
class on the panel and a focus trap; the panel is the element's region now and the
breakpoint is itsmediaattribute, declared once indocs.htmlrather than in the
markup and the stylesheet both. It is no longer modal — focus is not trapped and the
article is notinert, which is the APG disclosure pattern for what is, after all, a
list of links to the same site. What the theme still owns is the two ends the pattern
does not owe you: focus handed to the current page's link when the drawer opens, and
Escape or the scrim closing it.DOM:
<disclosure-elemental for="sidebar-nav" media="(min-width: 60rem)">wraps
the toggle; the element writesaria-expanded,aria-controlsand
hidden="until-found", so a closed drawer is reachable by find-in-page. CSS: the
drawer's rules key off[data-mode="free"]on the element and on the panel instead of
repeating 60rem, which also means none of them can apply before the script does. The
sidebar's own section toggles took the same caret while they were at it: the▸/▾text
markers are the chevron the elementals draw, as a mask onsummary::beforethat rotates
with[open]— so every caret on the page is one caret, and it takes its colour from the
text it sits beside. -
book-of-elementalsis^0.4.0(was^0.3.0). The row needs 0.4's two fixes to
be usable at all: the copy it measures is clipped, so a header whose links do not fit
no longer hands the whole page a horizontal scrollbar, and its items say
box-sizing: border-boxthemselves rather than assuming the page has. -
The theme toggle is
<switch-elemental>from
book-of-elementals, which is a new
dependency of this package. It writesrole="switch"andaria-checked, so the
state is announced rather than drawn, and the accessible name is "Dark mode" —
what the setting is, not what pressing it does, since "Toggle dark mode, switch,
on" says it twice.DOM: the topbar now emits
<switch-elemental class="switch-elemental-small switch-elemental-thin"><button data-theme-toggle>with a.switch-elemental-off
and a.switch-elemental-onspan inside it, in place of<button class="icon-btn" data-theme-toggle>with.theme-sunand.theme-moonsvgs. Both old classes are
gone, and so are the four rules that swapped them. The two icons are 14px, sized
to the knob thesmallpreset leaves rather than to the bar.CSS: the theme imports the element's own two stylesheets and re-points
--switch-elemental-knob-checkedto--bg, since the element's default there is
Canvasand a themed dark page keeps a light one. The size is the element's own
smallandthinpresets, 2.75rem × 1.5rem with a 1px border, taken as classes
rather than set here. The rest of the look is mixed out ofcurrentcolor, so a
page that themes the topbar's text colour themes the switch with it. Anything that
was styling.theme-sunor.theme-moonshould style the element's own two spans
instead. -
The topbar's right-hand group is spaced by inset, not by gap. An icon button
is a 2.25rem box around a 20px glyph, so it brings 8px to each edge and the switch
and the search field bring none — a singlegaplands on top of the first and on
nothing beside the second, and the icons read further apart than anything else in
the row. No number fixes that; the padding had to move.CSS:
.topbar-actionsisgap: 0and declares--actions-inset(0.5rem),
which.searchandswitch-elementaltake aspadding-inline—
the icon buttons already have it in their box. Any two neighbours are now 16px
apart.switch-elementalisdisplay: flexrather than the element's own
contents, so it has a box to pad, and.search-resultshangs off
--actions-insetrather than 0 so the panel still lines up with the field. A page
overriding the group's spacing wants--actions-inset, notgap.The group's outer edge moves in by that inset too: the last control's ink sits
24px from the viewport rather than 16px, against the brand's 16px on the left.
The docs layout already looked like that — its leftmost control is an icon
button. -
The site title truncates instead of pushing the bar wider.
.brandis a block
withtext-overflow: ellipsisrather than a flex row, and.brand-markkeeps its
place onvertical-alignand a margin instead ofalign-itemsandgap. A page
overriding either should check it still lands.
Added
-
--dangertoken,#e03131light and#ff8787dark, alongside--linkand
--focus. Nothing in the theme itself paints an error yet; the token exists so
elements embedded in a page can find one. It matches the name sulphuris already
emits through$color-aliases, so a component styled against one is styled
against the other. -
.prose code-preview > :is(pre, .code-wrap) { margin: 0 }, so a page using
<code-preview>no longer has to
write it. The element resets that margin itself, but itscode-preview > :is(pre, .code-wrap)is one class and one type against this theme's two-class
.prose :is(figure, .code-wrap)— so the theme's 1.75rem came back as a gap between
the frame and the code under it, doubled oncedocs.jshad wrapped theprefor its
copy button. Every site using the element was copying the same rule out of the
package's README; the specificity is the theme's, so the rule is now too.The package itself stays a non-dependency and is in neither bundle: a docs site with
no live samples should not carry an editor and an iframe runtime. Loading it is still
the consuming site'spoops.json, which is what the new Live samples section of
the README says, along with the two accommodations the theme already made (--danger,
and standing the copy button down inside the element) that were nowhere written. -
--focusand--dangerin the README's token list, which had neither.
Fixed
-
The search icon sat on the field's rounded corner rather than inside it.
.search
carries the action group's--actions-insetas padding, and an absolute offset resolves
against the padding box — so the icon'sleft: 0.6remwas 0.6rem from the wrapper and a
tenth of that into the field. It iscalc(var(--actions-inset, 0rem) + 0.6rem)now, the
same compensation.search-resultsalready made. -
On a phone, the theme switch painted on top of the search field that opens
over it. The switch's button isposition: relative— the knob is positioned
against it — and it comes after the field in the row, so with both at
z-index: autodom order decided which covered which. The open field now takes
a layer of its own. -
The topbar overflowed the viewport on a phone, and took the page with it: the
document picked up a horizontal scrollbar and the prose scrolled off the right
edge. A flex item does not shrink past its min-content width unless it is told
it may, and every control on the bar is fixed-width, so the title had to be the
one that gives —.brand-groupand.brandaremin-width: 0now and the
title truncates. Which title overflows, and at which width, is the site's own:
the switch is wider than the icon button it replaced, so a name that used to
fit no longer does.Below roughly 25rem there is no room left for a title at all, only for the
controls. If that is your site, thedocspill is the item to drop on a phone. -
The sidebar's rule stopped wherever the nav ran out — halfway down the page on a
short tree, somewhere else on the next page, so the line moved as you navigated.
The rail carries the border down the full column now.CSS:
.sidebarisheight: calc(100dvh - var(--topbar-h))rather than
max-height, and the scrolling moved off it —overflow-y: autoand
overscroll-behavior: containsit on.sidebar > .navnow, which is
max-height: 100%. A page overriding the rail's height, or hanging anything off
its scroll, has to move with them.
Full Changelog: v1.1.2...v2.0.0