Migrating from the DTCG format
This guide explains how to convert Design Tokens Community Group (DTCG) Format Module files into the Design Token Interchange Format (DTIF). Follow the sections below to translate the document structure, references, metadata, and token value payloads while embracing the richer semantics provided by DTIF. Every DTIF example shown here is a complete JSON document that can be validated with the schema in this repository (see the files under examples/dtcg-migration/
).
Compare the data models
DTCG and DTIF agree on storing tokens inside JSON documents but they diverge on several core behaviours:
- Collections instead of groups. DTIF treats any object without a
$value
as a collection. DTCG groups may declare a$type
that applies to every nested token, so copy inherited types onto each DTIF token because collections do not provide default typing. Collections may consist solely of metadata, so you can migrate group descriptions or governance fields without creating placeholder tokens. DTIF collections must omit$type
,$ref
, and$value
entirely—those members belong to tokens—so strip inherited types or aliases from the container before validating. - Reserved member prefixes. Both formats reserve
$
-prefixed keys. DTCG documents, groups, and tokens use$description
,$type
, and$extensions
members and forbid{
,}
, and.
in names. DTIF keeps the prefix rules and adds document-level members such as$version
and$overrides
, so migrate any group metadata into the corresponding collections and resolve naming conflicts before publishing. DTIF's schema now rejects unknown reserved members—any$foo
fields that existed purely for tooling hints must move under$extensions
with a vendor namespace so that validation succeeds. - Alias mechanics. DTCG aliases rely on brace-delimited strings such as
"{button.background}"
described in the alias section. DTIF aliases use$ref
JSON Pointers that start with#
for local targets or include a URI fragment for external references. Convert each DTCG alias into a$ref
and escape/
or~
characters as~1
or~0
per the pointer rules. DTIF additionally rejects directory traversal segments such as../
or their percent-encoded form%2E%2E
, so rewrite any exported DTCG file paths that rely on upward navigation before publishing. Partially encoded sequences like%2E./
,.%2E/
, or..%2F
are also rejected, so normalise build scripts that previously masked traversal with mixed escaping. - CSS identifier escaping. Fields such as
color.$value.colorSpace
,gradient.$value.stops[].color
, and function names inside$value.fn
follow the CSS<ident>
and<dashed-ident>
grammar. DTIF accepts the full Unicode range and CSS escape sequences, so identifiers beginning with digits or containing punctuation should be emitted using standard CSS escapes (for example"\31 6px"
). Keeping identifiers within that grammar avoids migration surprises when tools round-trip colour spaces or platform identifiers. - Type identifiers. DTCG
$type
strings often use short labels such as"sizing"
or"radius"
. DTIF reserves a registry of built-in categories but also accepts vendor-defined identifiers composed of ASCII words separated by dots. Map DTCG categories to the closest DTIF primitive (for example"sizing"
→"dimension"
,"color"
stays"color"
). When you need bespoke semantics, mint a stable identifier—reverse-DNS prefixes likecom.example.tokens.radius
avoid collisions even though the schema only enforces the character set. Tokens that provide either$value
or$ref
should declare$type
so automation can reason about the payload without resolving references; migrating tools ought to copy the original group type onto every token even when legacy exports omitted it.
DTCG export with vendor metadata in $extensions
:
{
"palette": {
"brand": {
"primary": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.0, 0.435, 1.0],
"hex": "#006FFF"
}
}
}
},
"button": {
"$type": "color",
"$value": "{palette.brand.primary}",
"$extensions": {
"com.example.workflow": {
"category": "actions"
}
}
}
}
DTIF conversion that preserves the hint under $extensions
:
{
"button": {
"$type": "color",
"$ref": "#/palette/brand/primary",
"$extensions": {
"org.example.workflow": {
"category": "actions"
}
}
}
}
Both formats reserve the $
prefix, so rewrite any legacy DTCG exports that still emit members such as $category
to use $extensions
before converting them to DTIF.
Example: groups, tokens, and aliases
DTCG structure
{
"$description": "Marketing tokens exported from a DTCG tool.",
"button": {
"$type": "color",
"$description": "Primary button colors",
"background": {
"$description": "Solid brand fill",
"$value": {
"colorSpace": "srgb",
"components": [0.0, 0.435, 1.0],
"hex": "#006FFF"
},
"$extensions": {
"com.example.export": { "legacyHex": "#006FFF" }
}
},
"text": {
"$description": "Alias to the background color",
"$value": "{button.background}"
}
}
}
DTIF structure (examples/dtcg-migration/data-model.tokens.json
)
{
"$version": "1.0.0",
"button": {
"$description": "Primary button colors",
"background": {
"$type": "color",
"$description": "Solid brand fill",
"$value": {
"colorSpace": "srgb",
"components": [0.0, 0.435, 1.0]
},
"$extensions": {
"org.example.export": { "legacyHex": "#006FFF" }
}
},
"text": {
"$type": "color",
"$description": "Alias to the background color",
"$ref": "#/button/background"
}
}
}
Copy the inherited $type
onto every DTIF token, convert string aliases into $ref
pointers, and migrate any vendor extensions while keeping their reverse-DNS names.
Model fallback chains
DTIF tokens may provide multiple candidates for a single design decision by placing an array in $value
. Each entry in the array must be either an inline literal, a $ref
alias object, or a function expression. Consumers evaluate the entries in order and stop when a candidate resolves, so you can mix references to other tokens with serialised fallbacks. Arrays must contain at least one item.
This non-empty rule applies even when the source token omitted $type
. Replace placeholder arrays with real candidates or remove them entirely so the DTIF schema does not reject empty fallback slots during validation.
{
"color": {
"surface": {
"$type": "color",
"$value": [
{ "$ref": "#/color/brand" },
{
"colorSpace": "srgb",
"components": [1, 1, 1, 1]
}
]
}
}
}
This pattern keeps legacy fallbacks close to their primary token while letting platform-specific implementations pick the first compatible entry. When you need a simple alias that still participates in fallback evaluation, assign a $ref
object directly to $value
instead of declaring a separate $ref
member.
Multi-layer tokens such as shadow
interpret arrays without $ref
or fn
entries as literal layer stacks. If you intend to supply fallbacks for those tokens, include at least one alias or function entry so the schema treats the array as an ordered fallback chain rather than a single composite value.
Translate computed values
The DTCG Format Module only serialises literal measurements for primitives such as dimension
. When workflows need to preserve the formula behind a computed value, they often store the original expression inside vendor $extensions
metadata. Migration scripts can promote those strings into DTIF function objects that declare an fn
keyword and optional parameters
array as defined in Token types. Each argument or parameter may be a literal, another function object, or a $ref
alias that resolves to a token declaring the same $type
.
DTCG export recording a calc expression inside vendor metadata:
{
"spacing": {
"side": {
"$type": "dimension",
"$value": { "value": 16, "unit": "px" },
"$extensions": {
"com.example.export": {
"sourceExpression": "calc(100% - 1rem)"
}
}
}
}
}
DTIF conversion using an explicit function object:
{
"spacing": {
"side": {
"$type": "dimension",
"$value": { "fn": "calc", "parameters": ["100%", "-", "1rem"] }
}
}
}
When the referenced grammar takes no arguments—such as vendor-specific functions that behave like keywords—omit parameters
entirely. The DTIF schema treats a missing member the same as an empty array, so migrating scripts can drop placeholder []
values instead of emitting redundant lists.
Normalise platform identifiers
Some DTCG workflows annotate platform targets inside $extensions
metadata or rely on implicit naming conventions for number
tokens. DTIF requires lower-case dot-separated segments for the dedicated identifier members—such as cursorType
, borderType
, font.$value.fontType
, filterType
, opacityType
, durationType
, zIndexType
, motionType
, and easingFunction
—so downstream tooling can infer the target platform. Normalise each segment to lower-case ASCII, replace camel case with hyphenated words where needed, and join the segments with dots when you move these annotations into DTIF fields.
DTCG export:
{
"opacity": {
"$type": "number",
"$extensions": {
"com.example.export": { "platform": "CSS.Opacity" }
},
"$value": 0.4
}
}
DTIF conversion:
{
"opacity": {
"$type": "opacity",
"$value": { "opacityType": "css.opacity", "value": 0.4 }
}
}
Prepare the document shell
- Declare a version. DTCG files do not mandate document versioning. DTIF documents SHOULD include a
$version
that follows Semantic Versioning so consumers can evaluate compatibility. - Copy document metadata. DTCG groups may expose
$description
,$extensions
, and$deprecated
. Mirror those members on the new collections, carrying group-level$deprecated
flags across as collection metadata and migrating any string explanations into$description
or vendor extensions. When you need file-level governance details, place them inside the document's top-level$extensions
; lifecycle fields such as$lastModified
and$author
remain token-scoped in DTIF. - Decide on layering. If the DTCG workflow produced separate files per theme or mode, DTIF lets you combine them by layering documents or by defining a
$overrides
array with explicit conditionals. Each override entry must supply at least one$when
key; migrate any empty condition objects into concrete predicates so the schema can evaluate the override. - Normalise names. DTCG forbids
{
,}
, and.
in token or group names because of its alias syntax. DTIF references do not impose those limits, but JSON Pointer segments treat/
and~
as structural characters, so escape them as~1
and~0
when building$ref
strings.
Example: merging themed documents
DTCG theme files (two documents)
{
"$description": "marketing.light",
"$extensions": {
"com.example.workflow": { "owner": "Design Systems" }
},
"color": {
"brand": {
"background": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.0, 0.435, 1.0],
"hex": "#006FFF"
}
}
}
},
"button": {
"$type": "color",
"background": { "$value": "{color.brand.background}" }
}
}
{
"$description": "marketing.dark",
"color": {
"brand": {
"background": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.0, 0.133, 0.333],
"hex": "#002255"
}
}
}
},
"button": {
"$type": "color",
"background": { "$value": "{color.brand.background}" }
}
}
DTIF merged document (examples/dtcg-migration/document-shell.tokens.json
)
{
"$version": "1.0.0",
"$description": "Marketing theme converted from split DTCG files.",
"$extensions": {
"org.example.workflow": { "owner": "Design Systems" }
},
"$overrides": [
{
"$token": "#/button/background",
"$when": { "prefers-color-scheme": "dark" },
"$ref": "#/color/brand/dark"
}
],
"color": {
"brand": {
"light": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.0, 0.435, 1.0, 1.0]
}
},
"dark": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.0, 0.133, 0.333, 1.0]
}
}
}
},
"button": {
"background": {
"$type": "color",
"$description": "Defaults to the light brand color",
"$ref": "#/color/brand/light"
}
}
}
Combine split DTCG files into a single DTIF document, declare $version
, and use $overrides
to handle conditional themes instead of duplicating documents.
Migrate metadata
DTIF reuses familiar fields and introduces additional lifecycle tracking:
$description
remains a plain-text explanation that consumers MAY display (Format and serialisation).$extensions
still hosts vendor data but DTIF requires reverse-DNS prefixes and preservation of unknown entries (Format and serialisation).$deprecated
accepts either a boolean or an object with a$replacement
pointer to another token of the same$type
. Convert DTCG boolean or string deprecation flags on tokens and groups into boolean metadata, move freeform explanations into$description
or vendor namespaces, and add$replacement
pointers for canonical successors. Collections MAY carry the same metadata so inherited group defaults survive the migration (Metadata).- Lifecycle fields. DTIF optionally records
$lastModified
,$lastUsed
,$usageCount
,$author
,$tags
, and$hash
. Populate these from any analytics captured alongside the source tokens, keep$lastUsed
paired with a positive$usageCount
, and ensure$lastUsed
is on or after$lastModified
. When$usageCount
is0
, omit$lastUsed
; the schema enforces these combinations and temporal ordering so telemetry imported from DTCG stays consistent (Metadata).
Example: migrating status metadata
DTCG metadata
{
"status": {
"$type": "color",
"$description": "Status indicator colors",
"$extensions": {
"com.example.workflow": { "owner": "Design Systems" }
},
"warning": {
"$description": "Legacy warning alias",
"$deprecated": "Use caution instead",
"$value": {
"colorSpace": "srgb",
"components": [1.0, 0.839, 0.0],
"hex": "#FFD600"
}
},
"caution": {
"$description": "Preferred caution token",
"$value": {
"colorSpace": "srgb",
"components": [1.0, 0.733, 0.0],
"hex": "#FFBB00"
}
}
}
}
DTIF metadata (examples/dtcg-migration/metadata.tokens.json
)
{
"$version": "1.0.0",
"status": {
"$description": "Status indicator colors",
"$extensions": {
"org.example.workflow": { "owner": "Design Systems" }
},
"warning": {
"$type": "color",
"$description": "Legacy warning alias",
"$deprecated": { "$replacement": "#/status/caution" },
"$value": {
"colorSpace": "srgb",
"components": [1.0, 0.839, 0.0]
}
},
"caution": {
"$type": "color",
"$description": "Preferred caution token",
"$value": {
"colorSpace": "srgb",
"components": [1.0, 0.733, 0.0]
}
}
}
}
Strings inside $deprecated
become structured objects with $replacement
pointers, and vendor metadata keeps its reverse-DNS prefix.
Convert primitive tokens
The table below summarises how the DTCG primitive types map to DTIF.
DTCG type(s) | DTIF target | Migration notes |
---|---|---|
color | color | Move the optional DTCG alpha channel into the components array (as the fourth entry). DTIF accepts optional hex (and matching alpha ) members for sRGB colours when you need to preserve CSS serialisations. DTIF colour spaces follow CSS Color Module Level 4 and expect channel counts that match the declared space. See the DTCG color type and the DTIF colour guidance. |
dimension | dimension | Supply a dimensionType (for example length ) and reuse the existing value and unit . DTCG only allows px or rem , whereas DTIF covers the CSS Values and Units grammar plus platform-specific units. Reference the DTCG dimension type and the DTIF dimension rules. |
duration | duration | Wrap the numeric payload with durationType (such as css.transition-duration ) and keep value /unit . DTCG units are ms or s ; DTIF adds CSS <time> keywords and platform identifiers like frame counts. See DTCG duration and DTIF duration tokens. |
cubicBezier | easing | Replace the bare four-number array with { "easingFunction": "cubic-bezier", "parameters": [...] } . DTIF also supports steps() and native spring identifiers when migrating bespoke timing curves. Compare DTCG cubic Bézier with DTIF easing tokens. |
fontFamily , fontWeight | font or typography | Consolidate DTCG font metadata—fontFamily and fontWeight —into richer DTIF font tokens for assets and typography tokens for composed styles. Normalise string weights such as "regular" or "bold" to their numeric equivalents before assigning them to font or typography payloads (see font tokens and typography). |
number | Contextual (opacity , z-index , typography ) | DTCG number tokens cover gradient stops, line-height ratios, and other scalars. Map each value to a DTIF type that exposes the same semantics: opacity multipliers become opacity , stacking contexts become z-index , and typographic ratios move inside the typography value instead of remaining free-floating numbers (see the DTCG number type). |
Colour tokens
DTCG color token
{
"overlay": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0, 0, 0],
"alpha": 0.5,
"hex": "#000000"
}
}
}
DTIF color token (examples/dtcg-migration/color.tokens.json
)
{
"$version": "1.0.0",
"color": {
"overlay": {
"$type": "color",
"$description": "Translucent overlay with legacy hex fallback",
"$value": {
"colorSpace": "srgb",
"components": [0.0, 0.0, 0.0, 0.5],
"hex": "#00000080"
}
}
}
}
Move the alpha
channel into the DTIF components
array and reuse the native hex
field—plus the optional alpha
number when the fallback omits embedded transparency— to preserve CSS-oriented serialisations without $extensions
.
Dimension tokens
DTCG dimensions
{
"spacing-medium": {
"$type": "dimension",
"$value": { "value": 16, "unit": "px" }
},
"radius-pill": {
"$type": "dimension",
"$value": { "value": 1.5, "unit": "rem" }
}
}
DTIF dimensions (examples/dtcg-migration/dimension.tokens.json
)
{
"$version": "1.0.0",
"dimension": {
"spacing-medium": {
"$type": "dimension",
"$value": {
"dimensionType": "length",
"value": 16,
"unit": "px"
}
},
"radius-pill": {
"$type": "dimension",
"$value": {
"dimensionType": "length",
"value": 1.5,
"unit": "rem"
}
}
}
}
Add dimensionType
to distinguish angles, resolutions, and platform-specific units while keeping the numeric payloads intact.
Temporal tokens
DTCG temporal tokens
{
"duration": {
"button-press": {
"$type": "duration",
"$value": { "value": 120, "unit": "ms" }
},
"button-delay": {
"$type": "duration",
"$value": { "value": 40, "unit": "ms" }
}
},
"easing": {
"standard": {
"$type": "cubicBezier",
"$value": [0.4, 0.0, 0.2, 1.0]
}
}
}
DTIF temporal tokens (examples/dtcg-migration/duration-easing.tokens.json
)
{
"$version": "1.0.0",
"duration": {
"button-press": {
"$type": "duration",
"$value": {
"durationType": "css.transition-duration",
"value": 120,
"unit": "ms"
}
},
"button-delay": {
"$type": "duration",
"$value": {
"durationType": "css.transition-delay",
"value": 40,
"unit": "ms"
}
}
},
"easing": {
"standard": {
"$type": "easing",
"$value": {
"easingFunction": "cubic-bezier",
"parameters": [0.4, 0.0, 0.2, 1.0]
}
}
}
}
Wrap each duration with a durationType
identifier and promote Bézier arrays to reusable easing
tokens.
Validate easing grammars
DTIF validates each easing function against the grammar defined in Token types
:
cubic-bezier
values must provide exactly four numbers. The first and third control points are clamped to the[0, 1]
domain so CSS, UIKit, and Android renderers stay in sync.steps
easings require a positive integer step count and, when the optional second argument is present, it must be one of the CSS<step-position>
keywords such as"start"
,"jump-end"
, or"jump-both"
.- Spring identifiers like
spring
,ios.spring
, orandroid.spring-force
demand four numeric parameters: the first three are positive magnitudes published byUISpringTimingParameters
andSpringForce
, and the final value is the initial velocity, which may be any real number.
{
"easing": {
"stagger": {
"$type": "easing",
"$value": { "easingFunction": "steps", "parameters": [4, "jump-end"] }
},
"spring": {
"$type": "easing",
"$value": { "easingFunction": "ios.spring", "parameters": [0.9, 12, 1.2, -0.4] }
}
}
}
Adjust any DTCG exports that rely on looser validation so they satisfy these grammars before importing them into DTIF.
Fonts and typography
DTCG typography
{
"fontFamily": {
"body": { "$type": "fontFamily", "$value": ["Inter", "Arial", "sans-serif"] }
},
"fontWeight": {
"regular": { "$type": "fontWeight", "$value": 400 }
},
"typography": {
"body": {
"$type": "typography",
"$value": {
"fontFamily": "{fontFamily.body}",
"fontWeight": "{fontWeight.regular}",
"fontSize": { "value": 16, "unit": "px" },
"letterSpacing": { "value": 0.5, "unit": "px" },
"lineHeight": 1.5
}
}
}
}
DTIF typography (examples/dtcg-migration/typography.tokens.json
)
{
"$version": "1.0.0",
"font": {
"inter-regular": {
"$type": "font",
"$value": {
"fontType": "css.font-face",
"family": "Inter",
"fallbacks": ["Arial", "sans-serif"],
"style": "normal",
"weight": 400
}
}
},
"dimension": {
"shared": {
"body-size": {
"$type": "dimension",
"$value": {
"dimensionType": "length",
"value": 16,
"unit": "px"
}
},
"body-letter-spacing": {
"$type": "dimension",
"$value": {
"dimensionType": "length",
"value": 0.5,
"unit": "px"
}
},
"body-line-height": {
"$type": "dimension",
"$value": {
"dimensionType": "length",
"value": 24,
"unit": "px"
}
}
},
"typography": {
"body-size": {
"$type": "dimension",
"$ref": "#/dimension/shared/body-size"
},
"body-letter-spacing": {
"$type": "dimension",
"$ref": "#/dimension/shared/body-letter-spacing"
},
"body-line-height": {
"$type": "dimension",
"$ref": "#/dimension/shared/body-line-height"
}
}
},
"typography": {
"body": {
"$type": "typography",
"$value": {
"fontFamily": "Inter",
"fontWeight": 400,
"fontSize": { "$ref": "#/dimension/typography/body-size" },
"letterSpacing": { "$ref": "#/dimension/typography/body-letter-spacing" },
"lineHeight": { "$ref": "#/dimension/typography/body-line-height" }
}
}
}
}
Consolidate font metadata into a reusable font
token, encode fallback stacks with the fallbacks
property, and promote repeated measurements to standalone dimension
tokens. Reference those measurements from typography
tokens via $ref
. Alias tokens can forward to other dimension tokens, so nested pointers keep shared measurements in one place while the typography composite stays well-typed.
DTIF also registers standalone line-height
tokens for reusing baseline spacing outside composite typography. DTCG exports that serialise line heights as strings such as "120%"
must be normalised into a unitless ratio (for example 1.2
) or a font-dimension
object during migration. The schema rejects other value types so that line-height tokens resolve predictably on every platform and alias tokens continue to declare matching $type
metadata.
DTIF likewise narrows the accepted keywords for wordSpacing
. CSS allows values such as normal
, wide
, and narrow
, and some DTCG pipelines emit those strings directly. DTIF keeps only the normal
keyword; all other spacing adjustments must be converted into explicit font-dimension
measurements or references to dimension
tokens whose dimensionType
is "length"
. This ensures word spacing imports express concrete distances instead of relying on loosely defined user-agent heuristics.
DTIF additionally validates every font family string using the CSS <family-name>
grammar described in Token types §font and Typography §font-face. Trim leading and trailing whitespace, quote identifiers that start with digits, and clean any DTCG $extensions
payloads that embed font metadata so the values you copy into DTIF font
, fontFace
, or typography
tokens already match the CSS naming rules.
Text decoration and transform strings
The schema now validates the textDecoration
and textTransform
strings that DTIF stores on typography
values against the CSS grammar. DTCG tokens do not define those members, so many pipelines stash decoration metadata in vendor $extensions
as free-form descriptions such as "underline dashed red"
or "capitalize smallcaps"
. Normalise that metadata before copying it into DTIF so tooling can translate the results directly into CSS, UIKit, and Android APIs without guessing the author’s intent.
- Decoration components. Supply recognised line keywords (
none
,underline
,overline
,line-through
,spelling-error
,grammar-error
), optional<line-style>
keywords (solid
,dotted
,dashed
,double
,wavy
), colour values (CSS named colours, system colours,#rrggbb
,color(...)
, orvar(...)
), and thickness tokens (thin
,medium
,thick
,from-font
,auto
, or<length-percentage>
). Remove informal descriptors such as"bevelled"
and normalise vendor strings to the CSS keyword set. - Transform lists. Provide whitespace-separated keywords such as
none
,capitalize
,uppercase
,lowercase
,full-width
,full-size-kana
, ormath-auto
. When a custom property drives the value, wrap it invar(...)
("var(--brand-transform)"
) instead of serialising plain text.
DTCG export with loose decoration metadata stored in $extensions
:
{
"fontFamily": {
"accent": {
"$type": "fontFamily",
"$value": ["Inter", "Arial", "sans-serif"]
}
},
"typography": {
"cta": {
"$type": "typography",
"$value": {
"fontFamily": "{fontFamily.accent}",
"fontWeight": 600,
"fontSize": { "value": 16, "unit": "px" },
"letterSpacing": { "value": 0, "unit": "px" },
"lineHeight": 1.2
},
"$extensions": {
"com.example.export": {
"textDecoration": "underline bevelled 2",
"textTransform": "capitalize smallcaps"
}
}
}
}
}
DTIF conversion honouring the CSS grammar:
{
"typography": {
"cta": {
"$type": "typography",
"$value": {
"fontFamily": "Inter",
"fontWeight": 600,
"fontSize": { "dimensionType": "length", "value": 16, "unit": "px" },
"letterSpacing": { "dimensionType": "length", "value": 0, "unit": "px" },
"lineHeight": 1.2,
"textDecoration": "underline color-mix(in srgb, #ff6600 40%, white) from-font",
"textTransform": "capitalize full-size-kana"
}
}
}
}
Normalise DTCG strings before validation by lower-casing the CSS keywords, ensuring named colours map to the CSS colour registry, and providing explicit units for thickness measurements ("2px"
instead of "2"
). DTIF accepts system colour keywords (AccentColor
, CanvasText
) and var()
references so existing theme hooks continue to work once their syntax matches the CSS specifications referenced above.
DTCG export with loose font family strings in $extensions
:
{
"fontFamily": {
"accent": {
"$type": "fontFamily",
"$value": [" 1 Example", "Arial ", "sans-serif"],
"$extensions": {
"com.example.export": {
"fontFace": {
"fontFamily": " 1 Example ",
"src": [
{ "local": " 1 Example " },
{ "url": "fonts/Accent Regular.woff2", "format": "woff2" }
]
}
}
}
}
}
}
DTIF conversion quoting the name and encoding the URL:
{
"font": {
"accent": {
"$type": "font",
"$value": {
"family": "\"1 Example\"",
"fallbacks": ["Arial", "sans-serif"]
}
}
},
"fontFace": {
"accent": {
"$type": "fontFace",
"$value": {
"fontFamily": "\"1 Example\"",
"src": [
{ "local": "\"1 Example\"" },
{ "url": "fonts/Accent%20Regular.woff2", "format": "woff2" }
]
}
}
}
}
In addition to quoting the family, percent-encode any whitespace inside fontFace.$value.src[].url
strings so they remain valid uri-reference
values. DTIF rejects URLs containing raw spaces or other illegal characters, so update the export pipeline to emit encoded paths before validating the converted document.
Convert composite tokens
DTCG defines several composite types that combine multiple primitives. DTIF provides richer schemas for these structures.
Borders and stroke styles
DTCG border
{
"color": {
"focus": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.0, 0.435, 1.0],
"hex": "#006FFF"
}
}
},
"dimension": {
"focus-outline-width": {
"$type": "dimension",
"$value": { "value": 2, "unit": "px" }
}
},
"strokeStyle": {
"focus": {
"$type": "strokeStyle",
"$value": {
"dashArray": [
{ "value": 4, "unit": "px" },
{ "value": 2, "unit": "px" }
],
"lineCap": "round"
}
}
},
"border": {
"focus-outline": {
"$type": "border",
"$value": {
"color": "{color.focus}",
"style": "solid",
"width": "{dimension.focus-outline-width}"
}
}
}
}
DTIF border (examples/dtcg-migration/border.tokens.json
)
{
"$version": "1.0.0",
"color": {
"focus-outline": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.0, 0.435, 1.0, 1.0]
}
}
},
"dimension": {
"focus-outline-width": {
"$type": "dimension",
"$value": {
"dimensionType": "length",
"value": 2,
"unit": "px"
}
}
},
"strokeStyle": {
"focus": {
"$type": "strokeStyle",
"$value": {
"dashArray": [
{
"dimensionType": "length",
"value": 4,
"unit": "px"
},
{
"dimensionType": "length",
"value": 2,
"unit": "px"
}
],
"lineCap": "round"
}
}
},
"border": {
"focus-outline": {
"$type": "border",
"$value": {
"borderType": "css.border",
"color": { "$ref": "#/color/focus-outline" },
"style": "solid",
"strokeStyle": { "$ref": "#/strokeStyle/focus" },
"width": { "$ref": "#/dimension/focus-outline-width" }
}
}
}
}
The shared color
and dimension
entries become standalone DTIF tokens that the border references through $ref
pointers, preserving the original DTCG aliases while exposing richer metadata.
DTIF border
tokens capture the rendering context through borderType
, reuse stroke metadata via a first-class strokeStyle
token, and avoid vendor extensions for dash patterns.
When migrating custom border styles, rewrite proprietary keywords to the CSS <line-style>
values (none
, hidden
, dotted
, dashed
, solid
, double
, groove
, ridge
, inset
, or outset
). DTIF rejects unrecognised styles so normalising them up front keeps validation noise-free.
Shadows
DTCG shadow
{
"color": {
"shadow": {
"ambient": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.0, 0.0, 0.0],
"alpha": 0.2,
"hex": "#000000"
}
},
"key": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.0, 0.0, 0.0],
"alpha": 0.302,
"hex": "#000000"
}
}
}
},
"dimension": {
"shadow": {
"offset-large": {
"$type": "dimension",
"$value": { "value": 2, "unit": "px" }
},
"offset-small": {
"$type": "dimension",
"$value": { "value": 1, "unit": "px" }
},
"blur-large": {
"$type": "dimension",
"$value": { "value": 6, "unit": "px" }
},
"blur-small": {
"$type": "dimension",
"$value": { "value": 3, "unit": "px" }
},
"spread": {
"$type": "dimension",
"$value": { "value": 0, "unit": "px" }
}
}
},
"shadow": {
"button-ambient": {
"$type": "shadow",
"$value": [
{
"color": "{color.shadow.ambient}",
"offsetX": "{dimension.shadow.spread}",
"offsetY": "{dimension.shadow.offset-large}",
"blur": "{dimension.shadow.blur-large}",
"spread": "{dimension.shadow.spread}"
},
{
"color": "{color.shadow.key}",
"offsetX": "{dimension.shadow.spread}",
"offsetY": "{dimension.shadow.offset-small}",
"blur": "{dimension.shadow.blur-small}",
"spread": "{dimension.shadow.spread}"
}
]
}
}
}
DTIF shadow (examples/dtcg-migration/shadow.tokens.json
)
{
"$version": "1.0.0",
"color": {
"shadow-ambient": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.0, 0.0, 0.0, 0.2]
}
},
"shadow-key": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.0, 0.0, 0.0, 0.3]
}
}
},
"dimension": {
"shadow-offset-large": {
"$type": "dimension",
"$value": {
"dimensionType": "length",
"value": 2,
"unit": "px"
}
},
"shadow-offset-small": {
"$type": "dimension",
"$value": {
"dimensionType": "length",
"value": 1,
"unit": "px"
}
},
"shadow-blur-large": {
"$type": "dimension",
"$value": {
"dimensionType": "length",
"value": 6,
"unit": "px"
}
},
"shadow-blur-small": {
"$type": "dimension",
"$value": {
"dimensionType": "length",
"value": 3,
"unit": "px"
}
},
"shadow-spread": {
"$type": "dimension",
"$value": {
"dimensionType": "length",
"value": 0,
"unit": "px"
}
}
},
"shadow": {
"button-ambient": {
"$type": "shadow",
"$value": [
{
"shadowType": "css.box-shadow",
"offsetX": { "$ref": "#/dimension/shadow-spread" },
"offsetY": { "$ref": "#/dimension/shadow-offset-large" },
"blur": { "$ref": "#/dimension/shadow-blur-large" },
"spread": { "$ref": "#/dimension/shadow-spread" },
"color": { "$ref": "#/color/shadow-ambient" }
},
{
"shadowType": "css.box-shadow",
"offsetX": { "$ref": "#/dimension/shadow-spread" },
"offsetY": { "$ref": "#/dimension/shadow-offset-small" },
"blur": { "$ref": "#/dimension/shadow-blur-small" },
"spread": { "$ref": "#/dimension/shadow-spread" },
"color": { "$ref": "#/color/shadow-key" }
}
]
}
}
}
Ensure every blur
entry remains zero or positive. CSS <shadow>
grammar and DTIF's schema both disallow negative blur radii, so adjust any exports that used sentinel values before validating the converted document.
Shared colour and dimension primitives become first-class DTIF tokens that each shadow layer references via $ref
, preserving the alias relationships used in the source DTCG document.
Convert each DTCG layer into a DTIF shadow object with an explicit shadowType
and dimension wrappers. Tokens with multiple layers continue to use arrays whose ordering matches the original DTCG payload.
Gradients
DTCG gradient
{
"color": {
"hero": {
"start": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [1.0, 0.541, 0.0],
"hex": "#FF8A00"
}
},
"end": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.929, 0.098, 0.792],
"hex": "#ED19CA"
}
}
}
},
"gradient": {
"hero-background": {
"$type": "gradient",
"$value": [
{ "color": "{color.hero.start}", "position": 0 },
{ "color": "{color.hero.end}", "position": 1 }
]
}
}
}
DTIF gradient (examples/dtcg-migration/gradient.tokens.json
)
{
"$version": "1.0.0",
"color": {
"hero-start": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [1.0, 0.541, 0.0, 1.0]
}
},
"hero-end": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.929, 0.098, 0.792, 1.0]
}
}
},
"gradient": {
"hero-background": {
"$type": "gradient",
"$value": {
"gradientType": "linear",
"angle": "to top right",
"stops": [
{ "position": "0%", "color": { "$ref": "#/color/hero-start" } },
{ "position": "100%", "color": { "$ref": "#/color/hero-end" } }
]
}
}
}
}
Convert fractional stop positions to percentage strings, add an explicit gradientType
(linear
, radial
, or conic
) and CSS-formatted angle
to describe the orientation, and keep the palette tokens as dedicated color
entries that gradient stops reference through $ref
so palette aliases survive the migration.
- Angles must include CSS units or keywords. DTCG exports frequently write bare numbers such as
45
or180
for gradient orientations. DTIF rejects those values; convert them to valid CSS<angle>
strings such as45deg
,0.5turn
, or use theto top right
keyword syntax. - Centre strings follow CSS
<position>
. When DTCG serialises centre points as fractional pairs like0.5 0.25
, translate them into CSS<length-percentage>
values (50% 25%
) or wrap calculations incalc(...)
so that DTIF accepts the string. Identifiers such asmiddle
are not part of the grammar and will fail validation. - Hints stay single
<length-percentage>
tokens. DTCG gradients sometimes embed hint offsets as two-element arrays or space-separated pairs. DTIF follows the CSS<color-hint>
production, so flatten those values into a single token (for example"var(--midpoint)"
or"calc(25% + 2px)"
). Supplying two tokens in the same string now fails validation because it implies a second stop rather than a midpoint. - Radial shapes only accept
circle
orellipse
. Rename vendor-specific shape labels to the standard<rg-ending-shape>
keywords defined by CSS Images Module Level 3 before validating with the DTIF schema.
DTCG gradient metadata stored in $extensions
:
{
"color": {
"hero": {
"start": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [1.0, 0.541, 0.0],
"hex": "#FF8A00"
}
},
"end": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.929, 0.098, 0.792],
"hex": "#ED19CA"
}
}
}
},
"gradient": {
"hero-background": {
"$type": "gradient",
"$value": [
{ "color": "{color.hero.start}", "position": 0 },
{ "color": "{color.hero.end}", "position": 1 }
],
"$extensions": {
"com.example.export": {
"angle": 45,
"center": [0.5, 0.25],
"hint": [0.25, 0.75]
}
}
}
}
}
DTIF conversion with CSS-compliant strings:
{
"gradient": {
"hero-background": {
"$type": "gradient",
"$value": {
"gradientType": "linear",
"angle": "45deg",
"center": "50% 25%",
"hints": ["calc(25% + 2px)"],
"stops": [
{ "position": "0%", "color": { "$ref": "#/color/hero-start" } },
{ "position": "100%", "color": { "$ref": "#/color/hero-end" } }
]
}
}
}
}
Transitions and motion
DTCG transition
{
"transition": {
"button": {
"$type": "transition",
"$value": {
"duration": { "value": 120, "unit": "ms" },
"delay": { "value": 40, "unit": "ms" },
"timingFunction": "{easing.standard}"
},
"$extensions": {
"com.example.export": { "property": "transform" }
}
}
},
"easing": {
"standard": {
"$type": "cubicBezier",
"$value": [0.4, 0.0, 0.2, 1.0]
}
}
}
DTIF transition (examples/dtcg-migration/transition.tokens.json
)
{
"$version": "1.0.0",
"duration": {
"button-press": {
"$type": "duration",
"$value": {
"durationType": "css.transition-duration",
"value": 120,
"unit": "ms"
}
},
"button-delay": {
"$type": "duration",
"$value": {
"durationType": "css.transition-delay",
"value": 40,
"unit": "ms"
}
}
},
"easing": {
"standard": {
"$type": "easing",
"$value": {
"easingFunction": "cubic-bezier",
"parameters": [0.4, 0.0, 0.2, 1.0]
}
}
},
"motion": {
"button-transition": {
"$type": "motion",
"$value": {
"motionType": "css.transition",
"parameters": {
"property": "transform",
"duration": { "$ref": "#/duration/button-press" },
"delay": { "$ref": "#/duration/button-delay" },
"easing": { "$ref": "#/easing/standard" }
}
}
}
}
}
Break transition composites into reusable duration
and easing
tokens, then reference those tokens from a motion
descriptor that names the target property. Copy any property hints stored under DTCG $extensions
into the motion parameters so the animation applies to the intended CSS attribute.
Embrace DTIF features
Migrating is an opportunity to adopt DTIF-only capabilities:
- Functions inside
$value
. DTIF tokens may embed calculated values using function objects (for exampleclamp
orcalc
) with nested$ref
parameters to express responsive behaviour. - Component tokens. Group related primitives (for example a button’s background, border, and typography) into DTIF component tokens with named slots to improve reuse and theming.
- Conditional overrides. Encode platform or preference-specific variants through
$overrides
rather than maintaining separate ad-hoc documents.
Example: upgrading component structure
DTCG component grouping
{
"button": {
"$type": "color",
"background": {
"$value": {
"colorSpace": "srgb",
"components": [0.0, 0.435, 1.0],
"hex": "#006FFF"
}
},
"text": {
"$value": {
"colorSpace": "srgb",
"components": [1.0, 1.0, 1.0],
"hex": "#FFFFFF"
}
}
},
"spacing": {
"$type": "dimension",
"padding": { "$value": { "value": 16, "unit": "px" } }
}
}
DTIF component token (examples/dtcg-migration/component.tokens.json
)
{
"$version": "1.0.0",
"color": {
"button-background": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [0.0, 0.435, 1.0]
}
},
"button-text": {
"$type": "color",
"$value": {
"colorSpace": "srgb",
"components": [1.0, 1.0, 1.0, 1.0]
}
}
},
"dimension": {
"padding-min": {
"$type": "dimension",
"$value": {
"dimensionType": "length",
"value": 8,
"unit": "px"
}
},
"padding-max": {
"$type": "dimension",
"$value": {
"dimensionType": "length",
"value": 16,
"unit": "px"
}
}
},
"component": {
"button": {
"$type": "component",
"$value": {
"$slots": {
"background": {
"$type": "color",
"$ref": "#/color/button-background"
},
"text": {
"$type": "color",
"$ref": "#/color/button-text"
},
"padding": {
"$type": "dimension",
"$value": {
"fn": "clamp",
"parameters": [
{ "$ref": "#/dimension/padding-min" },
{
"dimensionType": "length",
"value": 2,
"unit": "vw"
},
{ "$ref": "#/dimension/padding-max" }
]
}
}
}
}
}
}
}
Component tokens gather related primitives behind named slots and allow $value
functions to express responsive behaviour that DTCG cannot model directly.
Validate and iterate
Run the schema. Validate each migrated file with the published schema or the validator package described in Getting started. Example using Ajv:
bashnpx --yes ajv-cli validate --spec=draft2020 --strict=true --data=true -c ajv-formats \ -s schema/core.json -d "examples/dtcg-migration/*.tokens.json"
Strict Ajv settings help catch remaining schema mismatches in migrated fixtures. Temporarily pass
--strict=false
only when auditing exports that still rely on legacy semantics.Adopt automated tests. Integrate
@lapidist/dtif-validator
in your CI pipeline to catch regressions and enforce pointer resolution.Document extensions. When you port proprietary extension data, follow the extension naming guidelines so collaborators can understand and validate the additional payloads.
Careful, incremental conversion—type by type and component by component—keeps migrated artifacts testable and unlocks the richer interoperability guarantees provided by DTIF.