Skip to content

design tokens(draft)

Simeon Simeonoff edited this page Jan 14, 2026 · 2 revisions

Design Tokens Specification: Figma to DTCG to Sass [Draft]

Contents

  1. Overview
  2. Figma Variable Structure and Collections
  3. Exporting Tokens to DTCG JSON
  4. Transforming Tokens to Sass Maps with Terrazzo
  5. Future Specification Improvements

Owned by

Product Design Team

Developer Name:

Designer Name:

Requires approval from

  • Peer Developer:
  • Design Manager: Svilen Dimchevski

Signed off by

  • Product Owner: Radoslav Mirchev | Date:

Revision History

Version Users Date Notes
1 Initial draft Date 07-11-2025

Overview

This specification defines a workflow to translate Figma variable collections into W3C Design Tokens (DTCG) JSON files, and then convert those tokens into Sass maps consumable by Ignite UI’s theming framework. The goal is to maintain a single source of truth in Figma for design values (colors, typography, spacing, etc.) and ensure those values carry through to code with full fidelity (including theming modes and references). The process involves a custom Figma plugin to export variables in DTCG format, followed by a Terrazzo-based pipeline to transform the JSON tokens into Sass. This end-to-end flow is illustrated below:

Figure 1

Figure 1: Process from Figma to Sass – Figma variables are exported as DTCG tokens via a custom plugin, then processed by Terrazzo with a Sass transformer to generate Sass maps for the design system.

Figma Variable Structure and Collections

In Figma, design tokens are organized using Variable Collections. Each collection acts as a bucket for related variables and their modes. We will use separate collections for Colors, Typography, Spacing, Shadows, and Components, aligning with the design system’s domains. This separation ensures that, for example, color themes (light/dark) are isolated from other types of variables like spacing scales.

Naming and Grouping Conventions

Within a collection, variables are hierarchically grouped by naming paths using the "slash" (/) in Figma. For example, a variable named Button/Background/Default will appear under a Button group, then a Background subgroup, with the variable Default inside it. Figma's UI displays these groups in the Variables panel (as folders), making it easy to organize tokens. In the Components collection, we will leverage this to group variables by component name (Button, Card, Avatar, etc.), and within each, by the design property (Background, Foreground, Border, Shadow, etc.). For instance, all Button-related tokens can live under the Button group with subgroups for its parts. This convention improves clarity for designers and developers – Figma even shows the full path in Dev Mode (joining groups with hyphens, e.g. Button-Background-Default for the token above).

Figma Sample

Figure: Example Figma “Color – Semantic” collection (for component-specific colors). Shown is the Button group with subgroups Background and Label, each containing tokens like Default, Hover, Pressed. The table columns for Light and Dark indicate the two mode values for each token. In this setup, the Button background tokens are aliases referencing base color tokens (e.g. “Blue/300”) for the Light theme and alternate tokens (e.g. “Blue/100”) for Dark theme.

Primitive vs. Semantic Tokens

We distinguish primitive tokens (raw values with no contextual meaning) from semantic tokens (aliases that carry contextual names) . Primitive tokens reside in collections like Colors or Spacing – for example, a color variable Blue/500 with value #2D6AE3 or a spacing variable Spacing/16 with value 16px. These represent core values (options) without specific usage tied to them. Semantic tokens, on the other hand, live in the Components (or other semantic) collection and reference those primitives to convey usage intent. For instance, a token Toast/Background/Default in a semantic collection might alias the primitive Neutral/400 color . This tells designers and developers that the Toast background uses that neutral color, rather than an arbitrary value. By using aliases (references) in Figma, any change to a primitive value will propagate to all semantic tokens that reference it, reinforcing single source of truth .

Supported Variable Types

Figma currently supports four variable types: Color, Number (float), Boolean, and String . Our Figma tokens will use these types appropriately for each domain (colors as Color type, spacing/radius as Number, toggles as Boolean, etc.). Figma does not have native composite types for things like shadows or typography styles, so complex design tokens will be represented via multiple variables or as styles. For example, a Shadow token in design terms (which includes color, blur, offsets) might be broken into multiple Figma variables (color and numeric values) or managed as a Figma Effect Style. These will later be combined in the token JSON if needed. Typography styles (font, size, line-height, etc.) could be managed via separate variables or Figma Text Style definitions – our plugin can optionally include styles in the export for completeness. In summary, we will map Figma’s 4 types to the corresponding DTCG token types: Color → $type: "color", Number (float) → $type: "dimension" (with unit), Boolean → $type: "boolean", String → $type: "string" . (Figma’s “float” numbers will be assumed to represent pixel dimensions; our export will attach a unit "px" to these to form a proper dimensional token .)

Use of Modes for Theming

Figma’s mode feature will be used to capture theming variations (e.g. Light vs Dark mode values). Each collection can define one or more modes. For colors, we expect at least Light and Dark modes in the Colors collection, allowing each color variable to hold two values . Other collections can use modes as needed (for example, a Size collection might have Small, Medium, and Large modes, or a Typography collection might have scaling modes). In our setup, most primitives (spacing, etc.) will likely use only a default mode (no variation), whereas the Colors collection and possibly the Components (semantic) collection will support light/dark modes. Designers should define modes in Figma for any collection where tokens need theme-specific values, and assign the appropriate values for each mode in the Figma UI. The variables shown in the figure above demonstrate how the Button tokens have different alias targets for Light vs Dark – for example, button/background/idle references primary/300 in Light and primary/100 in Dark. This mode data will be preserved in the exported design token files.

Preserving References (Aliases)

