Before and after tokens

Compares the same button before and after conversion to tokens; the browser checks the computed styles match, so 'the look stays' is measured.

Organism · stable · v1.0.0 · owner: Site system lead · contract 19 of 20 points (open: figma)

Do and don't

Do: The comparison

Rendered by before-after.ts

Open example: Before and after tokens · Do: The comparison Use the full-page example to follow page links.

Measured, not claimed.

Do: With the chain

Rendered by before-after.ts

Open example: Before and after tokens · Do: With the chain Use the full-page example to follow page links.

Shows where each value comes from.

Don't: Claiming full automation

Rendered by before-after.ts

Open example: Before and after tokens · Don't: Claiming full automation Use the full-page example to follow page links.

The rule was written from the tokens; the caption says so.

Don't: Without the computed check

Rendered by before-after.ts

Open example: Before and after tokens · Don't: Without the computed check Use the full-page example to follow page links.

The match is the proof.

Rules

RuleWhyCheck
Before and after tokens is computed by Loom's code from an open-source notes app's base button rule and the tokens Loom generated for it, not drawn by hand.A showcase is proof only if the product's own code produced what it shows.Automated: scripts/site/interactives/check.ts (recomputes the showcase data and fails on drift)
The match is computed in the browser, not asserted.Only a measurement proves the look stayed the same.Automated: tests/site/demos.spec.ts (both buttons compute the same styles)

API

PropTypeRequiredDefaultWhat it does
langen | jaNo"en"Root interface locale; changing it requires disposal and preparation. Existing source output and static explanatory copy remain English.
instanceKeystringNoOptional deterministic unique key for repeated static specimens; letter first, then letters, digits, underscore or hyphen, up to64 characters. Omit for the existing singleton anchors; duplicate explicit keys are unsupported.

Contract details

The full contract: identity, anatomy, variants, states, tokens, layout, motion, accessibility, content, tests

Identity

AspectValue
Idbefore-after
LayerOrganisms
Categoryshowcase
OwnerSite system lead
Statusstable
Version1.0.0
Sourceapps/showcase/site/system/organisms/before-after.ts (private repository; the contract and the render function are this one module)
VersionDateChange
1.0.02026-10-26Before and after tokens: computed by Loom's code from an open-source notes app's base button rule and the tokens Loom generated for it.

Use when

  • Shape and its demo page. Why: It proves the conversion keeps the product's look.

Do not use when

  • To claim every rule converts automatically. Why: The rule shown was written from the generated tokens; the caption says so.

Anatomy

PartWhat it is
rootdiv.demo-ba
panesBefore and after code
previewThe two buttons with a scrubber
resultComputed styles comparison
chainToken → semantic → primitive chain
captionSample from a real open-source app

Made of: showcase-frame, code-block, control, caption.

Used in: pages directly.

Variants

VariantWhenWhy
fullOn its demo page and its section.The whole experience where the claim is made.

States

StateRendered or not applicable
defaultStatic comparison/caption remains readable before prepared conversion data and root-local locale enhance the scrubber.
hoverNative range and endpoint buttons use their hover affordances.
focus-visibleThe native comparison range and endpoint buttons show focus; keyboard changes retain their control focus.
activeThe reader scrubs directly or selects a before/after endpoint; the one visible sweep is a motion profile, not a Play/Pause state.
disabledNot applicable: The comparison has no disabled-controls state; enhancement failure leaves its static specimen.
loadingRequired conversion data and root-local locale prepare before synchronous managed initialization; the original static comparison remains readable.
emptyNot applicable: Required conversion data is validated before DOM initialization; no empty comparison UI is invented.
errorPreparation/chunk/init failure preserves or restores the static comparison and records failed; remount may retry. Computed visual differences are comparison results, not transport failure.
currentRange value and aria-valuetext name Before, After or a percentage after; code/caption reflect the selected comparison.
visitedNot applicable: No visited state on this root; any contained native links retain their own browser history.
expandedNot applicable: No disclosure state on this root; replacing a view is not aria-expanded.

Tokens

PartToken
pane.ground--term-bg
result.same--teal-ink word
result.diff--madder-ink word
--before-after-border-radius-radiusvar(--radius)
--before-after-border-linevar(--line)
--before-after-gap-space-3var(--space-3)
--before-after-font-size-fs-labelvar(--fs-label)
--before-after-gap-space-4var(--space-4)
--before-after-font-type-labelvar(--type-label)
--before-after-color-teal-inkvar(--teal-ink)
--before-after-gap-space-5var(--space-5)
--before-after-font-type-smallvar(--type-small)
--before-after-color-madder-inkvar(--madder-ink)
--before-after-gap-space-6var(--space-6)
--before-after-accent-color-indigo-inkvar(--indigo-ink)
--before-after-border-line-strongvar(--line-strong)
--before-after-color-ink-2var(--ink-2)
--before-after-padding-space-6var(--space-6)
--before-after-background-term-bgvar(--term-bg)
--before-after-color-term-fgvar(--term-fg)
--before-after-font-size-fs-bodyvar(--fs-body)
--before-after-background-madder-inkvar(--madder-ink)
--before-after-padding-space-1var(--space-1)
--before-after-background-teal-inkvar(--teal-ink)
--before-after-color-term-mutedvar(--term-muted)
--before-after-padding-space-2var(--space-2)
--before-after-padding-space-7var(--space-7)
--before-after-background-sample-onvar(--sample-on)
--before-after-color-sample-1var(--sample-1)
--before-after-font-family-font-samplevar(--font-sample)
--before-after-font-size-fs-smallvar(--fs-small)
--before-after-margin-top-space-2var(--space-2)
--before-after-scrollbar-color-term-accentvar(--term-accent)
--before-after-scrollbar-color-term-fgvar(--term-fg)

