Raptive · Design Systems · 2026

Design Systems Token Vitals

Auditing what a legacy platform’s code actually does, so a rebrand lands everywhere at once

In-depth case study · the work is ongoing

design-token-vitalsGitHubGrades a codebase’s design token layer, reports what it cannot prove, and ranks the fixes into a next move.
Company
Raptive
Role
Staff Product Designer
Duration
2026 — in progress
Scope
Token audit, semantic contract, and migration roadmap for Raptive Community
Team
Sole designer, partnered with Product Management and Platform Engineering · Discourse on Rails and Ember

What the audit returns first: eight vitals, four of them unproven. A hatched track is a reading not taken, never a score of zero — the grades, their notes, and why that distinction matters are below.

A detail from the platform audit board showing ten member-facing surfaces — Feed, Latest, For you, Categories, Community hot, Community latest, Topic, Tags, Search and Groups — each captured at desktop and mobile, annotated with the components it uses, and with those components isolated beneath it
Twenty of the audit’s 64 columns — ten surfaces at both breakpoints: Feed, Latest, For you, Categories, Community hot, Community latest, Topic, Tags, Search and Groups. Each screen is annotated with the components it uses, and has those components isolated beneath it. 32 surfaces, 64 screen and device columns in all.

87 canonical components, ranked by how much of the platform each one actually touches.

Every surface was captured at both breakpoints and audited as a screen. Responsive variants are merged and repeated instances inside one screen count once, so the number beside a component is the share of the platform that changes when it does.

Atoms

Foundational controls and visual primitives.

  • Button22%
  • Form field16%
  • Validation message16%
  • Avatar9%
  • Badge9%
  • Search field9%
  • Select / picker9%
  • Selection control9%
Molecules

Small functional groups built from atoms.

  • Filter controls34%
  • Reaction bar19%
  • Author row16%
  • Action group13%
  • Feed tabs13%
  • Profile tabs9%
  • Data visualization6%
  • Metric card6%
Organisms

Reusable interface regions and complex modules.

  • Top navigation88%
  • Bottom nav30%
  • Composer16%
  • Activity list13%
  • Post card13%
  • Sidebar11%
  • Admin navigation9%
  • Review nav9%

Three components are platform-specific and the reach ranking is what proves it rather than assumes it: the bottom nav exists only on mobile, the sidebar and the right rail only on desktop. A contract written from the desktop screens alone would have shipped a component the mobile app cannot use.

The platform did not have a consistency problem. It had a visibility problem: nobody could say which components a design change would reach until it shipped.

Raptive Community runs on Discourse, a Rails and Ember application whose codebase was started in 2013 — before design tokens were a practice and before CSS custom properties were a browser feature. Typography, color, spacing, radius and elevation decisions had accumulated across Discourse core, Raptive plugins, Sass variables and CSS custom properties. Each one was deliberate when it was made. Together they were fragmented — change a foundational token and some components followed, some reached past it into a primitive palette, and some read a mobile-specific copy of the same value defined in a different file.

Then a rebrand was scheduled. New typography, new palette, new button treatment, and this time the switch had to propagate cleanly across the whole platform. Finding out mid-launch that some of the buttons had updated was not an acceptable way to learn.

  • How might we see, before shipping, which components a token change will actually reach?
  • How might we order a migration by evidence rather than by whoever asks loudest?

None of the three could be answered from Figma. Figma holds the intent; the browser renders the code.

01What do we actually have479 token concepts live somewhere in the codebase. Which are real decisions, which are duplicates, which are orphaned?
02Why doesn't consistency holdChange one source and some components follow. Why the rest ignore it is written down nowhere.
03How do we migrate safelyIf the whole platform changes, which components move first, and what makes that order defensible?

So I built the scanner. It reads the production codebase — not a presumed token file — and grades what it finds.

It builds the Sass and CSS import graph, then separates the code Raptive owns from upstream Discourse. It resolves scalars, maps, aliases and custom properties into canonical concepts, and traces which files consume each one. Those consumers are grouped into components; literal colors are audited separately. What comes out is eight graded vitals and a fix queue ranked by impact. It is framework-agnostic and open source: design-token-vitals.

