| name | motion-tokens |
| description | Encoding duration, easing, and stagger as DTCG tokens and delivering them to Compose, SwiftUI, Flutter, and React Native. Use this when introducing or refactoring motion tokens. |
Motion Tokens
Instructions
Motion belongs in the token system. A component should no more embed 250ms than it should embed #4F46E5.
1. DTCG Types for Motion
DTCG ships duration and cubicBezier. Compose motion via transition groups.
{
"motion": {
"duration": {
"$type": "duration",
"xs": { "$value": "80ms" },
"sm": { "$value": "150ms" },
"md": { "$value": "250ms" },
"lg": { "$value": "400ms" }
},
"easing": {
"$type": "cubicBezier",
"standard": { "$value": [0.2, 0.0, 0.0, 1.0] },
"accelerate": { "$value": [0.3, 0.0, 1.0, 1.0] },
"decelerate": { "$value": [0.0, 0.0, 0.0, 1.0] },
"emphasized": { "$value": [0.2, 0.0, 0.0, 1.0] }
}
}
}
2. Semantic Motion Aliases
Components consume intent-bearing aliases, not raw motion.duration.md.
{
"motion": {
"transition": {
"stateChange": {
"$type": "transition",
"$value": {
"duration": "{motion.duration.sm}",
"timingFunction": "{motion.easing.standard}",
"delay": "0ms"
}
},
"surfaceEnter": {
"$type": "transition",
"$value": {
"duration": "{motion.duration.md}",
"timingFunction": "{motion.easing.decelerate}",
"delay": "0ms"
}
},
"surfaceExit": {
"$type": "transition",
"$value": {
"duration": "{motion.duration.sm}",
"timingFunction": "{motion.easing.accelerate}",
"delay": "0ms"
}
},
"stagger": {
"$type": "duration",
"$value": "30ms"
}
}
}
}
3. Style Dictionary Transforms
Style Dictionary does not ship duration / cubicBezier transforms out of the box for native platforms. Add small custom transforms.
const StyleDictionary = require('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: 'cubicBezier/array',
type: 'value',
matcher: t => t.$type === 'cubicBezier',
transformer: t => t.$value,
});
StyleDictionary.registerTransformGroup({
name: 'compose-motion',
transforms: ['attribute/cti', 'name/cti/pascal', 'duration/ms-to-number', 'cubicBezier/array'],
});
4. Compose Delivery
object Motion {
object Duration {
const val Xs = 80
const val Sm = 150
const val Md = 250
const val Lg = 400
}
val EasingStandard = CubicBezierEasing(0.2f, 0.0f, 0.0f, 1.0f)
val EasingDecelerate = CubicBezierEasing(0.0f, 0.0f, 0.0f, 1.0f)
val EasingAccelerate = CubicBezierEasing(0.3f, 0.0f, 1.0f, 1.0f)
}
val SurfaceEnter = tween<Float>(Motion.Duration.Md, easing = Motion.EasingDecelerate)
5. SwiftUI Delivery
public enum DSMotionDuration { public static let xs = 0.08, sm = 0.15, md = 0.25, lg = 0.4 }
public enum DSMotionEasing {
public static let standard = Animation.timingCurve(0.2, 0.0, 0.0, 1.0, duration: DSMotionDuration.md)
public static let decelerate = Animation.timingCurve(0.0, 0.0, 0.0, 1.0, duration: DSMotionDuration.md)
public static let accelerate = Animation.timingCurve(0.3, 0.0, 1.0, 1.0, duration: DSMotionDuration.sm)
}
6. Flutter Delivery
class AppMotion extends ThemeExtension<AppMotion> {
final Duration surfaceEnter;
final Duration surfaceExit;
final Curve standard;
final Curve decelerate;
const AppMotion({
required this.surfaceEnter, required this.surfaceExit,
required this.standard, required this.decelerate,
});
static const standard = AppMotion(
surfaceEnter: Duration(milliseconds: 250),
surfaceExit: Duration(milliseconds: 150),
standard: Cubic(0.2, 0.0, 0.0, 1.0),
decelerate: Cubic(0.0, 0.0, 0.0, 1.0),
);
@override AppMotion copyWith(...) => ...;
@override AppMotion lerp(ThemeExtension<AppMotion>? other, double t) => this;
}
7. React Native Delivery
export const motion = {
duration: { xs: 80, sm: 150, md: 250, lg: 400 },
easing: {
standard: Easing.bezier(0.2, 0.0, 0.0, 1.0),
decelerate: Easing.bezier(0.0, 0.0, 0.0, 1.0),
accelerate: Easing.bezier(0.3, 0.0, 1.0, 1.0),
},
} as const;
8. Reduced-Motion Variant
Ship a second token file motion.reduced.json where every duration resolves to 0ms. The platform provider picks the right file at runtime based on the accessibility flag.
9. Anti-Patterns
tween(300) inline in a component.
- Using CSS-string easings on native platforms instead of cubic-bezier arrays.
- Exposing reference durations (
motion.duration.md) as the component-facing API.
- Hardcoding stagger in component source — tokenize it.
Checklist