Tooling

DTCG

A spec-conformant parser, validator, and serializer for the W3C Design Tokens Community Group format — the interchange layer behind the styleframe dtcg export command.

Overview

The @styleframe/dtcg package is a spec-conformant parser, validator, and serializer for the W3C Design Tokens Community Group (DTCG) format (2025.10). It implements the Format, Color, and Resolver modules and is the interchange layer behind the styleframe dtcg export CLI command.

The package is runtime-agnostic — no filesystem, no DOM, no framework assumptions. You can use it directly to parse, validate, transform, or emit DTCG documents from any Node.js or browser environment.

What is DTCG?

DTCG is a portable JSON format for design tokens, standardised by the W3C Design Tokens Community Group. It defines how colours, dimensions, typography, shadows, and other design primitives should be represented so they can flow between tools — Figma, Tokens Studio, Style Dictionary, and any other DTCG-compatible system.

Learn more: Read the Design Tokens Format Module, Color Module, and Resolver Module specifications.

Why a dedicated package?

  • Standards compliance: Implements all eight primitive types (color, dimension, fontFamily, fontWeight, duration, cubicBezier, number, string) and all six composite types (border, strokeStyle, transition, shadow, gradient, typography).
  • Full color coverage: Supports all 14 color spaces from the Color Module (srgb, oklch, display-p3, lab, lch, hsl, hwb, …).
  • Lossless aliases: Transitive alias resolution (A → B → C) with circular-reference detection.
  • Multi-theme via Resolver: Native support for the Resolver Module — sets, modifiers, and explicit resolution order for theme bundles.
  • Type-safe classification: classifyValue() infers a DTCG $type from a raw value plus optional path hint, so untyped inputs get the right type stamp.
  • Framework-agnostic: No filesystem, DOM, or framework dependencies. Bring your own I/O.

What's included

The package is organised by spec concern. Each group below covers a slice of the DTCG specification.

Parse, validate, walk

SymbolPurpose
parse(input)Parse a .tokens.json document; throws ParseError on invalid shape
validate(doc)Validate against the spec; returns ValidationError[]
validateResolver(doc)Validate a .resolver.json document
walk(doc)Iterate every token and group with its path

Aliases

SymbolPurpose
parseAlias(string)Extract the path from "{color.primary}"
formatAlias(path)Wrap a path in alias syntax
resolveAliases(doc)Flatten transitive aliases; throws on cycles or unknown references
lookupToken(doc, path)Find a token by path
splitPath / joinPath / appendPathPath-string utilities

Inheritance

SymbolPurpose
applyInheritance(doc)Propagate $type and $deprecated from parent groups to children

Type guards

Runtime guards for narrowing union types: isToken, isGroup, isAlias, isColorValue, isDimensionValue, isDurationValue, isFontFamilyValue, isFontWeightValue, isNumberValue, isCubicBezierValue, isBorderValue, isShadowValue, isGradientValue, isTransitionValue, isTypographyValue, isStrokeStyleValue, and more.

Classification

SymbolPurpose
classifyValue(value, { path? })Detect a DTCG type from a raw value plus optional path hint
parseCubicBezier(string)Parse "cubic-bezier(0.42, 0, 1, 1)" into a tuple
easingKeywordToBezier(name)Convert CSS easing keywords (ease, ease-in-out, …) to cubic-bezier arrays

Per-type namespaces

Each value type ships parse / format / convert helpers under its own namespace:

  • color.* — 14 color spaces, culori-backed conversion
  • dimension.* — parse and format "16px", "2rem", etc.
  • duration.* — parse and format "100ms", "1.5s"
  • composite.* — helpers for border, strokeStyle, transition, shadow, gradient, typography

Resolver Module

SymbolPurpose
parseResolver(json)Parse a .resolver.json document with sets, modifiers, and resolutionOrder
resolveResolver(doc, context, fileLoader)Resolve sets and apply modifiers; async file loading via callback
mergeDocuments(...docs)Merge multiple DTCG documents into one

Extensions

SymbolPurpose
isValidNamespace(string)Validate reverse-DNS vendor namespaces (e.g. dev.styleframe.*)

CLI integration

The styleframe dtcg export command is the primary consumer of this package. The flow is:

  1. Load your Styleframe configuration via @styleframe/loader.
  2. Evaluate every variable to a primitive or CSS expression.
  3. Use classifyValue() to stamp the correct $type on each token.
  4. Use formatAlias() to preserve ref() references as DTCG aliases ({token.path}).
  5. Emit a spec-conformant tokens.json — and a tokens.resolver.json if your project defines themes.
styleframe dtcg export -o tokens.json

The output is consumable by any DTCG-compatible tool — including Tokens Studio, Style Dictionary, and the Styleframe Figma plugin (which performs DTCG → Figma value conversion). For the full Figma round-trip workflow, see the Figma Plugin page.

The CLI's value-conversion concerns (rem ↔ px, fluid expressions, Figma-specific lossiness) live in the consumer layers — not in @styleframe/dtcg. The package itself only emits and consumes spec-compliant DTCG.

Programmatic usage

You can use @styleframe/dtcg directly as a library in any tooling pipeline.

pnpm add @styleframe/dtcg
import {
    parse,
    validate,
    applyInheritance,
    resolveAliases,
    walk,
} from '@styleframe/dtcg';

const doc = parse(jsonString);
const errors = validate(doc);
if (errors.length > 0) {
    throw new Error(`Invalid DTCG document: ${errors[0].message}`);
}

const inherited = applyInheritance(doc);
const resolved = resolveAliases(inherited);

for (const entry of walk(resolved)) {
    console.log(entry.path, entry.token);
}

Diagnostic extensions

When the CLI cannot losslessly express a Styleframe value as a primitive DTCG type, it preserves it via the standard $extensions mechanism under the dev.styleframe namespace:

ExtensionMeaning
dev.styleframe.unknownTypeThe value's DTCG type could not be inferred — token kept as-is
dev.styleframe.expressionOriginal CSS expression (e.g. clamp(), calc()) preserved alongside a fallback
dev.styleframe.fluidBoundA fluid value was normalised to its min or max bound

Downstream tools that don't recognise these extensions ignore them safely. Tools that do (such as the Styleframe Figma plugin) use them to round-trip without information loss.

Resources