If you have been here before, you noticed. The cool slate-and-blue that dcyfr.ai wore since launch is gone from this site, replaced by warm paper, vermilion accents, and a serif reading voice. The identity is called phosphor, and this post is about the part of the redesign you cannot see: the engine that let us swap an entire visual identity by changing one attribute.
A redesign as a config change
Most redesigns are rewrites wearing a trench coat. Every hardcoded color, every component with an opinion about its own font, every one-off border radius becomes a migration line item, and the project balloons until someone cuts scope.
We spent the effort somewhere else. Before touching a single color, we extracted a theme engine : a token contract of 37 CSS variables covering surfaces, brand, status, type, motion, and radius. Components consume tokens and only tokens. A theme file supplies the values for one identity, scoped to a data-identity attribute on the root element, with a light block and a dark block. The site stamps that attribute at build time.
Once that held, the redesign collapsed into authoring one new theme file and flipping the stamp from slate to phosphor. The old identity did not die. Slate still ships from the same registry, satisfies the same contract, and renders the same components. It is no longer what this site wears.
What phosphor is
Phosphor trades the shadcn-derived cool neutrals for a warmer, more editorial temperament:
- Warm paper grounds instead of pure white, and a near-black dark scheme instead of navy-tinted charcoal.
- A vermilion accent ramp with four steps, because one accent value should never serve both fills and text. Filled controls use the 600 step, text on paper uses 700, and 400 is the text-safe step on dark grounds.
- A serif body face (Alegreya), with Geist retained for display and mono roles. Type ships as three role tokens, so the identity decides the faces and the engine decides where they apply.
- Stepped motion for transforms, continuous easing for color. Radius applies to interface chrome only; imagery stays square by design.
The gates that keep it honest
A contract nobody enforces is a suggestion. The component registry's CI enforces this one in both directions: a theme missing a contract variable fails, and a theme defining an off-contract variable also fails, in either scheme. A render matrix then mounts every published primitive under every identity and scheme in a real browser and checks WCAG AA contrast plus an identity-variance gate, which proves the two themes actually look different where they should.
That last check exists because token systems fail quietly. A component that hardcodes a color renders identically under both identities, and no human reviewing a single theme would catch it. The matrix does.
Where you can see both
Runtime theme switching is deliberately rare here. Every DCYFR surface pins one identity at build time, which keeps pages fast and keeps the system honest about what it ships. Exactly two surfaces are allowed to switch live, and both exist to show the engine working:
- The design colophon on this site renders every contract token, live, with a switcher that restamps the page between phosphor and slate in place.
- The registry gallery renders every published component under the same switcher, in both schemes.
Flip either one and watch the entire page reinterpret itself: same components, same markup, different identity.
What comes next
The engine and both themes live in our open component registry, installable with npx shadcn add @dcyfr-labs/dcyfr-theme-engine. The satellite sites still pin slate today and will adopt the engine on their own schedule, which is the point: identities are now versioned artifacts a site chooses, not css scattered across a codebase. When the next identity shows up, it will be a theme file and a build-time stamp, and this post will still be accurate.

Top comments (2)
Extracting the theme engine first so the redesign becomes a simple config swap is exactly how scaling should work. Treating redesigns as rewrites is a massive anti-pattern that usually ends in scope creep. I read a book on Amazon called Ship Your Design System recently, and the multi-brand architecture chapter advocates for this exact strict token contract to keep components completely agnostic. Did you run into any specificity headaches when scoping the light and dark blocks to that root data-identity attribute?
The identity-variance gate is the sharpest idea here, and it got me thinking about the failure sitting one level over from the one it catches.
Variance catches a component lying about where its value came from. A hardcoded color renders the same under both identities, so the matrix sees it.
What survives both gates is a component that picks the wrong token and stays correct mechanically. Use status-danger where brand-accent belonged. It's on contract, it varies between identities, it passes contrast. Every automated check goes green. The only thing wrong is meaning, and meaning is the one property the matrix can't render.
That's the gap I keep hitting with generated components specifically. A model picks from the contract, which is exactly what you told it to do, and picks a token that fits the slot without fitting the situation.
Curious whether you have anything encoding which tokens are legal in which roles, or whether that part still lives in review.