| name | cross-platform-token-delivery |
| description | Delivering a single token set to Jetpack Compose, SwiftUI, Flutter, and React Native with type safety and no runtime cost. Use this when wiring the build output into consumer apps. |
Cross-Platform Token Delivery
Instructions
The build output of Style Dictionary is not the end — it is the start. Tokens must arrive in each platform as idiomatic, type-safe, zero-runtime values that components consume without ceremony.
1. Delivery Contract
Every platform artifact must satisfy:
- Type-safe: compiler catches typos (
DsTokens.Color.Brand40, not a string).
- Theme-aware: one import gives access to the current theme.
- Zero runtime lookup cost: tokens resolved at composition / view-build time, not per frame.
- Tree-shakeable: unused tokens do not inflate the binary (important for RN).
2. Compose / Kotlin
Ship a generated DsTokens object + a CompositionLocal-backed theme that exposes semantic tokens.
object DsTokens {
object Color {
val Brand40 = Color(0xFF524173)
val Neutral99 = Color(0xFFFFFBFF)
}
object Space { val Md: Dp = 16.dp }
}
data class AppColors(
val surfaceDefault: Color, val contentDefault: Color,
val actionPrimaryBg: Color, val actionPrimaryFg: Color,
)
val LocalAppColors = staticCompositionLocalOf<AppColors> { error("AppColors not provided") }
object DsTheme {
val colors: AppColors @Composable @ReadOnlyComposable get() = LocalAppColors.current
}
@Composable
fun AppTheme(dark: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) {
val colors = if (dark) DarkAppColors else LightAppColors
CompositionLocalProvider(LocalAppColors provides colors) {
MaterialTheme(colorScheme = colors.toColorScheme()) { content() }
}
}
Consumer:
Surface(color = DsTheme.colors.surfaceDefault) { }
3. SwiftUI / Swift
Ship a generated DSTokens enum plus EnvironmentKeys for the theme.
public enum DSTokens {
public enum Color { public static let brand40 = SwiftUI.Color(hex: 0x524173) }
public enum Space { public static let md: CGFloat = 16 }
}
public struct DSTheme {
public var colors: DSColors
public var spacing: DSSpacing
public var motion: DSMotion
public static let light = DSTheme(colors: .light, spacing: .standard, motion: .standard)
public static let dark = DSTheme(colors: .dark, spacing: .standard, motion: .standard)
}
private struct DSThemeKey: EnvironmentKey { static let defaultValue: DSTheme = .light }
public extension EnvironmentValues {
var dsTheme: DSTheme { get { self[DSThemeKey.self] } set { self[DSThemeKey.self] = newValue } }
}
Consumer:
struct Banner: View {
@Environment(\.dsTheme) var t
var body: some View {
Text("Hi").foregroundStyle(t.colors.contentDefault).padding(t.spacing.md)
}
}
Color sets can also live in an .xcassets catalog (Color("surface/default")) — prefer that for colors because it gets Any/Dark appearance for free. Use the Swift enum for spacing, radii, motion.
4. Flutter / Dart
Use ThemeExtension<T> so tokens flow through Theme.of(context).
// generated
class DsTokens {
static const Color brand40 = Color(0xFF524173);
static const double spaceMd = 16;
}
// hand-written
class AppColors extends ThemeExtension<AppColors> {
final Color surfaceDefault, contentDefault, actionPrimaryBg, actionPrimaryFg;
const AppColors({required this.surfaceDefault, required this.contentDefault,
required this.actionPrimaryBg, required this.actionPrimaryFg});
static const light = AppColors(
surfaceDefault: Color(0xFFFFFBFF),
contentDefault: Color(0xFF14101F),
actionPrimaryBg: DsTokens.brand40,
actionPrimaryFg: Color(0xFFFFFBFF),
);
static const dark = AppColors(/* ... */);
@override AppColors copyWith({Color? surfaceDefault, /* ... */}) => AppColors(/* ... */);
@override AppColors lerp(ThemeExtension<AppColors>? other, double t) => this;
}
ThemeData buildTheme({required bool dark}) => ThemeData(
useMaterial3: true,
colorScheme: dark ? darkColorScheme : lightColorScheme,
extensions: [dark ? AppColors.dark : AppColors.light]);
Consumer:
final colors = Theme.of(context).extension<AppColors>()!;
Container(color: colors.surfaceDefault);
5. React Native / TypeScript
Ship a typed theme object and a ThemeProvider. Use useTheme() or a StyleSheet factory; avoid inline object creation in render.
export const tokens = {
color: { brand40: '#524173', neutral99: '#FFFBFF' },
space: { md: 16 },
} as const;
export type Tokens = typeof tokens;
export type Theme = {
colors: { surfaceDefault: string; contentDefault: string;
actionPrimaryBg: string; actionPrimaryFg: string };
space: typeof tokens.space;
};
export const lightTheme: Theme = { colors: { }, space: tokens.space };
export const darkTheme: Theme = { colors: { }, space: tokens.space };
const ThemeCtx = createContext<Theme>(lightTheme);
export const ThemeProvider = ({ children }: { children: ReactNode }) => {
const scheme = useColorScheme();
const value = useMemo(() => (scheme === 'dark' ? darkTheme : lightTheme), [scheme]);
return <ThemeCtx.Provider value={value}>{children}</ThemeCtx.Provider>;
};
export const useTheme = () => useContext(ThemeCtx);
Consumer:
const t = useTheme();
const styles = useMemo(() => StyleSheet.create({
card: { backgroundColor: t.colors.surfaceDefault, padding: t.space.md },
}), [t]);
6. Distribution
Publish each platform artifact as a versioned package, same version across all four (see design-tokens-versioning):
- Android: Maven Central (
com.ds:ds-tokens:X.Y.Z).
- iOS: Swift Package at
https://github.com/org/ds-tokens-ios tagged vX.Y.Z.
- Flutter:
ds_tokens: ^X.Y.Z on pub.dev.
- RN:
@ds/tokens@X.Y.Z on the npm registry.
Consumer apps pin to caret ^X.Y.Z and upgrade via Renovate / Dependabot.
7. Cross-Platform Stories
Per component, produce the same rendered output on all four platforms for the same set of props. A CI job collects screenshots side-by-side into a review site. Parity is reviewed quarterly; drift files as a bug.
8. Anti-Patterns
- Stringly-typed tokens (
theme('colors.primary')).
- Creating new theme objects per render (
new Theme() in build).
- Shipping raw hex via Style Dictionary directly into components without the semantic layer.
- Mixing pre-compiled Compose tokens with a separate Kotlin color constants file.
- RN
StyleSheet.create({ color: theme.x }) inside render — breaks memoization; use factories.
Checklist