It is critical that token relationships defined in Figma (one variable referencing another) are preserved in the design token output. The plugin will not “hard-code” resolved values for aliases; instead it will output a reference in the DTCG format. For example, if a Figma variable “button/foreground/idle” is an alias of “gray/800” (a color), the JSON token will use a reference like "{colors.gray.800}" as its value rather than duplicating the hex code. This way, the dependency remains clear – Button Foreground Idle will always pull from the "gray/800" token. Designers should use Figma’s aliasing feature to link variables (rather than copying values) whenever one token should derive from another. Our specification enforces that these links be reflected in code.

Additionally, designers are encouraged to fill out the description field for each variable in Figma. These descriptions (e.g. “The background color for contained buttons in idle state”) will be carried into the token JSON and ultimately serve as documentation for developers and in style guides. They help clarify intent and are especially useful for semantic tokens where the usage might not be obvious just from the name. Descriptions can be viewed in Figma’s Inspect or Dev Mode, and we will include them in the output as $description properties for each token .

Exporting Tokens to DTCG JSON

To extract the Figma variables into code, we will develop a custom Figma plugin that utilizes Figma's Plugin API to fetch all local variables and their metadata. This plugin's behavior will be modeled on existing solutions for tokenization, but refined to fully conform to the latest DTCG specification. Key aspects of the export format and process are outlined below:

  • One JSON File per Collection: Each Figma variable collection will be exported as a separate JSON file containing the tokens of that collection. For example, colors.json, typography.json, spacing.json, elevations.json, and components.json. This separation mirrors how the design team organizes tokens and makes it easy to integrate or update specific categories without affecting others. (If needed, we can also produce a single consolidated file, but the primary approach is to keep collections modular).
  • DTCG-Compliant Structure: The JSON files will adhere to the W3C Design Tokens format (latest spec, at the time of writing 2025.10) so they can be used with modern token tools. In practice, this means:
    • Using DTCG key names: $value, $type, $description, etc., rather than custom or legacy naming.
    • Nesting tokens according to their group hierarchy. For instance, a Figma variable named "button/background/idle" in the Components collection would appear in JSON as:
{
  "button": {
    "background": {
      "idle": {
        "$type": "color",
        "$value": "{colors.primary.300}"
      }
    }
  }
}

where {colors.primary.300} is a reference to a token in the colors collection. The JSON keys follow Figma group names exactly (with appropriate normalization for any spaces or special characters). We will transform naming to a consistent case or delimiter for code, but since Terrazzo can handle either slashes or dot notation transparently, we may keep names as-is or convert / to . in keys depending on what Terrazzo expects. The DTCG spec suggests using . as hierarchy separators in token IDs, but it is not strictly required.

Token Metadata

Base Values

For each token, the plugin will include the following properties:

  • Type: The $type field will denote the token’s type (color, dimension, etc.), mapped from Figma’s variable type.
  • Description: As noted, if a Figma variable has a description, it will be added as a $description field in the token JSON .

We will use DTCG’s standard types:

  • Figma Color$type: "color" (value will be a color object or string in a chosen format, e.g. hex).
  • Figma Number (for numeric values like spacing, radii) → $type: "dimension" with a unit. By default we’ll interpret these as pixels, so a Figma float 8 becomes:
{
  "$type": "dimension",
  "$value": {
    "unit": "px",
    "value": 8
  }
}
  • Figma Boolean$type: "boolean" (the $value key will be set to will be true/false).
  • Figma String$type: "string" (the $value is the string content).

Note

Figma does not support complex types like gradient, shadow, typography directly as variables. If such tokens are needed, we’ll either export them via styles or assemble them from primitives. For example, a Shadow token could be represented with $type: "shadow" and a composite $value object if we collect all necessary pieces (color, offsets, blur) from variables or styles . Our initial scope maps the four basic types; extending to composite types can be an evolution of the plugin.

Mode Values

For tokens with multiple mode values (e.g. a color that has different light and dark values), we will utilize the $extensions.modes property as recommended. In the exported JSON, the token’s $value will hold the default mode’s value, and under $extensions: { "modes": { } } we will list the alternative mode values keyed by mode name or identifier.

For example:

{
  "button": {
    "idle": {
      "$type": "color",
      "$value": "{colors.secondary.500}",
      "$extensions": {
        "modes": {
          "dark": {
            "$value": "{colors.secondary.400}"
          }
        }
      }
    }
  }
}

This indicates the token button/idle is secondary/500 in light mode and switches to secondary/400 in dark mode. We use mode names like “dark” as keys for clarity (assuming mode IDs can be mapped to names). Terrazzo and other tools will read this and know to apply the alternate value for the dark theme. (Our plugin bases this on Figma’s defaultModeId and the values per mode that Figma provides in its API.)

Rules:

  1. The collection's defaultModeId determines the token's root $value.
  2. All other modes are added to $extensions.modes keyed by mode name.
  3. Mode names from Figma are transformed to lower case (e.g. "Light" -> "light", "Dark" -> "dark").
  4. If a variable has the same value across all modes, $extensions.modes is omitted.

Alias References

