Cookbook
Chrome & accessibility
Navigation is the hardest surface to put a field on, because it is the one that absolutely must work when the field does not. The same discipline that makes chrome safe — signals only, meaning in the DOM, a plain fallback by construction — is what makes any field accessible.
Shipped · demo runs live on this pageNavigation chrome, signals-only
bindFieldNav is the idiom this site spread across a dozen surfaces — top nav,
sidebar, breadcrumbs, pagers, footer — lifted into the platform. It runs a Pattern
signals-only over the links inside a root: each becomes a body, the current one
can be pinned as the well, and previously-visited links can be marked from a predicate you
supply. Nothing is drawn, and the visit log stays yours.
import { bindFieldNav } from '@fundamental-engine/dom';
// Signals-only over real links: every <a> becomes a body, the current link is pinned
// as the well, and "visited" is a predicate YOU own. Nothing is drawn.
const handle = bindFieldNav(nav, 'navigation-current', {
pin: nav.querySelector('[aria-current="page"]'),
visited: (href) => visitLog.has(href),
});
// Returns NULL under prefers-reduced-motion, with no links, or on an unknown pattern —
// so the failure mode is "plain nav", never "broken nav".
handle?.destroy(); /* The binding writes the lanes; CSS decides what they mean visually.
Weight and ink only — no motion, nothing that moves a hit target. */
nav a {
color: color-mix(in srgb, var(--accent) calc(var(--field-attention, 0) * 100%), var(--text-3));
font-weight: calc(400 + var(--field-attention, 0) * 300);
}
nav a.nav-visited::after { content: ' ·'; } starting…
These are ordinary links: click them and they navigate. The weight and ink come from the attention lane the binding writes; the trailing dot marks a 'visited' route from this page's own predicate.
prefers-reduced-motion, with no links, or with a Pattern it cannot resolve,
bindFieldNav returns null and writes nothing. There is no degraded
state to design, because the undegraded state is the HTML.
The three rules that keep a field accessible
- The canvas carries nothing. It is
aria-hidden, not focusable and click-through by construction. If a reader would lose information with the canvas removed, the information is in the wrong place. - Meaning lives in the DOM. The field measures semantic HTML and writes numbers back onto it. Assistive technology reads the same heading it always did; the field only changes how emphatically it is drawn.
- Reduced motion keeps the meaning. The engine stops integrating under
prefers-reduced-motion, and engagement still works through focus as well as hover. What you build on top must degrade to a static equivalent — weight, ink, a border — not to nothing.
<!-- The canvas is decorative by construction: aria-hidden, not focusable,
click-through. Meaning never lives in it. -->
<field-root></field-root>
<!-- The meaning lives in the DOM, where assistive tech already reads it.
The field AMPLIFIES the heading; it does not carry it. -->
<h2 data-body="attract" data-feedback>Q3 sales</h2> /* Reduced motion is not "less animation" — it is the same MEANING,
expressed without movement. The engine stops integrating (dt = 0); make sure
what you built on top degrades to a static equivalent, not to nothing. */
@media (prefers-reduced-motion: reduce) {
.card { transition: none; }
/* keep the signal, drop the travel */
.card { border-left-width: calc(1px + var(--d-amp) * 3px); }
} The platform's own guarantees, and a side-by-side of the animated field against its static fallback, are on the accessibility page and its preview.
Checking your own surface
- Turn the field off. Every docs page here has a toggle for exactly this reason: the page must still be complete.
- Turn motion off. Then re-read the page and ask what information you lost.
- Tab through it. Focus must engage a body the way hover does.
- Run the lint.
lintPlatform()catches the feedback-lane mistakes — a body that writes a variable nothing reads, or CSS reading one nothing writes.