Deep Dive: Schema & Validation

Why Schema Validation?

Design tokens are data, and data needs validation. Without it, typos become runtime bugs, invalid references break builds, and inconsistent formats cause cross-platform issues. Schema validation catches these problems before they reach production.

Our validation strategy has two layers: JSON Schema for structural validation and IntelliSense, plus custom lint checks for semantic rules that JSON Schema can't express.

JSON Schema for DTCG Tokens

The schema validates token structure against the DTCG 1.0 specification. Every token file references the schema, enabling editor IntelliSense and build-time validation.

Schema Reference

// Every token file starts with schema reference
{
  "$schema": "./designTokens.schema.json",
  "color": {
    "palette": {
      "blue": {
        "500": {
          "$type": "color",
          "$value": "#0a65fe"
        }
      }
    }
  }
}

Type Validation

The schema enforces that $value matches the declared $type:

TypeValid ValuesInvalid Values
color#0a65fe, #0a65fe80blue, 16px
dimension16px, 1rem, 016, 1em
number1.5, 400"1.5", 16px
duration250ms, 0.5s250, fast
cubicBezier[0.4, 0, 0.2, 1]ease-in-out

Reference Pattern Validation

Token references must follow the {path.to.token} pattern:

// Schema validates reference syntax
{
  "pattern": "^\{[a-zA-Z][a-zA-Z0-9._-]*\}$"
}

// Valid references
"{core.color.palette.blue.500}"
"{semantic.spacing.size.04}"

// Invalid references (caught by schema)
"core.color.palette.blue.500"   // Missing braces
"{}"                            // Empty reference
"{123.invalid}"                 // Starts with number

Custom Lint Checks

JSON Schema validates structure, but some rules require semantic analysis. Our custom validator adds these checks:

1. Circular Reference Detection

References cannot form cycles. The validator builds a dependency graph and detects cycles:

// Bad — DETECTED: Circular reference
{
  "color": {
    "primary": { "$value": "{color.secondary}" },
    "secondary": { "$value": "{color.primary}" }
  }
}

// Error: Circular reference detected:
// color.primary → color.secondary → color.primary

2. Missing Reference Targets

References must point to tokens that exist:

// Bad — DETECTED: Missing reference target
{
  "foreground": {
    "primary": { "$value": "{core.color.pallete.blue.500}" }
    // Typo: "pallete" instead of "palette"
  }
}

// Error: Reference target not found:
// "{core.color.pallete.blue.500}" in foreground.primary

3. Type Compatibility

References must resolve to compatible types:

// Bad — DETECTED: Type mismatch
{
  "spacing": {
    "large": {
      "$type": "dimension",
      "$value": "{core.color.palette.blue.500}"  // Color, not dimension!
    }
  }
}

// Error: Type mismatch in spacing.large:
// Expected dimension, got color from reference

4. Unit Validation

Dimensions must have valid units. Numbers must be unitless. The validator enforces these rules:

// Bad — DETECTED: Invalid unit
{
  "spacing": {
    "medium": {
      "$type": "dimension",
      "$value": "1em"  // em not allowed in DTCG 1.0
    }
  }
}

// Error: Invalid unit "em" in spacing.medium
// Allowed units: px, rem

5. Suspicious Number Values

Large numbers without units often indicate mistakes (e.g., forgetting px):

// WARNING: Suspicious number
{
  "spacing": {
    "large": {
      "$type": "number",
      "$value": 48  // Did you mean "48px"?
    }
  }
}

// Warning: Large number 48 in spacing.large
// Consider if this should be a dimension with unit

Extension Validation

The $extensions property has its own validation rules for our custom features:

Design Paths Extension

// Valid design.paths structure
{
  "$extensions": {
    "design.paths.light": "{core.color.palette.neutral.600}",
    "design.paths.dark": "{core.color.palette.neutral.300}"
  }
}

// Bad — Invalid: paths must be references or values
{
  "$extensions": {
    "design.paths.light": null,  // Invalid
    "design.paths.dark": 123     // Invalid
  }
}

Design Calc Extension

// Valid calc expression
{
  "$extensions": {
    "design.calc": "calc({core.spacing.size.04} + 2px)"
  }
}

// Bad — Invalid: malformed calc
{
  "$extensions": {
    "design.calc": "calc({spacing.04} +"  // Unclosed expression
  }
}

Schema Generation

Our schema is programmatically generated from the DTCG 1.0 specification. This ensures it stays current and includes our custom extensions:

