Design Tokens for Color, Type, Spacing, and Motion

Capítulo 3

Estimated reading time: 29 minutes

+ Exercise
Audio Icon

Listen in audio

0:00 / 0:00

What design tokens are (and what they are not)

Design tokens are named, reusable variables that store the smallest pieces of a brand system in a technology-agnostic way. They capture decisions like “primary background color,” “body text size,” “default spacing between elements,” or “standard easing for UI transitions,” and make those decisions portable across design tools and codebases.

A useful mental model is: tokens are the source of truth for stylistic values, while components are the places those values get applied. A button component might reference color.background.brand and space.3; the token defines the value, the component defines the structure and behavior.

An isometric UI design system scene showing a button component card connected by lines to labeled design tokens like color.background.brand and space.3, clean modern style, neutral background, crisp typography, high contrast, no brand logos

Tokens are not the same as “a style guide page,” and they are not a replacement for components. They also are not just “CSS variables.” CSS variables can be one output format, but tokens should be defined in a way that can be transformed into multiple formats (CSS, iOS, Android, design tool styles, documentation tables). Tokens also should not be a dumping ground for every one-off value; the goal is consistency and scalability.

Token anatomy: name, value, type, and metadata

At minimum, each token has a name and a value. In practice, scalable systems also include a token type and metadata that helps automation and governance.

  • Name: A stable identifier like color.text.primary or type.size.body. Names should describe intent, not raw appearance.

    Continue in our app.
    • Listen to the audio with the screen off.
    • Earn a certificate upon completion.
    • Over 5000 courses for you to explore!
    Or continue reading below...
    Download App

    Download the app

  • Value: The actual value, such as #0B5FFF, 16, 24, cubic-bezier(0.2, 0, 0, 1).

  • Type: Color, dimension, fontFamily, fontWeight, duration, easing, shadow, etc. Types enable validation and correct platform output.

  • Metadata: Description, usage notes, accessibility constraints, deprecation status, and links to examples. Metadata is what turns a token file into a maintainable system rather than a list of numbers.

Many teams also distinguish between base tokens (raw palette steps, spacing scale values) and semantic tokens (meaningful roles like “text on brand background”). Semantic tokens are what components should reference most of the time, because they allow theme changes without rewriting component styles.

How to structure token sets: base, semantic, and component-level

A practical structure that scales across products and themes is a three-layer model:

  • Base (primitive) tokens: Foundational values with minimal meaning, such as color.blue.600 or space.4. These are stable building blocks.

  • Semantic tokens: Map base values into roles, such as color.text.primary or color.background.surface. These encode intent and are themeable.

  • Component tokens (optional): Component-specific aliases like button.primary.background. Use these when a component needs a controlled contract that can vary by component variant without leaking complexity into global semantics.

In many systems, component tokens are derived from semantic tokens (e.g., button.primary.background references color.background.brand). This keeps the system coherent while still allowing component-level flexibility.

Design tokens for color

Color token categories

Color tokens typically fall into these groups:

  • Palette (base): A set of hue ramps or neutral ramps, e.g., color.gray.0 through color.gray.1000, color.blue.50 through color.blue.900.

  • Semantic roles: Text, background, border, icon, focus ring, overlay, and state colors (success, warning, danger, info).

  • Interaction states: Hover, pressed, selected, disabled, and subtle variants (e.g., “danger background subtle”).

  • Data visualization: Distinct series colors and their accessible pairings, often separate from UI colors.

A key practice is to keep palette tokens stable and map semantics on top. That way, a dark theme can remap color.background.surface from color.gray.0 to color.gray.950 without changing component code.

A split-screen UI theme illustration showing light mode and dark mode panels with token labels mapping color.background.surface from color.gray.0 to color.gray.950, clean design system diagram, minimal, high readability, no logos

Step-by-step: define a color token system

Step 1: Create neutral and brand ramps as base tokens. Start with neutrals (for surfaces and text) and one or more brand ramps (for accents). Use consistent steps (e.g., 50–900 or 0–1000). Example:

