Back to blog
Quality & community

Why Every Live Preview Has a Demo Twin

Preserve the site/runtime boundary with static preview twins, class-mirror tests, accessibility isolation, and component-level visual baselines.

DucksssArchitecture · Component previews · Testing
Class-name fidelity assertion beside the aria-hidden, no-links, no-buttons, and no-headings guard for presentational demo twins.

Payload Components is two systems in one repository. The public site is a Fumadocs-powered Next.js application with no Payload database. The installable component source targets somebody else's Payload v3 application. Importing target code into the site would collapse that boundary and make a documentation deploy depend on a CMS runtime it does not own.

Demo twins let the catalog show real component design without crossing that line. A twin is a static, site-only preview that contains the installable component's visual class tokens while using fixture content and no Payload imports.

A one-way class token contract connecting installable Payload source to a site-only demo twin while each remains in its own runtime
The token-presence guard keeps every source class token represented in the twin without requiring identical token sets or a runtime dependency.

Why not render the source directly?

Installable files can import generated Payload types, project aliases, link renderers, media utilities, or server modules expected in a consumer starter. The docs site intentionally has none of that application context. Adding mocks until the source imports would slowly recreate a fake Payload app inside the marketing site.

That approach would also blur ownership. Code under payload-components/source must remain portable target source. Code under src/components/site/demos belongs to this site's presentation and test environment. The architecture guide documents the split.

It also keeps the public site fast to understand. A new contributor can run the catalog without seeding content, configuring a database adapter, or creating PAYLOAD_SECRET. The preview is a documentation artifact with a narrow job, while the installed component is exercised in dedicated consumer fixtures where Payload behavior belongs.

The twin pays a small duplication cost to keep deployments independent and the component contract honest.

Keep source class tokens present

The key rule is that every visual class token found in the source component is also present in the demo. An integration test reads both files and enforces that one-way containment. Change a spacing class, grid breakpoint, radius, or typography token in source, and the corresponding twin must carry it in the same pull request.

The guard does not require identical control flow or identical token sets. Target source can narrow Payload relationships, render real links, and handle wrapper props. The twin can use small static arrays and demo-only layout classes. What should not drift is the source styling users are evaluating in the catalog.

This rule makes the preview useful as design evidence. Without it, the site could advertise one component and install another.

A structure-only Hero Basic demo fixture beside the Hero Basic documentation Code tab and the real class-token mirror assertion from the demo-twin integration test
The preview is explicitly labeled as fixture content; the source and test panels show how the docs site mirrors shipped class tokens without importing the Payload runtime.

Keep the twin out of the accessibility tree

Catalog cards already contain headings, links, and controls for browsing. A second fake hero heading or FAQ button inside the preview would pollute the document outline and keyboard order.

Twins remain aria-hidden, contain no interactive elements, and avoid heading elements. Decorative text can use neutral containers styled like headings. Buttons and links become noninteractive visual shapes. The installed component retains real semantics and behavior.

This is not permission to ignore accessibility in source. It is deliberate isolation of a miniature visual specimen from the surrounding page. Component docs and browser tests inspect the actual source contract separately.

The accessible FAQ article shows how interactive semantics live in the installed version while its preview remains inert.

Give every twin a chrome-free route

Each registered demo can render at /components/preview/<slug> without the site header or footer. The route owns its viewport, so Tailwind breakpoints respond to the actual frame width rather than a CSS-scaled desktop canvas. Docs embed that route, and Playwright captures it for visual baselines.

Stable routes make failures reproducible. A reviewer can open the exact specimen, compare mobile and desktop behavior, and update a baseline only when the visual change is intentional.

The preview route has no database and no live editor content. That keeps capture fast and deterministic.

Test source, twin, and screenshot together

Three checks protect different facts:

  1. Class-token containment: every source class token is present in the twin, which may also have demo-only tokens.
  2. Visual standards: both rely on named color, radius, spacing, tracking, and font tokens.
  3. Visual regression: the rendered twin matches a committed platform baseline.

None replaces the others. A pixel snapshot cannot explain that source drifted while the twin stayed stable. A class comparison cannot catch a changed DOM arrangement or asset. A token rule cannot prove the section still looks correct.

The visual regression guide follows the capture and coverage pipeline.

Update both sides in one change

When editing an installable component, open its twin immediately. Preserve wrapper behavior in source, preserve inert preview behavior in the twin, and run the focused mirror and visual tests. Only regenerate snapshots for an intended design change. Linux baselines are minted in the CI renderer so the gate compares like with like.

When adding a new component, register its twin before writing the docs page. The docs preview renders nothing without that registry entry, and a missing baseline should fail coverage once the platform's baseline set exists.

Install a real component and compare its source with the catalog specimen:

npx payload-components add hero-basic

Open the Hero Basic docs and the component catalog, then inspect payload-components/source beside the demo. If you find a visual mismatch, contribute the source/twin correction and the focused test result through the contributing guide. The twin architecture works only when the community treats fidelity as one shared change.

Keep reading