479Canonical token conceptsCollapsed from every definition the scan could resolve.
21Confirmed definition sourcesSome hand-maintained, some generated, some in conflict.
4Naming grammars in useCore color, Raptive color, typography, layout.
110Reachable declaration filesWith 89 more held as candidates or local overrides.
105Components consuming tokensAcross 354 component and style surfaces.
36.7%Of all references, top fiveImpact is concentrated, which is what makes an order possible.
26Exact-value color groupsHardcoded values that already have a token.
163Uncovered color groupsHardcoded values with no token to point at.
Vital · Single source

Same value. Four names. No semantic connection between them.

White appears 76 times across 20 files and answers to at least four token names. The brand primary has the same problem. Teams had each named the same decision separately, unaware the others had already named it — so changing one name leaves every other spelling of the same decision untouched, and nothing in the codebase says which ones those are.

#ffffff76 occurrences across 20 files
  • --background-inverse
  • --google
  • --mobile-background
  • --mobile-secondary
#6b65ff
  • --brand-primary
  • --icon-brand-primary
  • --mobile-primary-blue
  • --raptive-primary-blue

Value equality is proven; semantic equivalence is not. The scanner will not merge these on its own — deciding that a Google button and a mobile background are the same decision is a human call, and the report says so rather than quietly collapsing them.

Vital · Tier integrity

The button pulls 26 distinct tokens, 74 times, from more than one source file.

Some buttons consume a semantic layer, some reach directly into the primitive palette, and some read a mobile-specific override. Not because anyone decided that — because there was no shared contract, and nothing standing between a deadline and a local decision.

74Token referencesIn one component.
26Distinct tokensFor one visual decision.
2+Source filesSemantic, primitive, and a mobile override.
8.7%Of the token footprintBy references in code — not by screens.

369 rules in the platform paint something button-shaped. Twelve of them define enough to actually draw the button.

So I rendered every one from its own source. Each record carries the selector, the button drawn from the rule that paints it, its resolved geometry, the tokens it reaches for, and the file and line it came from — then sorts by how much the code will admit.

This is a third instrument counting a third thing. The audit counted token references inside the button component; the reach ranking counted screens a component appears on; this counts CSS rules that paint a button anywhere in the platform. 74, 22% and 369 are three different questions with three correct answers.

12Fully drawn contractsEnough evidence to render the button truthfully.
294Partial contractsFill, geometry or type still inherited or unresolved.
68Selector onlyA real class that defines no visual properties.
369Button-related rulesAcross 187 source files, from 552 candidates.

Most certain first.

The twelve highest-confidence records, and the reason the rebrand could not simply change one color: a neutral pill, a gray icon control, a white surface with a gray outline, a red fill, a red outline, a purple, two greens. Every one is a button. No two agree on fill, radius or type, and each cites a different file.

Twelve highest-confidence button records, each rendered from its own CSS rule with its selector, geometry, tokens, and source file and line
Tier 1 — the twelve records where the source defines enough to render the button truthfully. Each carries its selector, resolved geometry, the tokens it reaches for, and the file and line it came from.

The grayed and outlined placeholders are a rule, not a style: they mean the evidence is incomplete, never that the button is gray. A record only gets a color once the source or a runtime observation defines one. It is the same refusal the eight vitals make — the tool would rather show a blank than a color it cannot prove.

A sample of the partial button contracts: rows of records whose fill, geometry and type are marked inherited or unresolved
Tier 2 — fifteen of 294 partial contracts. Almost every panel reads “partial contract / inherited / unresolved”, and most cite no token at all: no token reference in this rule.

That is fifteen of 294, and the proportion is the point rather than the parade — the remaining 279 look the same. Below them sit 68 more that are selector-only: a real class, in a real file, defining no visual properties at all. Together they are why “change the button color” was never a one-line job. Twelve rules can be edited with confidence; the rest have to be resolved before anyone can say what editing them would do.