{"color":{ "gray":{ "0":"#FFFFFF","50":"#F7F7F8","100":"#EDEEF0","200":"#D9DCE1","300":"#C3C8D0","400":"#A7AFBA","500":"#8791A1","600":"#667085","700":"#475467","800":"#344054","900":"#1D2939","950":"#0B1220" }, "blue":{ "50":"#EFF8FF","100":"#D1E9FF","200":"#B2DDFF","300":"#84CAFF","400":"#53B1FD","500":"#2E90FA","600":"#1570EF","700":"#175CD3","800":"#1849A9","900":"#194185" } } }

Step 2: Define semantic tokens for text, background, border, and icon. These should describe purpose. Example:

{"color":{ "text":{ "primary":"{color.gray.900}", "secondary":"{color.gray.700}", "disabled":"{color.gray.500}", "onBrand":"{color.gray.0}" }, "background":{ "canvas":"{color.gray.0}", "surface":"{color.gray.0}", "subtle":"{color.gray.50}", "brand":"{color.blue.600}", "overlay":"rgba(11,18,32,0.6)" }, "border":{ "default":"{color.gray.200}", "subtle":"{color.gray.100}", "focus":"{color.blue.600}" } } }

Step 3: Add state tokens that can be reused across components. Avoid defining hover colors inside each component if the behavior is consistent. Example:

{"color":{ "state":{ "brandHover":"{color.blue.700}", "brandPressed":"{color.blue.800}", "danger":"#D92D20", "dangerHover":"#B42318", "success":"#12B76A" } } }

Step 4: Validate accessibility at the semantic level. Instead of checking every component instance, verify that semantic pairings meet contrast requirements (e.g., color.text.primary on color.background.canvas, color.text.onBrand on color.background.brand). If a pairing fails, adjust the mapping (semantic to base) rather than introducing exceptions.

Step 5: Prepare theme overrides. For dark mode, keep the palette but remap semantics. Example:

{"themes":{ "dark":{ "color":{ "background":{ "canvas":"{color.gray.950}", "surface":"{color.gray.900}", "subtle":"{color.gray.800}" }, "text":{ "primary":"{color.gray.0}", "secondary":"{color.gray.200}" }, "border":{ "default":"{color.gray.700}" } } } } }

Naming guidance for color tokens

  • Use semantic names for roles (text.primary, background.surface) rather than text.black or surface.white.

  • Reserve hue names for base palette steps (blue.600), not for UI roles.

  • Include state in the name when it is a reusable concept (state.brandHover), but avoid encoding component names unless necessary.

Design tokens for typography

What to tokenize in type

Typography tokens often include more than font size. A robust set usually covers:

  • Font families: brand sans, brand serif, mono.

  • Font weights: regular, medium, semibold, bold (mapped to numeric values per platform).

  • Font sizes: a scale for body and headings.

  • Line heights: often proportional or fixed per size.

  • Letter spacing: especially for caps or large display sizes.

  • Paragraph spacing: sometimes treated as spacing tokens, but can be part of text styles.

Many teams create text style tokens (sometimes called “typography composites”) that bundle size, line height, weight, and letter spacing into a single token like type.textStyle.body. This reduces errors and ensures consistent combinations.

Step-by-step: build typography tokens that scale

Step 1: Define font family and weight tokens. Example:

{"type":{ "fontFamily":{ "sans":"Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif", "serif":"Georgia, Times, serif", "mono":"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace" }, "fontWeight":{ "regular":400, "medium":500, "semibold":600, "bold":700 } } }

Step 2: Define a size and line-height scale. Keep the scale small enough to be learnable. Example:

{"type":{ "size":{ "xs":12, "sm":14, "md":16, "lg":18, "xl":20, "2xl":24, "3xl":30, "4xl":36 }, "lineHeight":{ "xs":16, "sm":20, "md":24, "lg":28, "xl":28, "2xl":32, "3xl":38, "4xl":44 } } }

Step 3: Add letter spacing where it matters. Example:

{"type":{ "letterSpacing":{ "default":0, "tight":-0.2, "tighter":-0.4, "caps":1.2 } } }

Step 4: Create composite text styles for common roles. This is where typography becomes easy to apply consistently. Example:

