Skip to main content
Below you’ll find answers to the questions that come up most often when teams are evaluating or adopting Substrate. If your question isn’t covered here, open a discussion in the Substrate GitHub repository.
Style Dictionary and Tokens Studio are excellent tools for managing and transforming static token values. Substrate sits one level earlier: instead of storing values, it stores intent and computes values. You feed Substrate a color hue and chroma — its runtime solver figures out the correct lightness for every surface, at every position on the continuous scheme and contrast axes. The DTCG output can feed into Style Dictionary or other tooling if needed. Think of Substrate as the layer that eliminates the spreadsheet of hex values your team has been maintaining by hand.
Almost none. Color is pure intent: hue and chroma per intent, with lightness always solver-derived. A handful of brand decisions have no mathematical basis Substrate could infer, so they are authored literals: the font stacks (typography.heading-family / body-family / mono-family), the easing curve (motion.easing), the base duration, corner radius (shape.radius-base), the spatial unit (space.unit), and the elevation steps. Gradient stops are the one place lightness itself is authored.
Yes. You can run Substrate alongside an existing system — for example, using it for color and adaptive behavior while keeping existing component tokens. The generated CSS custom properties integrate with any CSS-in-JS library, Tailwind, or plain CSS. Collisions are unlikely because Substrate’s names are distinctly prefixed (--ucs-*, --surface-*) and its cascade is opt-in: only elements carrying data-ucs participate, and brand scoping rides on data-brand.
Use the DTCG output: each brand emits W3C Design Tokens JSON per mode at generated/brands/<brand>/<sub-brand>/dtcg/tokens.{light,dark,highContrast,darkHighContrast}.gen.dtcg.json. Import those with the Tokens Studio plugin or Figma’s native variable import, mapping each mode file to a Figma variable mode. When you regenerate after a brand config change, re-import to keep Figma in sync. The files are committed generation output, so imports are reproducible from version control.
CVD compensation is a runtime preference, not a build-time config: the preference vector carries cvd: { type, severity } where type is protan, deutan, tritan, or achromat and severity is continuous from 0 to 1. The kernel redistributes intent hues onto a CVD-safe arc (with a minimum separation between adjacent intents) before the APCA solve, and achromat adds chroma reduction plus pattern overlays so meaning never rides on color alone. Nothing is pre-generated per profile, and it composes freely with every other axis — dark + protan is just { scheme: 1, cvd: { type: 'protan', severity: 1 } }.Honest limits, straight from the kernel: hue redistribution helps dichromats most and is weak for anomalous trichromats (the majority of CVD users); treat it as an aid, not a guarantee, and never as a substitute for non-color cues (WCAG 2.2 §1.4.1). See Color Vision.
Change intents.brand.hue in your brand’s YAML config and regenerate (npm run generate in the engine checkout). All downstream values — across every mode, every intent, and every platform output — are recomputed, and the runtime solver re-derives lightness for the new hue, so contrast guarantees are maintained without any additional work on your part.
There is no per-component density config field, but density is a runtime variable, so scoped overrides are first-class: spacing composes as calc(var(--density) * var(--scale) * var(--space-unit) * n), and --density is the lever the engine itself varies — override it on a subtree (<div style="--density: 0.85">) and everything inside tightens. Don’t override --space-unit; that is the brand constant.
Modes are presets your brand authors under presets.mode — an open map of named levels, each a position on the continuous scheme/contrast axes. Author fewer levels and the pipeline bakes fewer per-mode artifacts (CSS, Swift, Kotlin, DTCG files exist per authored level). The continuous runtime is unaffected: users within your flexibility bounds can still occupy any position on the axes. Omitting high-contrast levels is not recommended for products serving users with visual accessibility needs.
Start by identifying your existing semantic roles — primary, surface, error, and so on — and mapping them to Substrate intents (brand, neutral, danger, plus custom intents for anything else). Replace your fixed hex values with OKLCH hue and chroma values (a color picker that shows OKLCH makes this straightforward). Regenerate and compare the output against your existing values — expect lightness differences, since APCA replaces whatever contrast approach you were using. Migrate component by component, starting with the lowest-risk surfaces: background layers and borders first, then text colors, then interactive states.
Yes — and more than that. Per brand, the pipeline emits CSS, Swift (per-mode SubstrateSystemTokens files in Display P3, plus kernel-interpreted component descriptors), Kotlin/Compose (mirror structure plus a generated Material ColorScheme), DTCG JSON, React Native, xcassets, and JSON conformance vectors. All derive from the same brand data, so platforms stay in sync automatically. See iOS and Android.
Substrate performs all color math in OKLCH — a perceptually uniform color space that separates lightness (L), chroma (C), and hue (H) into independent axes. Perceptual uniformity means that equal numerical changes in lightness produce equal perceived changes across the full lightness range. This property is what makes APCA solving tractable: Substrate can do a binary search on the L axis to hit an Lc target without hue or chroma drift. In the output, component CSS composes oklch() colors from the solver primitives, while the baked per-mode token files emit plain hex for maximum compatibility.