By token footprint the button ranks first. By platform presence it ranks fourth, behind a navigation bar that touches 88% of the product.

Both numbers are correct, and they answer different questions. Token footprint counts references in the codebase — it says where the migration work is. Platform presence counts screens — it says where a mistake would be seen. The button carries 8.7% of the token references and appears on 22% of the screens. The top navigation appears on 88% — I have not measured its token footprint, so I cannot tell you whether it is the cheaper fix, only that it is the more visible one.

The scopes differ too, which is easy to miss and expensive to conflate. The code audit resolved 479 token concepts across everything the platform loads, Discourse core included. The design-side inventory scopes to the 118 tokens Raptive actually owns, with the runtime Discourse palette feeding the rest. Neither number is the “real” one. Quoting either without saying which instrument produced it is how a slide deck becomes wrong.

118Explicit Raptive tokensThe ones Raptive owns outright.
78ColorSemantic, brand, mobile, interaction and feature families.
23LayoutSpacing from 2px to 128px, radius from 8 to 32.
17TypeSeven sizes, four weights, six line heights.

The same 87 components group a second way — not by how complex they are, but by what each group needs from the token layer.

01Shared shellHeader, sidebar sections, community items, icon buttons. Inherits global spacing, typography and the runtime palette.
02Social contentTopic and discussion cards, question cards, reaction controls and summaries, avatars. Adds avatar, radius, outline and reaction tokens.
03Operational controlsSelect and filter controls, admin navigation, report tiles, profile stats and badges. Relies on runtime theme tokens and must be aliased before any Figma library work.

Operational controls is the group that matters most and reads least urgent. Those components run almost entirely on runtime Discourse theme variables, so they cannot be represented in a Figma library at all until they are aliased into the same semantic color, spacing, radius and type families as everything else. Building the library before that aliasing would produce a design system that quietly excludes the admin, review and preferences surfaces.

The audit reports what it can and cannot prove about itself.

  • Naming coherenceFour grammars active; needs consolidation.fail
  • Tier integritySome consumers reach past semantics into primitives.attention
  • Coverage14 of 19 families confirmed; 5 unmeasured.attention
  • Single sourceMultiple names still resolve to one value.attention
  • Leakage26 exact-match and 163 uncovered candidates await semantic review.blocked
  • Mode completenessNo compiled light and dark output audited yet.blocked
  • OrphansReachability analysis incomplete.blocked
  • EnforcementNo token-aware CI gate proven.blocked

Blocked is not a failing grade. It is the tool refusing to grade what it has not measured — four of the eight say “no evidence yet” instead of inventing one. An audit that grades everything is not measuring; it is guessing with a rubric.

Design somewhere the framework cannot argue back, approve on clean evidence, then migrate in the order the audit already proved.

01Design in isolationA separate React and Vite environment outside Discourse's constraints — 224 tokens, 45 components, CI-integrated, built in four weeks.
02Approve on evidenceA portable feedback loop. Product, engineering and design could judge the new language without first asking whether Discourse would allow it.
03Migrate by impactThe order comes from the audit. Buttons first at 8.7% of the footprint, then typography, then spacing, then the local overrides.

One canonical contract. Two framework-native implementations. Every delivery format generated, never hand-maintained in parallel.

React consumes the contract and produces React components. Discourse consumes it and produces Ember and Glimmer components. Both implement the same visual and behavioral contract without sharing a line of source. CSS custom properties, Sass aliases and the Figma JSON are all generated from it, which is what prevents the split brain that created the fragmentation in the first place.

Button / Primary

  background        color.action.primary
  foreground        color.text.on-action
  height            size.control.medium
  padding-inline    space.md
  radius            radius.control
  focus-ring        color.focus

  states            default · hover · pressed
                    focus · disabled · loading
01One canonical contractA typed, framework-neutral source of truth for every governed decision. Not Sass, not React, not Figma.
02Native implementationsReact and Ember each build the same contract in their own idiom. Visual parity without a shared codebase.
03Generated adaptersCSS custom properties, Sass aliases, Figma JSON — all generated from the contract, never maintained beside it.