If a token is an alias of another, the $value will be a reference string in curly braces per the DTCG syntax. For cross-collection references, we may include the collection name in the path (e.g. {colors.primary.500} or use some agreed prefix. The plugin will ensure the syntax is correct.

Figma-specific Metadata

Optionally, the export can include Figma-specific IDs or scopes if we need them for traceability. For example, we could include the Figma variable ID, style ID, or scope (like ["ALL_SCOPES"] or ["COLOR"] tags that Figma provides to indicate usage scope. By default, we might exclude these to keep the token files clean. However, for completeness, an option could allow including them under $extensions.figma. This could help in verifying we captured everything or in round-tripping tokens back to Figma if needed. For the initial implementation, we will likely not include Figma’s internal IDs unless a use-case emerges.

Example JSON Excerpt

Below is a conceptual example combining several of the above points (note: for illustration; not actual file syntax):

{
  "button": {
    "$description": "Design tokens for button component",
    "background": {
      "idle": {
        "$type": "color",
        "$value": "{colors.secondary.500}",
        "$description": "Default background color for buttons",
        "$extensions": {
          "modes": {
            "dark": {
              "$value": "{colors.secondary.400}"
            }
          },
          "legacy": {
            "cssVar": "--button-background"
          }
        }
      },
      "hover": {
        "$type": "color",
        "$value": "{colors.secondary.800}",
        "$description": "Button background on hover",
        "$extensions": {
          "modes": {
            "dark": {
              "$value": "{colors.secondary.600}"
            }
          }
        }
      }
    },
    "border": {
      "radius": {
        "$type": "dimension",
        "$value": {
          "value": 4,
          "unit": "px"
        },
        "$description": "Border radius for buttons"
      }
    }
  }
}

In this snippet, button/background/idle and hover are color tokens referencing colors.secondary.* values, with alternate dark mode values specified. button/border/radius is a dimension token (4px). The structure is nested exactly per the group path. Each token has a description, and the idle background has a legacy CSS variable name recorded. This is the kind of JSON our plugin will generate, following the DTCG format. By comparison, an earlier example from industry practice showed semantic token JSON with light/dark keys – our format encapsulates those under the DTCG $extensions.modes to remain spec-compliant.

The plugin will handle converting color values to a chosen format. We’ll likely export colors as HEX or RGBA strings for simplicity unless a different format is required (the plugin can support hex, rgba, hsla, etc. – HEX is a safe default). If a color has opacity in Figma, we might use hex8 (e.g. #RRGGBBAA) or an rgba() string .

Finally, once the plugin runs, designers or developers can download the JSON files. If running the plugin within Figma, we’ll provide a way to save each collection’s JSON (or a combined JSON) to disk. If using the REST API approach, we’d run a script that fetches the data and writes out the JSON. We will ensure this process is repeatable and document the steps so it can be part of the design-to-development pipeline (potentially even automated on version updates).

Transforming Tokens to Sass Maps with Terrazzo

This section defines how DTCG component tokens exported from Figma are transformed into Sass component schemas and CSS variables.

[!INFO] The initial implementation covers component tokens only (e.g. button, card, avatar). Primitive collections (e.g. colors, typography, elevation) are not consumed directly from DTCG in this phase. Instead, the primitive tokens will be added in CSS via existing Ignite theming mechanisms. Future phases may extend Terrazzo to consume primitive collections directly from DTCG.

Inputs

The Terrazzo transformer receives:

Component tokens JSON (DTCG)

A file such as components.json with tokens grouped by component name and nested paths, e.g.:

  • button.background.idle
  • button.background.hover
  • button.border.radius

Each token exposes:

  • $type (e.g. "color", "dimension")
  • $value (literal or DTCG reference string)
  • $description (from Figma)
  • Optional $extensions (may be enriched later)

Legacy schema alias map (hand-written JSON)

A repo-maintained file mapping new DTCG token paths to old schema property keys:

  • Keys are dot-separated DTCG paths under the components collection.
  • Values are the legacy Ignite schema keys currently used in existing Sass schemas (e.g. the keys passed to var-get / used in existing component maps).
  • If absent, no alias is added (the token has no legacy equivalent and can be considered “new only”).

Example:

{
  "button.background.idle": "button-background",
  "button.background.hover": "button-hover-background",
  "button.border.radius": "button-border-radius"
}

The enriched token objects (with $extensions.alias where applicable) are then used as the source of truth for Sass schema generation.

This ensures the mapping between new DTCG token paths and old Ignite schema keys is explicit and version-controlled (in the alias JSON), but also available at the token level via $extensions.

Component Sass schema structure (DTCG-shaped)

For each component (e.g. button, card), the transformer creates a Sass map schema that mirrors the DTCG hierarchy.

Example for button (simplified):

$button: (
  background: (
    idle: (
      $type: 'color',
      $value: '{colors.primary.500}',
      $description: 'Button background (idle)',
      $extensions: (
        alias: 'button-background',
        modes: (
          dark: '{colors.primary.600}'
        )
      )
    ),
    hover: (
      $type: 'color',
      $value: '{colors.primary.600}',
      $extensions: (
        alias: 'button-hover-background'
      )
    )
  ),
  border: (
    radius: (
      $type: 'dimension',
      $value: (
        default: (
          value: 4,
          unit: 'px'
        ),
        min: (
          value: 0,
          unit: 'px'
        ),
        max: (
          value: 20,
          unit: 'px'
        )
      ),
      // Grouped value structure for border-radius($default, $min, $max)
      $extensions: (
        alias: 'button-border-radius'
      )
    )
  )
);

Requirements:

  • Schema is component-scoped:
    • One schema map per component (e.g. $button, $card).
  • Leaf nodes must:
    • Preserve $type and $value from DTCG.
    • Preserve $description (if present).
    • Include $extensions.alias when an alias is provided by the mapping file.
    • Include any additional structured extensions needed for special semantics.

Note

The schema remains DTCG-shaped and declarative: no var(), no Ignite UI Theming-specific function calls embedded. All behavior is derived later based on $type and $extensions.

Type-aware value resolution (resolve-token-value)

A resolver function (conceptually resolve-token-value) operates on schema leaf nodes to compute the Sass expression that should be assigned to the component CSS variable for that token.

Inputs:

  • $node – the leaf map containing $type, $value, $extensions, $description.
  • $path – the component-local path, e.g. ('background', 'idle') or ('border', 'radius').

Behavior (conceptual):

  1. Read $type:
    • "color" – interpret $value according to the existing theming rules (literal or alias to an existing palette). No DTCG color primitives are consumed at this stage; color resolution is delegated to the existing Ignite theming mechanisms or to literal values defined in DTCG.
    • "dimension" – interpret either as:
      • A simple dimension object: { value: 4, unit: 'px' } → returns the literal value
      • A composite dimension object with default/min/max structure (for roundness) → see below
  2. Read $extensions:
    • This grouped value structure keeps all related constraint values together in $value, avoiding confusion with $extensions.modes which is reserved for theming variations (light/dark).
    • For other categories (spacing, typography, etc.), resolution may simply return the literal or apply any existing helpers; the exact mapping is intentionally kept implementation-specific.
  3. The resolver does not concern itself with legacy schema keys; those are used later when building backwards-compatible structures.

Component CSS variables (tokens mixin)

A tokens mixin is responsible for turning a component schema into component-scoped CSS custom properties.

Signature (conceptual):

/// Emits CSS custom properties for a component based on its DTCG-shaped schema.
/// @param $component-name  e.g. 'button'
/// @param $schema          component schema map (e.g. $tokens-button-schema)
/// @param $selector        CSS selector for scoping (default theme root)
@mixin tokens($component-name, $schema, $selector: :root);

Behavior:

  1. Flatten $schema into (path → node) pairs.
  2. For each leaf node:
    • Determine the canonical CSS var name based on $component-name and $path:
    • Call resolve-token-value($node, $path) to get the Sass expression.
  3. Emit into $selector:
#{$selector} {
  --{$component-name}-#{$normalized-path}: #{resolve-token-value($node, $path)};
}

The result is a set of component-level CSS variables whose values are:

  • Literal colors / dimensions, or
  • Computed expressions using existing Ignite helper functions (e.g. border-radius from mode values),

but whose names are consistently derived from the new DTCG component+path convention.

At this stage, primitives are not consumed directly from DTCG. The assumption is that:

  • Component token $value fields may contain either literal values (e.g. "#2D6AE3") or DTCG reference strings (e.g. "{colors.primary.500}").
  • When a reference string is encountered, it is passed through to the existing Ignite theming helper functions, which are responsible for resolving these references against the existing palette/primitive token system already in use.
  • In future phases, primitive collections (colors, spacing, etc.) may be consumed directly from DTCG, at which point the resolver would handle reference resolution internally.

Using legacy schema keys (backwards compatibility)

The alias extension captured from the alias map is used to maintain compatibility with existing Ignite schema property names and APIs.

Specifically:

  • For each schema leaf that has $extensions.alias, the transformer MUST:
    • Provide a way to construct a legacy-compatible view of the component schema (or theme) where the old property key is preserved.
    • Example: build a secondary map (per component or per theme) keyed by alias, pointing to the same resolved values, so existing code that does var-get($theme, 'button-background') can still resolve correctly.

The exact shape of this compatibility map can be decided during implementation, but the spec requires:

  • The mapping DTCG path → legacy schema key is captured in $extensions.
  • The Sass side has enough information to:
    • Either generate legacy-style schemas, or
    • Generate an adapter map that bridges from old property keys to new token-derived values.

This ensures we can move to DTCG-shaped component schemas without breaking existing usage of var-get and legacy schema names.

The _token function (optional helper)

An optional token helper function provides a simple, token-centric way to consume component CSS variables:

/// Returns a CSS custom property reference for a component token.
/// @param $component-name  e.g. 'button'
/// @param $path...         e.g. 'background.idle' or ('background', 'idle')
@function token($component-name, $path...);
  • It must use the same name-building rule as the tokens mixin.
  • It returns: var(--ig-<component-name>-<path-as-dashes>, <fallback>) where the fallback is:
    • For tokens referencing primitives (colors, shadows, border-radius, etc.): a CSS custom property reference to the corresponding global primitive variable (e.g. var(--ig-primary-500))
    • For tokens with literal values: the resolved literal value from the DTCG token (e.g. #2d6ae3 or 4px)

This function is additive and does not replace var-get. Legacy code can continue to use var-get with legacy schema keys; new code can opt into token with DTCG-style paths.


Future Specification Improvements

[!TODO] The following areas need to be addressed in future versions of this specification:

1. Figma Mode to DTCG Mapping Rules

Context: The spec explains that both Figma and DTCG support modes, but doesn't specify the exact mapping algorithm.

Needed:

  • Define how Figma's collection.defaultModeId determines which value goes into the token's root $value
  • Specify that non-default modes are added to $extensions.modes keyed by mode name (normalized to lowercase)
  • Clarify behavior when a variable has identical values across all modes (omit $extensions.modes?)

2. Phase 1 Scope Boundary (Variables vs Styles)

Context: Figma has both Variables API and Styles API. The spec mentions shadows/typography but doesn't clearly define what's in/out of scope.

Needed:

  • Explicitly state that Phase 1 only exports Figma Variables (Color, Number, Boolean, String)
  • Document that Effect Styles (shadows), Text Styles (typography), and Paint Styles (gradients) require the Styles API and are deferred to Phase 2
  • Provide a workaround: manually-authored DTCG files for composite types that Terrazzo can consume alongside Figma-exported tokens
  • Define what Phase 2 will include and how composite token types will be structured

Add to lines 89-92 area: Clear table of supported vs unsupported types in Phase 1

3. Plugin Error Handling and Validation Strategy

Context: No specification for how the plugin handles error conditions during export.

Needed (add new section after line 138):

Fatal Errors (block export):

  • Broken references: Variable references non-existent variable
  • Type mismatches: Number variable references Color variable
  • Circular references: A → B → C → A

Warnings (export with notification):

  • Missing mode values: Variable has value in Light but not Dark mode
  • Empty descriptions: Semantic tokens without descriptions
  • Scoped variables: Variables with component-specific scope restrictions

Export Behavior:

  • For missing mode values: Use default mode value for all modes (with warning)
  • For scoped variables: Include $extensions.figma.scopes array for traceability
  • Log all warnings to console and export a validation summary file

4. Terrazzo Configuration Specification

Context: The spec mentions "Terrazzo with a Sass transformer" but doesn't show the configuration.

Needed (add new section after line 196):

  • Complete terrazzo.config.js example showing:
    • Token input paths (which JSON files)
    • Output directory structure
    • Custom Ignite UI plugin reference
    • Plugin configuration (alias map path, resolver mapping, etc.)
  • Document custom transformer responsibilities:
    • Type-aware resolution (call appropriate Ignite helpers per $type)
    • Alias mapping (apply legacy schema keys from JSON map)
    • Mode handling (generate Sass structures for theme modes)
    • Component grouping (output one Sass file per component)

5. Legacy Compatibility Implementation Details

Context: Lines 361-376 describe the goal but not the implementation approach.

Needed:

  • Choose between two implementation strategies:
    • Option A (Recommended): Dual schema generation - generate both DTCG-shaped and flat legacy schemas per component
    • Option B: Runtime adapter - translate legacy keys to DTCG paths at resolution time
  • Document the chosen approach with complete code examples
  • Show how var-get($theme, 'button-background') continues to work with the chosen strategy
  • Define deprecation timeline for legacy API (Phase 1: both supported, Phase 2: deprecated warnings, Phase 3: removal)

Recommendation: Specify Option A with dual schemas for clearer separation and better performance.

6. CI/CD Integration and Automation

Context: This is a design-to-development pipeline but lacks automation guidance.

Needed (add new section):

Recommended Workflow:

  1. Designer updates Figma variables
  2. Triggers workflow (manual or webhook)
  3. Export script runs Figma plugin via API
  4. Validation script checks DTCG compliance
  5. Terrazzo generates Sass schemas
  6. Tests run against new schemas
  7. PR created automatically with changes

Required Tooling:

  • scripts/figma-export.js - Automated Figma token export
  • scripts/validate-tokens.js - DTCG schema validation
  • GitHub Actions workflow (or equivalent CI)
  • Token validation rules and test suite

7. Testing Strategy

Context: No specification for how to test this pipeline end-to-end.

Needed:

  • Unit tests: Token resolution, type validation, reference parsing
  • Integration tests: Figma → JSON export, JSON → Sass transformation
  • End-to-end tests: Figma → CSS output validation
  • Visual regression tests: Component rendering with tokens, theme switching
  • Contract tests: DTCG schema compliance, API stability checks

Document test coverage expectations and provide example test cases.

8. Developer Workflows and Troubleshooting

Context: Spec is implementation-focused but lacks practical usage guidance.

Needed:

  • Step-by-step workflow: "Adding a New Component Token"
  • Troubleshooting guide with common issues:
    • Token not resolving → Check reference syntax
    • Wrong theme value → Verify mode configuration
    • Build failing → Validation tool usage
  • Tooling ecosystem recommendations:
    • VS Code extension for token autocomplete
    • Figma plugin usage guide
    • CLI tools for validation/migration

9. Mode Handling in CSS Generation

Context: The Sass schema examples show $extensions.modes (e.g., dark mode values), but the specification doesn't explain how these modes translate to actual CSS output.

Needed:

  • Define how mode values in $extensions.modes are converted to CSS
  • Specify the CSS generation strategy:
    • Option A: Generate separate CSS variable sets per theme (e.g., [data-theme="dark"])
    • Option B: Generate media query-based variables (e.g., @media (prefers-color-scheme: dark))
    • Option C: Generate multiple CSS files (e.g., theme-light.css, theme-dark.css)
  • Document how the tokens mixin should handle mode-aware tokens
  • Provide examples showing:
    • Input: Sass schema with modes
    • Output: Generated CSS with theme selectors/media queries
  • Clarify if modes are theme-exclusive or if other mode types (size, density) use different mechanisms

Example to document:

// Input schema
$button: (
  background: (
    idle: (
      $type: 'color',
      $value: '#2D6AE3',
      $extensions: (
        modes: (
          dark: '#5B8DEF'
        )
      )
    ),
    error: (
        hover: (
        )
        active: ()
        focus: ()
    )
  )
);

// Output CSS (Option A example)
:root {
  --button-background-idle: #2D6AE3;
}

[data-theme="dark"] {
  --button-background-idle: #5B8DEF;
}

10. Reference Resolution Mechanism

Context: Lines 424-427 state that DTCG reference strings (e.g., {colors.primary.500}) are "passed through to existing Ignite theming helper functions," but these functions and their behavior are not defined.

Needed:

  • Document the reference resolution strategy:
    • Which Ignite UI helper functions handle reference resolution?
    • How do they parse DTCG reference syntax {collection.path.to.token}?
    • Do they expect primitives to be loaded as Sass variables? CSS variables? A lookup map?
  • Define the resolution algorithm:
    1. Parse reference string to extract collection and path
    2. Look up the referenced token in [specify data structure]
    3. Return resolved value or [error handling]
  • Specify error handling:
    • What happens when a reference can't be resolved?
    • Should build fail or use fallback value?
    • How are circular references detected and handled?
  • Clarify the primitive collection loading strategy:
    • If primitives aren't consumed from DTCG in Phase 1, how are they made available for reference resolution?
    • Are they loaded from existing Sass palette definitions?
    • Document the bridge between new DTCG references and existing palette system

Example to document:

// How does this:
$value: '{colors.primary.500}'

// Get resolved to:
$resolved: #2D6AE3  // or var(--ig-colors-primary-500) ?

// Document the resolution function signature:
@function resolve-reference($reference-string, $context) {
  // Implementation details needed
}

11. Token Enrichment Process

Context: Line 302 mentions "enriched token objects (with $extensions.alias where applicable)" but the enrichment process—how the Figma export JSON and the hand-written alias map are merged—is never defined.

Needed:

  • Define where enrichment happens in the pipeline:
    • Is it a Terrazzo plugin responsibility?
    • A pre-processing script before Terrazzo runs?
    • Done at Sass compilation time?
  • Document the enrichment algorithm:
    1. Load DTCG tokens from Figma export (components.json)
    2. Load legacy alias map (aliases.json)
    3. For each token in DTCG:
      • Look up token path in alias map
      • If found, inject $extensions.alias property
      • Write enriched token to [output location]
  • Specify data flow:
    Figma Export (components.json)
           +
    Alias Map (aliases.json)
           ↓
    [Enrichment Process - DEFINE THIS]
           ↓
    Enriched Tokens
           ↓
    Terrazzo Transformer
    
  • Clarify file structure:
    • Does enrichment create new files or modify existing ones?
    • Are enriched tokens cached?
    • How is the enrichment triggered in the build process?

Example to document:

// enrichment script (conceptual)
const figmaTokens = loadJSON('components.json');
const aliasMap = loadJSON('aliases.json');

const enrichedTokens = enrichTokens(figmaTokens, aliasMap);
// What does enrichTokens() do exactly? Document this.

writeJSON('components.enriched.json', enrichedTokens);

12. Complex Dimension Value Structure

Context: The border-radius example (lines 337-350) introduces a complex dimension structure with default, min, max keys, but this structure is never explained in the Token Metadata or Base Values sections.

Needed:

  • Document when to use simple dimension vs. grouped dimension structure
  • Add to "Base Values" section (after line 143):
    • Simple Dimension: Single value tokens (spacing, font-size, etc.)
      {
        "$type": "dimension",
        "$value": { "value": 16, "unit": "px" }
      }
    • Grouped Dimension: Tokens requiring constraint values (border-radius with min/max)
      {
        "$type": "dimension",
        "$value": {
          "default": { "value": 4, "unit": "px" },
          "min": { "value": 0, "unit": "px" },
          "max": { "value": 20, "unit": "px" }
        },
        "$extensions": { "category": "roundness" }
      }
  • Define which $extensions.category values trigger grouped structure
  • Explain the Figma variable setup for grouped dimensions:
    • Are these represented as 3 separate Figma variables?
    • Or assembled during export from a single variable?
  • Show this structure in the "Example JSON Excerpt" section (line 210+)

13. Token Naming Conventions and Semantic Standards

This section establishes the authoritative naming conventions for design tokens used across Figma variable collections and the Ignite UI Theming package. These conventions ensure consistency between design and development, reduce ambiguity, and enable automated validation.

13.1 Token Path Structure

All component tokens follow this hierarchical structure:

component[.part].property[.state]
Segment Required Description Examples
component Yes Component name in kebab-case button, text-field, date-picker
part No Component anatomy element (omit for root element) icon, label, header, overlay
property Yes Visual property being styled background, foreground, border, shadow
state No Interaction or synthetic state idle, hover, pressed, focused, disabled

Format by context:

  • Figma: Use slash (/) as delimiter → button/background/idle
  • DTCG JSON: Nested object hierarchy
  • CSS Variables: Hyphen-delimited → --button-background-idle

13.2 Properties Vocabulary

Use only these standard property names. The Figma plugin will warn on non-standard terms.

13.2.1 Color Properties
Standard Term Avoid Description Example Path
background fill, bg, surface, backgroundColor Background color of element button/background/idle
foreground text, color, content, text-color Color of text and icons button/label/foreground/idle
13.2.2 Border Properties
Standard Term Avoid Description Example Path
border/color outline, stroke, borderColor Border color card/border/color/idle
border/width border-size, thickness, stroke-width Border thickness input/border/width
border/radius corner-radius, roundness, corners Corner rounding button/border/radius
border/radius/block-start top-left/top-right based on direction Corner rounding button/border/block-start
border/style - Border style (solid, dashed) input/border/style
13.2.3 Shadow Properties
Standard Term Avoid Description Example Path
shadow elevation, depth, box-shadow Drop shadow card/shadow/idle
13.2.4 Spacing Properties
Standard Term Avoid Description Example Path
padding/inline padding-x, padding-horizontal Horizontal (start/end) padding button/padding/inline
padding/block padding-y, padding-vertical Vertical (top/bottom) padding button/padding/block
gap spacing, margin-between Space between child elements stack/gap
13.2.5 Typography Properties
Standard Term Avoid Description Example Path
font/size text-size, fontSize Font size button/label/font/size
font/weight text-weight, fontWeight Font weight heading/font/weight
font/family text-family, fontFamily Font family body/font/family
line-height leading, lineHeight Line height paragraph/line-height
letter-spacing tracking, letterSpacing Letter spacing heading/letter-spacing
13.2.6 Other Properties
Standard Term Avoid Description Example Path
opacity alpha, transparency Transparency values overlay/opacity
size dimension, width, height Explicit dimensions avatar/size
min-size / max-size - Constraint dimensions icon/min-size
13.2.7 Unified Terms Reference
Common Alternatives Unified Term Description
fill, bg, backgroundColor, surface background All background-related tokens
text, color, content, text-color foreground Content color (text, icons)
outline, stroke, borderColor border Namespace for border tokens
elevation, depth, box-shadow shadow Shadow and depth styling
font, typography, textStyle font Typography tokens
alpha, transparency opacity Transparency values
animation, duration, ease, timing motion Animation and timing values

13.3 States Vocabulary

13.3.1 Interactive States (Native)

These states correspond to user interactions and map to CSS pseudo-classes:

Standard Term Avoid CSS Equivalent Description
idle default, rest, normal, base :not(:hover):not(:active)... Default state, no interaction
hover hovered, over, mouseover :hover Mouse pointer over element
pressed active, clicked, pressing :active Element being clicked/pressed
focused focus :focus-visible Keyboard focus visible
disabled inactive, off :disabled, [aria-disabled] Non-interactive state
13.3.2 Synthetic States (Application-specific)

These states are controlled by application logic, not direct user interaction:

Standard Term Description Example Path
selected Currently selected item list-item/background/selected
active Active/current navigation item nav-item/background/active
error Error/invalid validation state input/border/color/error
success Success/valid validation state input/border/color/success
warning Warning state badge/background/warning
info Informational state alert/background/info
open Expanded/opened state dropdown/indicator/foreground/open
checked Checked/toggled on checkbox/indicator/background/checked
unchecked Unchecked/toggled off checkbox/indicator/background/unchecked
indeterminate Partially checked checkbox/indicator/background/indeterminate
loading Loading/processing state button/background/loading
current Current item in sequence stepper/indicator/background/current
completed Completed step/item stepper/indicator/background/completed
13.3.3 State Combinations

When combining synthetic and interactive states, use this order:

[synthetic-state]-[interactive-state]

Examples:

  • button/background/error-hover → Error state + hover interaction
  • input/border/color/error-focused → Error state + focused interaction
  • checkbox/indicator/background/checked-hover → Checked state + hover interaction

13.4 Component Parts (Anatomy)

When a component has multiple visual elements, use explicit part names:

Part Name Description Example
(omitted) Root element of component button/background/idle
label Text label element button/label/foreground/idle
icon Icon element button/icon/foreground/idle
prefix Leading/prefix element input/prefix/foreground
suffix Trailing/suffix element input/suffix/foreground
header Header section card/header/background
content Main content area dialog/content/background
footer Footer section card/footer/background
overlay Overlay/backdrop element dialog/overlay/background
indicator State indicator (checkmarks, arrows) checkbox/indicator/foreground
track Track element (sliders, toggles) slider/track/background
thumb Draggable thumb element slider/thumb/background
separator Divider/separator line menu/separator/background
value Usually text generated via user interaction (typing, selection, etc.) input/value/foreground
placeholder Placeholder content input/placeholder/foreground
helper Helper text element input/helper/foreground
counter Character/item counter input/counter/foreground

Rules for parts:

  1. Omit part for root element - button/background/idle (not button/root/background/idle)
  2. Use explicit parts ONLY when needed - When a component has distinct visual elements with their own styling
  3. Parts inherit component context - button/icon/foreground refers to the icon inside the button

13.5 Border Token Structure

Border tokens use a nested structure under the border namespace:

Token Path Type Description Stateful?
{component}/border/color[/state] color Border stroke color Yes
{component}/border/width dimension Border thickness Usually no
{component}/border/radius dimension Corner rounding Usually no
{component}/border/style string Border style (solid, dashed) Usually no

Complete example for input component:

input/border/color/idle
input/border/color/hover
input/border/color/focused
input/border/color/disabled
input/border/color/error
input/border/color/error-focused
input/border/width
input/border/radius

13.6 Figma Variable Naming Rules

13.6.1 Format

In Figma, use slash (/) as the delimiter to create visual groups:

{component}/{part?}/{property}/{subproperty?}/{state?}
13.6.2 Rules for Designers
Rule Correct Incorrect
Use lowercase kebab-case for all segments text-field/border/color/focused TextField/Border/Color/Focused
Use slash (/) as delimiter button/background/hover button-background-hover, button.background.hover
Omit part for root element properties button/background/idle button/root/background/idle
Place state last in the path button/background/hover button/hover/background
Use standard vocabulary only button/foreground/idle button/text-color/default
Use singular names button, shadow buttons, shadows
Include full path for stateful properties button/background/idle button/background (ambiguous, the button is a stateful component)
Non-stateful properties omit state suffix avatar/background avatar/background/idle

Note: If the component is stateless(like the avatar), simply omit the state segment.

13.6.3 Figma Collection Organization

Tokens should be organized into these Figma variable collections:

Collection Contents Modes
Colors Primitive color palette (gray, primary, etc.) Light, Dark
Typography Font sizes, weights, families, line-heights Default (or scale modes)
Spacing Spacing scale values Default
Shadows Shadow definitions Light, Dark
Components Component-specific semantic tokens Light, Dark

13.7 Variants, Sizes, and Modes

These variations are handled via separate token sets or Figma modes, not in the token path:

Variation Type Handling Strategy Example
Variants (primary, outlined, ghost) Separate Figma collections per variant Components - Primary, Components - Outlined
Sizes (small, medium, large) Separate Figma collections or modes Components - Small, Components - Large
Themes (light, dark) Figma modes on the collections Light/Dark modes

This approach keeps token paths clean and prevents combinatorial explosion.

13.8 Complete Component Example: Button

13.8.1 Figma Variables
button/background/idle
button/background/hover
button/background/pressed
button/background/focused
button/background/disabled

button/foreground/idle
button/foreground/hover
button/foreground/pressed
button/foreground/focused
button/foreground/disabled

button/icon/foreground/idle
button/icon/foreground/hover
button/icon/foreground/pressed
button/icon/foreground/focused
button/icon/foreground/disabled

button/label/foreground/idle
button/label/foreground/hover
button/label/foreground/pressed
button/label/foreground/focused
button/label/foreground/disabled

button/border/color/idle
button/border/color/hover
button/border/color/pressed
button/border/color/focused
button/border/color/disabled
button/border/width
button/border/radius

button/shadow/idle
button/shadow/hover
button/shadow/pressed
button/shadow/focused

button/padding/inline
button/padding/block
button/gap
13.8.2 Resulting DTCG JSON
{
  "button": {
    "$description": "Button component tokens",
    "background": {
      "idle": { "$type": "color", "$value": "{colors.primary.500}", "$description": "Default button background" },
      "hover": { "$type": "color", "$value": "{colors.primary.600}" },
      "pressed": { "$type": "color", "$value": "{colors.primary.700}" },
      "focused": { "$type": "color", "$value": "{colors.primary.500}" },
      "disabled": { "$type": "color", "$value": "{colors.gray.200}" }
    },
    "foreground": {
      "idle": { "$type": "color", "$value": "{colors.white}" },
      "disabled": { "$type": "color", "$value": "{colors.gray.400}" }
    },
    "icon": {
      "foreground": {
        "idle": { "$type": "color", "$value": "{colors.white}" },
        "disabled": { "$type": "color", "$value": "{colors.gray.400}" }
      }
    },
    "label": {
      "foreground": {
        "idle": { "$type": "color", "$value": "{colors.white}" },
        "disabled": { "$type": "color", "$value": "{colors.gray.400}" }
      }
    },
    "border": {
      "color": {
        "idle": { "$type": "color", "$value": "transparent" },
        "focused": { "$type": "color", "$value": "{colors.primary.800}" }
      },
      "width": { "$type": "dimension", "$value": { "value": 1, "unit": "px" } },
      "radius": { "$type": "dimension", "$value": { "value": 4, "unit": "px" } }
    },
    "shadow": {
      "idle": { "$type": "shadow", "$value": "none" },
      "hover": { "$type": "shadow", "$value": "{shadows.sm}" }
    },
    "padding": {
      "inline": { "$type": "dimension", "$value": { "value": 16, "unit": "px" } },
      "block": { "$type": "dimension", "$value": { "value": 8, "unit": "px" } }
    },
    "gap": { "$type": "dimension", "$value": { "value": 8, "unit": "px" } }
  }
}

13.9 Complete Component Example: Input

13.9.1 Figma Variables
input/background/idle
input/background/hover
input/background/focused
input/background/disabled
input/background/error
input/background/error-focused

input/foreground/idle
input/foreground/disabled

input/placeholder/foreground/idle
input/placeholder/foreground/disabled

input/prefix/foreground/idle
input/prefix/foreground/disabled

input/suffix/foreground/idle
input/suffix/foreground/disabled

input/helper/foreground/idle
input/helper/foreground/error

input/border/color/idle
input/border/color/hover
input/border/color/focused
input/border/color/disabled
input/border/color/error
input/border/color/error-focused
input/border/width
input/border/radius

input/padding/inline
input/padding/block
input/gap

13.10 Validation Rules

The Figma export plugin should validate token names and report issues:

13.10.1 Errors (Block Export)
Rule Message
Incorrect casing "Use lowercase kebab-case: 'text-field' not 'TextField'"
Invalid delimiter "Use slash (/) as delimiter in Figma variable names"
13.10.2 Warnings (Export with Notification)
Rule Message
Unknown property name "Property 'fill' not recognized. Did you mean 'background'?"
Invalid state name "State 'hovered' not recognized. Did you mean 'hover'?"
Unknown component part "Part 'body' not in standard list. Consider 'content'."
Missing state for stateful property "Color property missing state suffix. Use 'idle' for default state."
Non-standard variant in path "Variant 'primary' found in token path. Variants should use separate collections."

13.11 Naming Examples: Correct vs Incorrect

Correct
{
  "button": {
    "background": {
      "idle": { "$type": "color", "$value": "#2D6AE3" },
      "hover": { "$type": "color", "$value": "#1E5BBF" },
      "pressed": { "$type": "color", "$value": "#174A9E" }
    },
    "foreground": {
      "idle": { "$type": "color", "$value": "#FFFFFF" }
    },
    "icon": {
      "foreground": {
        "idle": { "$type": "color", "$value": "#FFFFFF" }
      }
    },
    "border": {
      "radius": { "$type": "dimension", "$value": { "value": 4, "unit": "px" } }
    }
  }
}
Incorrect (Mixed Conventions)
{
  "button": {
    "fill": {
      "default": { "$type": "color", "$value": "#2D6AE3" },
      "hovered": { "$type": "color", "$value": "#1E5BBF" },
      "active": { "$type": "color", "$value": "#174A9E" }
    },
    "text-color": {
      "normal": { "$type": "color", "$value": "#FFFFFF" }
    },
    "iconColor": {
      "base": { "$type": "color", "$value": "#FFFFFF" }
    },
    "corner-radius": { "$type": "dimension", "$value": { "value": 4, "unit": "px" } }
  }
}
Issue Problem Correction
fill Non-standard property Use background
default, normal, base Non-standard state Use idle
hovered Non-standard state Use hover
active Ambiguous state Use pressed for click state
text-color Non-standard property Use foreground
iconColor camelCase and non-standard Use icon/foreground
corner-radius Non-standard property Use border/radius

13.12 Decision Summary

Decision Convention
Hierarchy structure component.part.property.state
Interactive state names idle, hover, pressed, focused, disabled
Variants handling Separate token sets/collections
Parts granularity Explicit when component has multiple elements
Root part naming Omit (implicit)
Content part naming Separate icon and label parts
Border structure border/color, border/width, border/radius
Shadow naming shadow/{state}
Figma delimiter Slash (/)
Multi-word casing kebab-case
Directional spacing inline/block (CSS logical properties)
Sizes Separate token sets
Namespace prefix None

Clone this wiki locally