{"type":{ "textStyle":{ "body":{ "fontFamily":"{type.fontFamily.sans}", "fontWeight":"{type.fontWeight.regular}", "fontSize":"{type.size.md}", "lineHeight":"{type.lineHeight.md}", "letterSpacing":"{type.letterSpacing.default}" }, "bodyStrong":{ "fontFamily":"{type.fontFamily.sans}", "fontWeight":"{type.fontWeight.semibold}", "fontSize":"{type.size.md}", "lineHeight":"{type.lineHeight.md}", "letterSpacing":"{type.letterSpacing.default}" }, "caption":{ "fontFamily":"{type.fontFamily.sans}", "fontWeight":"{type.fontWeight.medium}", "fontSize":"{type.size.sm}", "lineHeight":"{type.lineHeight.sm}", "letterSpacing":"{type.letterSpacing.default}" }, "heading1":{ "fontFamily":"{type.fontFamily.sans}", "fontWeight":"{type.fontWeight.semibold}", "fontSize":"{type.size.4xl}", "lineHeight":"{type.lineHeight.4xl}", "letterSpacing":"{type.letterSpacing.tight}" } } } }

Step 5: Define semantic typography roles. Similar to color semantics, map usage to a token so components can reference intent: type.role.body, type.role.heading, type.role.code. Example:

{"type":{ "role":{ "body":"{type.textStyle.body}", "bodyStrong":"{type.textStyle.bodyStrong}", "caption":"{type.textStyle.caption}", "pageTitle":"{type.textStyle.heading1}" } } }

This extra layer helps when you need to adjust the system (e.g., increase body size for accessibility) without hunting through component styles.

Practical example: applying typography tokens in CSS

One output approach is to transform tokens into CSS custom properties and then define utility classes or component styles that reference them.

:root{ --type-font-sans: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; --type-weight-regular: 400; --type-size-md: 16px; --type-line-md: 24px; --type-letter-default: 0px; } .text-body{ font-family: var(--type-font-sans); font-weight: var(--type-weight-regular); font-size: var(--type-size-md); line-height: var(--type-line-md); letter-spacing: var(--type-letter-default); }

The key is that the CSS is generated from tokens, not manually curated. That keeps design and code aligned.

Design tokens for spacing

Why spacing tokens matter

Spacing is one of the fastest ways a system becomes inconsistent: margins drift, paddings become arbitrary, and layouts feel “almost” aligned. Spacing tokens enforce a limited set of increments so that layouts snap to a predictable rhythm.

A clean UI layout grid illustration showing cards and text aligned to a 4px spacing rhythm, with highlighted spacing increments and token labels like space.1, space.2, space.4, minimal design system aesthetic, no logos

Spacing tokens typically cover:

  • Space scale: padding/margin/gap increments.

  • Layout constraints: container widths, grid gutters, breakpoints (sometimes separate from spacing but often managed similarly).

  • Radii and borders: not spacing per se, but also “dimension” tokens that benefit from scale discipline.

Step-by-step: create a spacing scale and usage rules

Step 1: Choose a base unit and scale pattern. Common choices are 4px or 8px bases. A 4px base often provides enough granularity for dense UI. Example scale:

{"space":{ "0":0, "1":4, "2":8, "3":12, "4":16, "5":20, "6":24, "8":32, "10":40, "12":48, "16":64 } }

Step 2: Define semantic spacing aliases for common layout intents. This reduces the cognitive load of remembering numbers and supports future adjustments. Example:

{"space":{ "inset":{ "xs":"{space.2}", "sm":"{space.3}", "md":"{space.4}", "lg":"{space.6}" }, "stack":{ "xs":"{space.2}", "sm":"{space.3}", "md":"{space.4}", "lg":"{space.6}", "xl":"{space.8}" } } }

Step 3: Establish component spacing contracts. For example, a card might always use space.inset.md for padding, while a form field group might use space.stack.md between fields. If you need component-level tokens, define them as aliases:

{"component":{ "card":{ "padding":"{space.inset.md}", "gap":"{space.stack.sm}" }, "form":{ "fieldGap":"{space.stack.md}" } } }

Step 4: Add responsive spacing where necessary. Instead of inventing new values, remap semantic spacing at breakpoints. For example, space.inset.lg might be 24 on mobile and 32 on desktop. This can be handled by theme-like modes (e.g., density.compact, density.comfortable) or breakpoint transforms in your build pipeline.