Layout

AspectValue
breakpointsRequired review at 390, 768, 1024 and 1440 px; execution is not implied by this declaration; columns stack under 768 px and controls fill the row on phones.
wrappingText wraps; code panes scroll inside their own focusable region.
textExpansionJapanese strings come from the i18n tables and wrap; engine output stays English.

Spacing and rhythm

AspectValue
internalInside the Showcase frame's padding, controls and caption at its foot.
externalFull width of its section.
rhythmOne showcase per section, after the section's claim (D-SITE-008).

Motion

AspectValue
movesOne automatic sweep on first visible entry or a manual endpoint animation; direct range input follows the reader.
triggerOne IntersectionObserver entry at threshold0.5; native endpoint buttons or range input.
duration700ms interpolation; automatic return begins after1300ms, per the existing before-after profile.
easing1-(1-progress)^3 for endpoint interpolation; direct range input has none.
interruptibleManual input cancels pending automatic return. Hidden/offscreen/live reduced preference settles the current target and cancels RAF/return timer.
reducedNo automatic sweep or animated interpolation; native controls remain direct.
whyPreserves the authored comparison and lets the reader inspect the same visual result.

Runtime and reuse

  1. stylesheet: /css/site.css. Built Site tokens, core and registered element CSS; preserve modes, focus and responsive rules.

  2. stylesheet: /css/demos.css. Existing legacy demo styles where the rendered specimen uses them.

  3. font: /fonts/. Self-hosted files referenced by Site @font-face, with licences; Japanese locale overrides remain applicable.

  4. data: /i18n/ja.json. Required for a Japanese root; English interface fallbacks are authored in source. Root language changes require disposal and fresh preparation.

  5. data: /data/conversion-button.json. Required first-party native source data; fetch aborts with the mount and invalid consumed fields reject before DOM initialization.

  6. Static fallback: Original build-time specimen remains readable before loading and after chunk/initialization failure; enhancement controls retain authored hidden state.

  7. Before DOM initialization the configured host awaits apps/showcase/site/demos/before-after.ts#prepare for this root with a mount-owned AbortSignal. Preparation must throw on missing/invalid data; disposal/replacement aborts pending work, and a stale result cannot initialize or claim ready.

  8. Configured initializer: apps/showcase/site/demos/before-after.ts#init. The host owns transactional static-markup restoration and runtime state.

  9. Consumer module (imports relative to the configured source root):
    import { createRuntimeHost } from "./src/workflows/runtime-host";
    import { SITE_RUNTIMES } from "./apps/showcase/site/runtime-registry";
    const definition = SITE_RUNTIMES.find(entry => entry.binding.id === "before-after");
    if (!definition || definition.binding.lifecycle.kind !== "managed") throw new Error("Managed runtime binding unavailable");
    const host = createRuntimeHost([definition]);
    export function mountRuntime(container: ParentNode) { return host.mountAll(container); }
  10. After inserting the rendered markup, call mountRuntime(container) for the container holding [data-demo="before-after"]. Retain the returned disposal function and call it before removing, replacing or remounting, including every Storybook args render. The configured host restores captured static markup/attributes on disposal or caught load/initialization failure; retry starts a fresh managed remount.

Accessibility passport

AspectValue
roleregion with a heading, or a figure
nameIts heading or caption
keyboardTab to the comparison range or endpoint buttons; native range arrows/Home/End scrub; endpoint buttons use Enter/Space.
focusReader-operated controls keep focus; no playback-control focus order is implied.
screenReaderThe range names Before/After/percentage; the comparison result states actual computed style agreement.
contrastInk on surface 14:1; state dyes only as marks with words.
targetControls are 44 px.

Content

SlotLengthVoice
caption2 sentencesWhat was compared and how

Good: “The browser compares the computed styles of both buttons, and they match.”

Bad: “Pixel perfect, always”

Japanese: English only; the Japanese Shape page describes it.

Icons and illustrations

None: this element uses no icon or illustration of its own.

Decisions and exceptions

Usage

138 uses across the built pages (measured by npm run site:system).

Tests

AspectValue
unittests/unit/site-system.test.ts: renders every do and don't example through the registry and checks the classes it owns
visualNot yet: there are no screenshot baselines; the Library page is checked for sideways scroll at 1440 px by tests/site/system.spec.ts
a11ytests/site/system.spec.ts: axe (0 serious or critical) on the element's Library page in light and dark
parityscripts/site/system/check.ts: Library examples are rendered by the same render function the site imports

Figma

not-yet: The Loom Site Figma library is the last stage of the plan; this element is not mapped yet.

Open in Storybook

Start with a conversation about your system.

We read your system, show you what we found, and agree with your team what to fix first.