Raptive · Design Systems · 2026

Design Systems Token Vitals

Auditing what Discourse, the open source platform the community runs on, actually does in code, so a visual overhaul lands everywhere at once

Skill case study · the work is ongoing

design-token-vitalsGitHubGrades a front end’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
Platform
Raptive Community, a Discourse app built on Rails and Ember
Team
Sole designer, partnered with Product Management and Platform Engineering

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 graded rows are below. So is why that distinction matters.

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

Every surface was captured at both screen sizes and reviewed as one screen. The desktop and mobile versions of the same thing count once. A component that repeats inside a screen also counts once. So the number beside a component is the share of the product 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.

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 screen sizes. Feed, Latest, For you, Categories, Community hot, Community latest, Topic, Tags, Search and Groups. Each screen is marked up with the components it uses, and has those components pulled out beneath it. 32 surfaces, 64 screen and device columns in all.

The product 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 a platform whose code was started in 2013, before design tokens were a practice and before CSS variables existed in browsers. Decisions about type, color, spacing, corners and shadows had piled up across the platform itself, Raptive’s own plugins, and two separate systems of style variable. Every one of them made sense when it was made. Together they had drifted apart. Change one foundational value and some components followed. Others reached past it to the raw color underneath. A third group read a mobile-only copy of the same value kept in a different file.

Then a rebrand was scheduled. New type, new colors, new buttons. This time the change had to land cleanly across the whole product. Finding out mid-launch that only 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 in Figma. Figma holds what we meant to build; the browser shows what actually shipped.

01What do we actually have479 tokens are defined somewhere in the code. Which are real decisions, which are copies of each other, and which are used by nothing at all?
02Why doesn't consistency holdChange a value and some components pick it up. Why the rest ignore it is written down nowhere.
03How do we migrate safelyIf the whole product has to change, which components move first, and what makes that order the right one?

So I built the scanner. It reads the real code rather than a token file someone assumed was accurate, and grades what it finds.

It follows how the Sass and CSS files import one another. Then it separates the code Raptive owns from the platform’s own. It untangles every way a value can be written (plain values, groups, one token pointing at another, CSS variables) into one entry per real decision, and traces which files use each one. Those files are grouped into components; hard-coded colors are counted separately. Out comes eight graded vitals and a fix list ordered by impact. It works on any front end built this way and it is open source: Design Token Vitals.

479Separate token decisionsOne entry per decision, after every duplicate definition was merged.
21Places tokens are definedSome written by hand, some generated, and some disagreeing with each other.
4Naming styles in useCore color, Raptive color, typography, layout.
110Files the scan could followPlus 89 more it could not confirm, held as maybes or local overrides.
105Components that use tokensFound across 354 component and style files.
36.7%Of all uses, from five tokensThe impact is concentrated, which is what makes an order possible.
26Hard-coded colors with a matchThe exact value already exists as a token.
163Hard-coded colors with no matchNo token exists for these values yet.
Vital · Single source

Same value. Four names. Nothing in the code connects them.

White appears 76 times across 20 files and answers to at least four different token names. The brand color has the same problem. Teams had each named the same decision separately, not knowing someone else already had. Renaming one leaves every other spelling of that decision untouched. Nothing in the code tells you which ones those are.

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

The values are provably identical; whether they mean the same thing is not. The scanner will not merge them on its own. Deciding that a Google button and a mobile background are the same decision is a judgement call for a person, and the report says so rather than quietly combining them.

Vital · Tier integrity

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

Some buttons read a named layer. Some reach straight past it to the raw color underneath. Some read a mobile-only override. Nobody chose that. There was no agreed contract and nothing standing between a deadline and a quick local fix.

74Token referencesIn one component.
26Distinct tokensFor one visual decision.
2+Source filesA named layer, the raw values under it, and a mobile override.
8.7%Of all token use in the codeCounted by uses in code, not by how many screens it appears on.

369 rules across the product paint something button-shaped. Twelve of them say enough to actually draw the button.

So I drew every one of them from its own source. Each record carries the class name, the button as that rule paints it, the size and shape it works out to, the tokens it reaches for, and the file and line it came from. Then it sorts them by how much the code is willing to tell us.

A third instrument counting a third thing: CSS rules that paint a button anywhere in the product. That is neither how often the button component uses a token nor how many screens it appears on. 74, 22% and 369 answer three different questions.

12Rules that fully describe oneEnough detail to draw the button truthfully.
294Rules that describe part of oneThe fill, the shape or the text comes from somewhere else, or never resolves.
68Rules that describe nothingA real class name that sets no visual properties at all.
369Rules that paint a buttonAcross 187 source files, narrowed from 552 candidates.

Most certain first.

The twelve records we can trust most, 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, corner or type, and each points at 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 says enough to draw the button truthfully.

The gray and outlined placeholders are a rule rather than a style: they mean the evidence is incomplete, never that the button is gray. The same refusal the eight vitals make.

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

Fifteen of 294. The proportion is the point rather than the parade. Twelve rules can be edited with confidence. The other 357 have to be worked out before anyone can say what editing them would do. That is why “change the button color” was never a one-line job.

The button ranks first by how much code uses it. It ranks fourth by how much of the product you see it on, behind a navigation bar that appears on 88% of screens.

Both numbers are right and they answer different questions. Counting uses in code tells you where the work is. Counting screens tells you where a mistake would be seen. The button is 8.7% of all token use and appears on 22% of screens. The top navigation appears on 88%. I have not measured how much code it uses, so I cannot tell you whether it is the cheaper fix, only that it is the more visible one.