Practical example: spacing tokens in a layout

Imagine a settings page with a header, a card list, and a sidebar. A token-driven approach might specify:

  • Page padding: space.inset.lg

  • Gap between cards: space.stack.md

  • Card padding: component.card.padding

This makes it easy to adjust the entire product’s “airiness” by changing a small number of semantic mappings rather than editing dozens of screens.

Design tokens for motion

What to tokenize in motion

Motion tokens standardize how the interface animates: durations, easing curves, and sometimes distances. Without tokens, motion becomes inconsistent: some elements snap, others glide, and timing feels random. With tokens, motion becomes part of the brand’s interaction voice.

Motion tokens commonly include:

  • Duration: fast, medium, slow, plus specific durations for micro-interactions vs. page transitions.

  • Easing: standard curves for enter/exit, emphasized, linear, etc.

  • Delay: for staggered animations (use sparingly).

  • Distance: small translate values for slide/fade patterns (optional but useful).

  • Opacity levels: sometimes treated as color/alpha tokens, but can be referenced in motion patterns.

It is often helpful to define motion patterns (composites) such as “fade in,” “slide up,” or “scale in,” but keep them separate from raw tokens. Raw tokens should remain broadly reusable.

Step-by-step: define motion tokens and apply them consistently

Step 1: Define duration tokens. Use a small set that covers most needs. Example:

{"motion":{ "duration":{ "instant":0, "fast":120, "medium":200, "slow":320, "xslow":480 } } }

Step 2: Define easing tokens. Include at least one standard, one emphasized, and one linear. Example:

{"motion":{ "easing":{ "standard":"cubic-bezier(0.2, 0, 0, 1)", "emphasized":"cubic-bezier(0.2, 0, 0, 1.2)", "decelerate":"cubic-bezier(0, 0, 0.2, 1)", "accelerate":"cubic-bezier(0.4, 0, 1, 1)", "linear":"linear" } } }

Step 3: Define distance tokens for common micro-movements. Keep distances subtle. Example:

{"motion":{ "distance":{ "sm":4, "md":8, "lg":16 } } }

Step 4: Create composite motion recipes for repeated patterns. These are not always called “tokens” in every organization, but they can be stored similarly as structured values. Example:

{"motion":{ "pattern":{ "fadeIn":{ "duration":"{motion.duration.medium}", "easing":"{motion.easing.decelerate}", "fromOpacity":0, "toOpacity":1 }, "drawerEnter":{ "duration":"{motion.duration.slow}", "easing":"{motion.easing.decelerate}", "fromTranslateY":"{motion.distance.lg}", "toTranslateY":0 } } } }

Step 5: Add accessibility controls for reduced motion. Tokens can support a reduced-motion mode by remapping durations to near-zero and removing distances. Example:

{"modes":{ "reducedMotion":{ "motion":{ "duration":{ "fast":0, "medium":0, "slow":0 }, "distance":{ "sm":0, "md":0, "lg":0 } } } } }

Components can then respect the mode without special-case logic scattered throughout the codebase.

Practical example: using motion tokens in CSS

:root{ --motion-fast: 120ms; --motion-medium: 200ms; --ease-standard: cubic-bezier(0.2, 0, 0, 1); --ease-decelerate: cubic-bezier(0, 0, 0.2, 1); } .button{ transition: background-color var(--motion-fast) var(--ease-standard), transform var(--motion-fast) var(--ease-standard); } .button:active{ transform: translateY(1px); } .modal{ animation: modal-enter var(--motion-medium) var(--ease-decelerate) both; } @keyframes modal-enter{ from{ opacity:0; transform: translateY(8px); } to{ opacity:1; transform: translateY(0); } }

In a token-driven workflow, the 8px translate would also be generated from motion.distance.md rather than hard-coded.

Cross-cutting practices: governance, versioning, and quality checks

Token linting and validation

Because tokens are data, you can validate them automatically. Common checks include:

  • Type validation: colors must be valid hex/RGBA; durations must be numbers; dimensions must be non-negative.

  • Reference integrity: referenced tokens like {color.gray.900} must exist.

  • Contrast checks: enforce minimum contrast for defined semantic pairs (e.g., primary text on canvas, on-brand text on brand background).

  • Naming conventions: prevent accidental drift like colour vs color, or inconsistent casing.

