| name | style-dictionary-pipeline |
| description | Building a Style Dictionary v4+ pipeline with platform transforms and CI publishing. Use this when setting up or evolving the token build. |
Style Dictionary Pipeline
Instructions
Style Dictionary is the canonical build for DTCG tokens. It takes JSON in, produces Kotlin, Swift, Dart, and TypeScript out, and runs on every PR. Configure it once, treat outputs as generated code.
1. Project Layout
design-system/
├── tokens/
│ ├── reference/**/*.json
│ ├── system/**/*.json
│ └── component/**/*.json
├── scripts/
│ ├── style-dictionary.config.mjs
│ └── transforms/
│ ├── duration.mjs
│ ├── cubic-bezier.mjs
│ └── typography.mjs
├── build/ # generated — gitignored
│ ├── android/
│ ├── ios/
│ ├── flutter/
│ └── rn/
└── package.json
2. Config (Style Dictionary v4+)
import StyleDictionary from 'style-dictionary';
import { registerTransforms as registerTokensStudio } from '@tokens-studio/sd-transforms';
import './transforms/duration.mjs';
import './transforms/cubic-bezier.mjs';
import './transforms/typography.mjs';
registerTokensStudio(StyleDictionary);
export default {
source: ['tokens/**/*.json'],
platforms: {
compose: {
transformGroup: 'compose',
transforms: ['attribute/cti', 'name/cti/pascal', 'color/composeColor',
'size/dp', 'size/sp', 'duration/ms-to-number', 'cubicBezier/array'],
buildPath: 'build/android/',
files: [{
destination: 'DsTokens.kt',
format: 'compose/object',
options: { packageName: 'com.ds.tokens', className: 'DsTokens' },
}],
},
ios: {
transformGroup: 'ios-swift',
transforms: ['attribute/cti', 'name/cti/camel', 'color/UIColorSwift',
'size/swift/remToCGFloat', 'duration/ms-to-double'],
buildPath: 'build/ios/',
files: [{ destination: 'DSTokens.swift', format: 'ios-swift/class.swift',
options: { className: 'DSTokens' } }],
},
flutter: {
transformGroup: 'flutter',
buildPath: 'build/flutter/',
files: [{ destination: 'ds_tokens.dart', format: 'flutter/class.dart',
options: { className: 'DsTokens' } }],
},
rn: {
transformGroup: 'js',
buildPath: 'build/rn/',
files: [
{ destination: 'tokens.ts', format: 'javascript/es6' },
{ destination: 'tokens.d.ts', format: 'typescript/es6-declarations' },
],
},
},
};
3. Custom Transforms
DTCG ships types Style Dictionary does not natively handle. Write small transforms and register them.
import StyleDictionary from 'style-dictionary';
StyleDictionary.registerTransform({
name: 'duration/ms-to-number',
type: 'value',
matcher: t => t.$type === 'duration',
transformer: t => parseInt(String(t.$value).replace('ms', ''), 10),
});
StyleDictionary.registerTransform({
name: 'duration/ms-to-double',
type: 'value',
matcher: t => t.$type === 'duration',
transformer: t => parseFloat(String(t.$value).replace('ms', '')) / 1000,
});
StyleDictionary.registerTransform({
name: 'cubicBezier/array',
type: 'value',
matcher: t => t.$type === 'cubicBezier',
transformer: t => t.$value,
});
4. Custom Formats
For composite types (typography, shadow), a custom format emits idiomatic platform code.
StyleDictionary.registerFormat({
name: 'compose/object',
formatter({ dictionary, options }) {
const { packageName, className } = options;
const lines = dictionary.allTokens.map(t =>
` val ${t.name}: ${composeType(t)} = ${composeLiteral(t)}`);
return `package ${packageName}\n\nobject ${className} {\n${lines.join('\n')}\n}\n`;
},
});
5. Validation Step
Before Style Dictionary builds, validate tokens:
- JSON schema against DTCG.
- Layer check: no
component/* token aliases a reference/* token.
- Contrast check on every paired
color.surface.* / color.content.*.
- Alias resolution check: no dangling
{...} references.
import { validate } from './validators/index.mjs';
await validate({ sources: ['tokens/**/*.json'] });
Fail the build on any violation.
6. Multi-Theme / Multi-Brand
Either run Style Dictionary N times (one per theme × brand) or use @tokens-studio/sd-transforms with sets/modes.
const combos = [
{ name: 'light', sets: ['reference', 'system.light'] },
{ name: 'dark', sets: ['reference', 'system.dark'] },
{ name: 'acme-light', sets: ['reference', 'brand/acme', 'system.light'] },
{ name: 'acme-dark', sets: ['reference', 'brand/acme', 'system.dark'] },
];
for (const combo of combos) {
await buildFor(combo);
}
Output to build/<combo>/<platform>/.
7. CI Publishing
- Run
pnpm build:tokens on every PR; fail on diff between committed and generated outputs.
- On release, publish packages per platform:
- Android: Maven Central artifact (
com.ds:ds-tokens:X.Y.Z) from build/android/.
- iOS: Swift Package from
build/ios/ — push a tag.
- Flutter: publish to
pub.dev or private pub from build/flutter/.
- RN: publish
@ds/tokens@X.Y.Z to the registry.
- Version is derived from Conventional Commits; token removals bump major automatically.
8. Anti-Patterns
- Manually editing
build/ — rewritten on next run.
- Running Style Dictionary per app rather than publishing packages — fans out config and drifts.
- One gigantic transform file per platform — write small, testable transforms.
- Skipping validation because "it builds" — broken aliases become runtime crashes.
Checklist