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:
| Type | Valid Values | Invalid Values |
|---|---|---|
color | #0a65fe, #0a65fe80 | blue, 16px |
dimension | 16px, 1rem, 0 | 16, 1em |
number | 1.5, 400 | "1.5", 16px |
duration | 250ms, 0.5s | 250, 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 numberCustom 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.primary2. 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.primary3. 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 reference4. 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, rem5. 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 unitExtension 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.jsonValidation 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 validationValidation 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
$typevalues - 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 token2. 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 // Verify3. 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.jsonSummary
- 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 schemautils/designTokens/generators/generateSchema.mjs— Schema generatorutils/designTokens/validators/validateTokens.mjs— Custom validator