The two also cover different ground and mixing them up is expensive. The code audit found 479 tokens across everything the product loads, the platform included; the design-side inventory counts only the 118 Raptive owns outright. Neither is the “real” number. Quoting either without saying which count produced it is how a slide deck ends up wrong.

118Explicit Raptive tokensThe ones Raptive owns outright.
78ColorNamed, brand, mobile, interaction and feature groups.
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 complicated they are but by what each group needs from the token layer.

01Shared shellHeader, sidebar, community items, icon buttons. These take their spacing, type and color from the platform's own defaults.
02Social contentTopic and discussion cards, question cards, reaction controls and summaries, avatars. These add tokens of their own for avatars, corners, outlines and reactions.
03Operational controlsSelect and filter controls, admin navigation, report tiles, profile stats and badges. These run on values the platform fills in as the page loads, so each one has to be pointed at a real token before any Figma library work can start.

Operational controls is the group that matters most and looks least urgent. Those components run almost entirely on values the platform fills in as the page loads. They cannot appear in a Figma library at all until each one is pointed at the same named color, spacing, corner and type groups as everything else. Building the library before that happens would produce a design system that quietly leaves out the admin, review and settings screens.

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

  • Naming coherenceFour naming styles are in use where there should be one.fail
  • Tier integritySome components skip the named token and use the raw value underneath.attention
  • Coverage14 of 19 token groups were checked. Five were never measured.attention
  • Single sourceThe same value answers to several different names.attention
  • Leakage26 hard-coded colors match a token exactly; 163 more have no token at all.blocked
  • Mode completenessThe built light and dark themes have never been checked.blocked
  • OrphansWhich tokens nothing uses is still unknown.blocked
  • EnforcementNothing in the build stops a hard-coded value getting in.blocked

Blocked means the tool refused to grade what it never measured. Four of the eight say “no evidence yet” rather than inventing a number. An audit that grades everything is guessing with a scoring sheet.

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

01Design in isolationA separate build, away from what the platform allows. 230 token values, 63 components, checked automatically on every change, built in four weeks.
02Approve on evidenceSomewhere anyone could look. Product, engineering and design could judge the new design on its merits, without the conversation turning into what the platform would permit.
03Migrate by impactThe order comes from the audit, not from taste. Buttons first at 8.7% of all token use, then type, then spacing, then the one-off overrides.

One agreed set of decisions. Two builds of it, each native to its own framework. Every output format generated, never kept up to date by hand in two places.

React reads the contract and produces React components. The platform reads the same one and produces its own. Both end up looking and behaving identically without sharing a line of code. The CSS variables, the Sass and the Figma JSON are all generated from it. That is what stops the two halves drifting apart. That drift is what caused the mess in the first place.

Button / Primary

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

  states            default · hover · pressed
                    focus · disabled · loading
01One canonical contractOne place that holds every agreed decision, belonging to no framework. Not Sass, not React, not Figma.
02Native implementationsReact and Ember each build the same thing their own way. They look identical without sharing a line of code.
03Generated adaptersCSS variables, Sass, Figma JSON. Every format is generated from that one place, never kept up to date by hand alongside it.

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

It holds the raw values and a temporary bridge back to the platform in the same file. That is what will let the migration go component by component instead of all at once. The bridge is meant to be temporary. Every token in it marks a component still to be moved. It is not wired into production yet. Counting it as delivered would be counting the plan as the work.

909Tokens in the exportOne file, meant to feed both Figma and code.
14Top-level groupsRaw values, named values, components, and a bridge back to the platform.
613Color tokensAlongside 149 sizes and 105 text values.
20Type stylesFont, weight, size, line height and letter spacing, all settled.
PhaseWhy it sits hereStatus
1 · ButtonsThe most used, at 8.7% of all token use. Not the most seen. See Two rankings.Roadmapped
2 · TypographyEverything after it sits on top of type. The DM Sans files are already confirmed from source.Planned
3 · Spacing & layoutGrid, gaps between components, padding inside them. Check the scale, then fill in its missing steps.Planned
4 · Color harmonization
Vital · Leakage
Point the 26 exact matches at their token, sort the 163 with no match, and retire the duplicate names.In discussion
5 · Mode & accessibility
Vital · Mode completeness
Build every light and dark pair, look at them in real screens, and check they pass WCAG 2.2 AA.Next gate

Buttons are the critical path: type and spacing both depend on the shape the button contract settles. The order follows measured use.

Locating a decision

Before

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

After

A ranked list of 105 components, with the file and line for every single use.

Shipping a visual change

Before

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

After

Components read one named layer, so a color change spreads for one reason instead of four.

Design against production

Before

Figma and production drift apart until something ships wrong.

After

The audit draws the line between what is proven and what is not. Differences get written down, not discovered late.

None of this is specific to one platform, or to a rebrand. It needs only a product 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 or a design system’s second version all start there. The scanner reads Sass and CSS, so it fits most front ends built on them. It is open source. What it cannot do is read a value that does not exist until the page runs: a variable a theme changes after load is invisible to anything that only reads files. That limit is the same everywhere. Knowing where it falls is part of using the tool rather than a flaw in it.

The part that transfers without any tool at all is the order you do things in. Measure the code rather than the design file, because the design file holds what you meant 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.

Proven

  • 26 hard-coded colors that match a token and 163 that do not, each found at its file and line
  • DM Sans and 17 brand colors confirmed from the real files, not from a spec
  • A migration order based on measured use rather than on preference

Still open

  • The built light and dark themes have not been checked yet
  • Nothing in the build stops a hard-coded value getting in
  • Whether those 163 values actually mean the same thing still needs a person to decide
  • 70 tokens are still unsorted
  • Whether this makes teams faster is unmeasured; the starting point is being recorded 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 structural is what makes the next one cheap.