These checks reduce regressions when multiple teams contribute to the token library.

Deprecation strategy

Tokens will change. A controlled deprecation approach prevents breaking products:

  • Mark tokens as deprecated in metadata (e.g., deprecated: true, replacedBy).

  • Keep deprecated tokens for at least one release cycle while updating consumers.

  • Provide codemods or automated find/replace guidance when possible.

This is especially important for semantic tokens that components reference widely.

When to add a new token vs. reuse an existing one

Use these decision rules to avoid token sprawl:

  • Add a new semantic token when a new meaning appears repeatedly (e.g., a new surface type like “elevated” used across multiple components).

  • Reuse an existing token when the intent matches, even if the value is “close but not perfect.” Consistency usually beats micro-optimization.

  • Add a component token when a component needs a stable contract that may evolve independently (e.g., a complex data table with multiple density modes).

  • Avoid adding base tokens for one-off values. If a value is truly unique, reconsider whether it should exist or whether the design should align to the scale.

Putting it together: a minimal token file example

The following example shows a compact, coherent set spanning color, type, spacing, and motion, using references to keep the system maintainable:

{"color":{ "gray":{ "0":"#FFFFFF","50":"#F7F7F8","200":"#D9DCE1","700":"#475467","900":"#1D2939","950":"#0B1220" }, "blue":{ "600":"#1570EF","700":"#175CD3","800":"#1849A9" }, "text":{ "primary":"{color.gray.900}", "secondary":"{color.gray.700}", "onBrand":"{color.gray.0}" }, "background":{ "canvas":"{color.gray.0}", "surface":"{color.gray.0}", "brand":"{color.blue.600}" }, "border":{ "default":"{color.gray.200}", "focus":"{color.blue.600}" }, "state":{ "brandHover":"{color.blue.700}", "brandPressed":"{color.blue.800}" } }, "type":{ "fontFamily":{ "sans":"Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif" }, "fontWeight":{ "regular":400,"semibold":600 }, "size":{ "sm":14,"md":16,"xl":20 }, "lineHeight":{ "sm":20,"md":24,"xl":28 }, "letterSpacing":{ "default":0,"tight":-0.2 }, "textStyle":{ "body":{ "fontFamily":"{type.fontFamily.sans}","fontWeight":"{type.fontWeight.regular}","fontSize":"{type.size.md}","lineHeight":"{type.lineHeight.md}","letterSpacing":"{type.letterSpacing.default}" }, "heading":{ "fontFamily":"{type.fontFamily.sans}","fontWeight":"{type.fontWeight.semibold}","fontSize":"{type.size.xl}","lineHeight":"{type.lineHeight.xl}","letterSpacing":"{type.letterSpacing.tight}" } } }, "space":{ "0":0,"1":4,"2":8,"3":12,"4":16,"6":24,"8":32, "inset":{ "sm":"{space.3}","md":"{space.4}","lg":"{space.6}" }, "stack":{ "sm":"{space.3}","md":"{space.4}","lg":"{space.6}" } }, "motion":{ "duration":{ "fast":120,"medium":200,"slow":320 }, "easing":{ "standard":"cubic-bezier(0.2, 0, 0, 1)","decelerate":"cubic-bezier(0, 0, 0.2, 1)" }, "distance":{ "sm":4,"md":8 } } }

In implementation, components should primarily consume semantic tokens like color.text.primary, space.inset.md, and motion.duration.fast. Base tokens remain available for controlled extension, but they should not become the default choice in component styling.

Now answer the exercise about the content:

Why should components usually reference semantic tokens instead of base palette or spacing tokens?

You are right! Congratulations, now go to the next page

You missed! Try again.

Semantic tokens encode intent (like primary text or surface background) and can be remapped for themes (such as dark mode) while components keep referencing the same token names.

Next chapter

Logo Families and Responsive Identity Rules

Arrow Right Icon
Free Ebook cover Designing Modular Brand Systems: From Visual DNA to Scalable Asset Libraries
19%

Designing Modular Brand Systems: From Visual DNA to Scalable Asset Libraries

New course

16 chapters

Download the app to earn free Certification and listen to the courses in the background, even with the screen off.