// utils/designTokens/generators/generateSchema.mjs

// DTCG 1.0 type definitions
const dtcgTypes = {
  color: {
    pattern: '^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$',
    description: 'Hex color value'
  },
  dimension: {
    pattern: '^(0|[0-9]+(\.[0-9]+)?(px|rem))$',
    description: 'Dimension with px or rem unit'
  },
  // ... other types
};

// Custom extensions
const extensionSchema = {
  'design.paths.light': { type: 'string' },
  'design.paths.dark': { type: 'string' },
  'design.calc': { type: 'string', pattern: '^calc\(.*\)$' }
};

// Generate combined schema
generateSchema(dtcgTypes, extensionSchema);

Regenerating the Schema

# Regenerate schema after spec changes
npm run tokens:schema

# Output: ui/designTokens/designTokens.schema.json

Validation in CI/CD

Validation runs in the CI pipeline, blocking merges when tokens don't conform:

# .github/workflows/tokens.yml
- name: Validate Tokens
  run: npm run tokens:validate

# Validation steps:
# 1. JSON Schema validation (AJV)
# 2. Reference resolution check
# 3. Circular dependency detection
# 4. Type compatibility verification
# 5. Unit validation
# 6. Extension validation

Validation Output

$ npm run tokens:validate

Validating design tokens...

Schema validation:
  core/color.tokens.json ........... PASS
  core/spacing.tokens.json ......... PASS
  semantic/color.tokens.json ....... PASS

Reference validation:
  Checking 247 references .......... PASS
  No circular dependencies ......... PASS

Type validation:
  Checking type compatibility ...... PASS

Unit validation:
  Checking dimension units ......... PASS
  No suspicious numbers ............ PASS

All validations passed.

Editor IntelliSense

The schema enables rich editor support. VS Code, WebStorm, and other editors provide:

  • Autocomplete for $type values
  • Inline errors for invalid values
  • Hover documentation from $description
  • Go to definition for references
// VS Code settings.json
{
  "json.schemas": [
    {
      "fileMatch": ["**/designTokens/**/*.tokens.json"],
      "url": "./ui/designTokens/designTokens.schema.json"
    }
  ]
}

Pitfalls to Avoid

1. Ignoring Validation Errors

Validation errors exist for a reason. Don't bypass them with --force flags or by disabling checks.

// BAD: Bypassing validation
npm run tokens:build --skip-validation

// GOOD: Fix the underlying issue
// If validation fails, understand why and fix the token

2. Outdated Schema

If you add new token types or extensions, regenerate the schema. Outdated schemas cause false positives/negatives.

// After adding new extension type
npm run tokens:schema  // Regenerate
npm run tokens:validate  // Verify

3. Missing Schema Reference

Token files without $schema don't get editor IntelliSense. Always include the reference:

// BAD: No schema reference
{
  "color": { ... }
}

// GOOD: Schema reference included
{
  "$schema": "./designTokens.schema.json",
  "color": { ... }
}

4. Overly Permissive Patterns

Don't weaken schema patterns to “fix” validation errors. If a value doesn't match the pattern, the value is wrong, not the pattern.

// BAD: Weakening the schema
{
  "dimension": {
    "pattern": ".*"  // Accepts anything!
  }
}

// GOOD: Fix the token value
{
  "$type": "dimension",
  "$value": "16px"  // Correct format
}

Running Validation

Validation can be run manually or as part of the build:

# Full validation
npm run tokens:validate

# Validation as part of build
npm run tokens:build  # Includes validation

# Verbose output for debugging
npm run tokens:validate -- --verbose

# Validate specific file
npm run tokens:validate -- --file=core/color.tokens.json

Summary

  • JSON Schema validates structure and enables IntelliSense
  • Custom lint checks catch semantic issues (circular refs, missing targets)
  • Type validation ensures values match declared types
  • Unit validation enforces DTCG 1.0 unit restrictions
  • Extension validation checks custom metadata format
  • CI integration blocks invalid tokens from merging
  • Never bypass validation—fix the underlying issue

Next Steps

Schema validation works with the build pipeline to ensure only valid tokens reach production. For understanding what the schema validates, see DTCG formats. For resolver-specific validation, see the resolver module.

Source files:

  • ui/designTokens/designTokens.schema.json — Generated schema
  • utils/designTokens/generators/generateSchema.mjs — Schema generator
  • utils/designTokens/validators/validateTokens.mjs — Custom validator
← Back to Design Tokens