Most component libraries end up duplicating the token system they were supposed to implement. The color values are defined in tokens, and also in the component styles. The spacing scale exists in a token file, and also in the component's hardcoded padding values. The two sources drift apart over time because there is no enforcement mechanism that keeps them aligned.
The alternative approach: build the component library as a transformation of the token file, not alongside it. When the token file is the authoritative input and the component output is derived from it, drift becomes structurally impossible. A component cannot have a hardcoded color value because the generation pipeline does not produce hardcoded color values.
This article describes the workflow we use to make that practical with Tokens Studio and a React target. It includes the places where the approach breaks down, because there are real limitations.
The token file structure that enables generation
A token file that can drive component generation needs more than color values and spacing scales. It needs component-level tokens that express the relationship between tokens and specific component properties. The structure looks like this:
Primitive tier: raw values. color.blue.500 = #3B82F6. Semantic tier: meaningful aliases. color.interactive.primary = {color.blue.500}. Component tier: component-specific applications. component.button.bg.default = {color.interactive.primary}.
The component tier is what most token systems skip. Without component-level tokens, the generator cannot know that a Button's background color should use the interactive primary semantic rather than any other token that resolves to the same value. The component tier makes that assignment explicit and machine-readable.
Setting up Tokens Studio for generation
Tokens Studio organizes tokens into sets. For generation, the structure needs to be: a primitives set (never exported to production CSS), a semantic set (the shared vocabulary), and a components set (one group per component, each property mapped to a semantic token).
The components set is where the structural work happens. For a Button component, the definition includes: background color (default, hover, active, disabled states), text color, border color, border radius, padding horizontal, padding vertical, font size, font weight, transition duration. Each of these maps to a semantic token, not a primitive. The semantic-to-primitive resolution happens in the transformation step, not in the component definition.
When this is set up correctly, a Tokens Studio export produces a JSON file where each component's properties are lists of semantic token references. The generator reads those references, resolves them through the semantic tier to get computed values, and produces component code that uses CSS custom properties for all tokenized values.
The Style Dictionary configuration for React output
Style Dictionary handles the transformation from token JSON to CSS and optionally to JavaScript token objects. For a React component library, you typically want both: CSS custom properties for runtime styling, and TypeScript token types for type-safe component prop definitions.
The configuration has three key pieces. First, the platform config: define two output platforms, one for CSS custom properties and one for JS/TypeScript token constants. Second, the token filter: primitives should not appear in either output. Apply a filter that removes any token with private: true or in the primitives category. Third, the transforms: the name transform converts slash-path token names to CSS kebab-case; the value transform handles unit conversion for spacing and size tokens.
A common mistake is applying global transforms to all token types. If your spacing tokens need a px-to-rem conversion and your color tokens need no conversion, the transform needs to check token type before applying. Style Dictionary's matcher option on transforms handles this.
The generation step for React components
The generation step takes the processed token definitions and produces React component files. The core question is how opinionated to be about the component implementation. There are two useful positions:
Position 1: generate the full component, including JSX structure, TypeScript interface, and CSS module file. This produces the most complete output but requires the template to match the component's expected structure exactly. Any deviation from the template needs to be handled in template logic, which becomes complex for components with non-trivial structure.
Position 2: generate only the token bindings and variant logic, and write the JSX structure by hand. The generator produces a getTokenValues(variant) utility that returns the correct token references for a given variant, and the component applies those values. The JSX structure stays hand-authored and only the styling logic is generated.
DesignVerse uses a constrained version of Position 2. We generate the token binding logic and the variant type definition from the component token set. The JSX structure is handled by a template that the team can customize per project. This splits the generated output cleanly from the manually authored parts and avoids the fragility of full-component generation.
Where this breaks down: interactive states
Component-level tokens handle default state styling cleanly. They handle hover, focus, and active states adequately if you define a token for each state. Where they break down is for compound interactions: the appearance of a focused button that is also in a loading state, or the color of a hovered element inside a disabled container.
CSS handles these cases with compound selectors and pseudo-class combinations. Tokens cannot express compound state relationships because tokens are values, not selectors. At some point, a component implementation needs CSS logic that goes beyond what a token definition can specify.
The practical resolution: token the default states and the primary single-state variants. Write the compound-state CSS by hand, inside the component's CSS module. Mark these hand-written rules with a comment indicating they are not generated. This creates a clean distinction between token-driven and hand-authored styling in every component file, and it makes the non-tokenized parts visible.
Keeping the library in sync as tokens change
The generation step needs to run whenever the token file changes. In a CI workflow, this means watching for changes to the token JSON in the design system repository and triggering a generation run. The generated component output is then committed as a PR, reviewed for correctness, and merged.
The review step matters. Generated output can have unexpected results when a token reference chain is longer than expected or when a new token introduces a conflict. Human review of generated component diffs catches these before they reach production.
The alternative, automatic commit of generated output without review, works for token value changes (a hex color update should not change component behavior). It is riskier for structural changes: new component tokens, renamed tokens, added variants. We gate structural changes behind a manual review step and allow automatic commits only for pure value changes.
Limitations to state clearly
A token-driven component library is not the right choice for every team. If your component library is small (under 20 components), hand-authoring everything and maintaining token discipline manually is probably less work than setting up the generation pipeline. The pipeline pays off at scale: when you have 60+ components, four themes, and a monorepo with three applications consuming the library.
The pipeline also requires investment upfront. Getting the Tokens Studio structure right, configuring Style Dictionary correctly, and building the generation templates takes meaningful engineering time. Teams that skip that investment and build the pipeline incrementally often end up with a partially generated library where some components follow the token-driven pattern and others do not, which produces more complexity than either approach alone would have created. Plan the migration fully or do not start it.