The unified library is drafted: 909 tokens across 14 groups, in one export meant to become the single source Figma and code both read.

It carries a primitive layer and a Discourse-facing bridge layer in the same file, which is what will let the migration run component by component instead of as a single flip. The bridge is temporary by design — every token in it is one a component still has to be moved off. It is not wired to production yet; counting it as delivered would be counting the plan as the work.

909Tokens in the exportOne file, feeding Figma and code.
14Top-level groupsPrimitives, semantics, components, and a Discourse bridge.
613Color tokensBeside 149 dimension and 105 string values.
20Type stylesFamily, weight, size, line-height and tracking, resolved.
PhaseWhy it sits hereStatus
1 · ButtonsHighest token footprint at 8.7%. Not the most visible component — see Two rankings.Roadmapped
2 · TypographyFoundational to everything after it. The DM Sans asset is already source-verified.Planned
3 · Spacing & layoutGrid, component gaps, surface padding — audit the ladder, then fill its missing steps.Planned
4 · Color harmonization
Vital · Leakage
Resolve 26 exact matches, classify 163 uncovered values, retire the duplicate names.In discussion
5 · Mode & accessibility
Vital · Mode completeness
Compile every light and dark bundle pair, render in real contexts, verify WCAG 2.2 AA.Next gate

Buttons are the critical path: typography and spacing both depend on the contract shape that migrating buttons settles. The order is not a preference — it is the token footprint, ranked.

Locating a decision

Before

Search 21 files, guess which components consume the change, ship it and find out.

After

A ranked list of 105 components, with file and line evidence for every reference.

Shipping a visual change

Before

The change reaches some components and not others. Discovered in QA, or after launch.

After

Components read one semantic layer, so a color change propagates for one reason rather than four.

Design against production

Before

Figma and production drift apart until something ships wrong.

After

The audit is an evidence boundary. Documented differences are tracked, not discovered.

None of this is specific to Discourse, or to a rebrand. It needs only a codebase where one decision answers to more than one name.

Any change that has to land everywhere at once asks the same question first — a rebrand, an acquisition, a design system’s second version. The scanner reads Sass and CSS, so it fits most front ends built on them, and it is open source. What it cannot do is read a value that does not exist until the page runs: a custom property a theme overrides after load is invisible to anything that only reads files. That limit is the same on every codebase, and knowing where it falls is part of using the instrument rather than a flaw in it.

The part that transfers without any tool at all is the order of operations. Measure the code rather than the design file, because the design file holds the intent and the browser holds what shipped. Rank the work by something you measured rather than by whoever asks loudest. And write down what you could not prove, so the next person knows where to look instead of inheriting a scorecard that graded everything.

Strategy

  • Framed the rebrand as an architecture problem, not a visual one
  • Defined the framework-neutral token contract
  • Set migration order from measured token footprint

Execution

  • Built the audit tool that scans the production codebase
  • Built the 0→1 React and Vite environment: 224 tokens, 45 components, CI-integrated
  • Drafted the unified 909-token library, the intended single source for Figma and code
  • Specified the button contract across every state and mode

Leadership

  • Ran rebrand phasing with product managers against audit evidence
  • Gave engineering shared vocabulary for tokens, tiers and leakage
  • Wrote down what the audit cannot prove, so nobody over-claims it

Proven

  • 479 token concepts mapped across 21 definition sources
  • 105 components ranked by confirmed token footprint
  • 26 exact-match and 163 uncovered color candidates located with file and line
  • DM Sans and 17 brand colors verified from source, not from a spec
  • Migration order grounded in footprint rather than in preference

Still open

  • Compiled light and dark bundle pairs are not audited yet
  • No token-aware CI gate has been proven
  • Semantic equivalence for the 163 literal matches needs human review
  • 70 concepts remain unclassified
  • Delivery-speed impact is unmeasured; the baseline is being captured during rollout

The audit fixes nothing. It shows the shape of what needs fixing and puts it in order — which is the part a rebrand cannot be planned without. A rebrand is normally a visual change. Making this one architectural is what makes the next one cheap.