Duration
Overview
The duration composables help you create consistent animation and transition timing systems with minimal code. They generate duration variables that can be easily referenced throughout your application, enabling flexible theming and consistent motion design.
Why use duration composables?
Duration composables help you:
- Centralize timing values: Define all your animation durations in one place for easy management.
- Enable flexible theming: Override duration values to instantly update animation speed across your application.
- Maintain consistency: Use semantic names to ensure consistent timing throughout your design system.
- Eliminate magic numbers: Replace hardcoded millisecond values with meaningful token names.
useDuration
The useDuration() function creates a set of duration variables from a simple object of timing value definitions. It includes sensible defaults covering a full range from instant (0ms) through slowest (1000ms).
import { styleframe } from 'styleframe';
import { useDuration } from '@styleframe/theme';
const s = styleframe();
const {
duration,
durationFast,
durationNormal,
durationSlow,
} = useDuration(s, {
default: '@normal',
fast: '150ms',
normal: '250ms',
slow: '300ms',
} as const);
export default s;
:root {
--duration--fast: 150ms;
--duration--normal: 250ms;
--duration--slow: 300ms;
--duration: var(--duration--normal);
}
Each key in the object becomes a duration variable with the prefix duration, and the export name is automatically converted to camelCase (e.g., default → duration, fast → durationFast).
default key for your primary duration. It will create a variable named --duration without any suffix, making it the natural choice for standard transitions throughout your application.Default Values
The useDuration() composable comes with comprehensive defaults. You can use them directly without passing any arguments:
import { styleframe } from 'styleframe';
import { useDuration } from '@styleframe/theme';
const s = styleframe();
// Use all defaults
const {
durationInstant,
durationFastest,
durationFaster,
durationFast,
durationNormal,
durationSlow,
durationSlower,
durationSlowest,
} = useDuration(s);
export default s;
Duration Scale
| Name | Value | Use Case |
|---|---|---|
instant | 0ms | Immediate state changes, no animation |
fastest | 50ms | Micro-interactions (ripple effects) |
faster | 100ms | Hover and focus states |
fast | 150ms | Button presses, toggles |
normal | 250ms | Standard UI transitions (modals, dropdowns) |
slow | 300ms | Page transitions, complex reveals |
slower | 500ms | Spring and bounce animations |
slowest | 1000ms | Full-page animations, onboarding flows |
Using Duration Variables
Once created, duration variables can be used anywhere in your styles:
import { styleframe } from 'styleframe';
import { useDuration, useEasing } from '@styleframe/theme';
const s = styleframe();
const { ref, selector, css } = s;
const { durationFast, durationNormal } = useDuration(s, {
fast: '150ms',
normal: '250ms',
} as const);
const { easingEaseOutCubic } = useEasing(s, {
'ease-out-cubic': 'cubic-bezier(0.215, 0.61, 0.355, 1)',
} as const);
selector('.button', {
transition: css`background-color ${durationFast} ${easingEaseOutCubic}`,
});
selector('.modal', {
transition: css`opacity ${durationNormal} ${easingEaseOutCubic}, transform ${durationNormal} ${easingEaseOutCubic}`,
});
export default s;
:root {
--duration--fast: 150ms;
--duration--normal: 250ms;
--easing--ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);
}
.button {
transition: background-color var(--duration--fast) var(--easing--ease-out-cubic);
}
.modal {
transition: opacity var(--duration--normal) var(--easing--ease-out-cubic), transform var(--duration--normal) var(--easing--ease-out-cubic);
}
Examples
Semantic Duration Names
Use semantic names to make animation intent clear:
import { styleframe } from 'styleframe';
import { useDuration, durationValues } from '@styleframe/theme';
const s = styleframe();
const {
duration,
durationMicro,
durationUi,
durationPage,
} = useDuration(s, {
default: '@ui',
micro: durationValues.faster,
ui: durationValues.normal,
page: durationValues.slow,
} as const);
export default s;
:root {
--duration--micro: 100ms;
--duration--ui: 250ms;
--duration--page: 300ms;
--duration: var(--duration--ui);
}
Combined with Easing
Pair duration tokens with easing tokens for a complete motion system:
import { styleframe } from 'styleframe';
import { useDuration, useEasing } from '@styleframe/theme';
const s = styleframe();
const { selector, css } = s;
const { durationFast, durationNormal, durationSlower } = useDuration(s);
const { easingEaseOut, easingEaseInOut, easingSpring } = useEasing(s);
selector('.tooltip', {
transition: css`opacity ${durationFast} ${easingEaseOut}`,
});
selector('.dropdown', {
transition: css`transform ${durationNormal} ${easingEaseInOut}`,
});
selector('.notification', {
transition: css`transform ${durationSlower} ${easingSpring}`,
});
export default s;
:root {
--duration--fast: 150ms;
--duration--normal: 250ms;
--duration--slower: 500ms;
--easing--ease-out: ease-out;
--easing--ease-in-out: ease-in-out;
--easing--spring: linear(0, 0.0018, ...);
}
.tooltip {
transition: opacity var(--duration--fast) var(--easing--ease-out);
}
.dropdown {
transition: transform var(--duration--normal) var(--easing--ease-in-out);
}
.notification {
transition: transform var(--duration--slower) var(--easing--spring);
}
Best Practices
- Keep micro-interactions fast: Hover and focus effects should use
fastest(50ms) tofast(150ms) for responsive feedback. - Use the
defaultkey: This creates a clean--durationvariable that's perfect for general-purpose transitions. - Pair with easing tokens: Duration alone doesn't make great animations — combine with
useEasingfor polished motion. - Respect reduced motion: For users who prefer reduced motion, consider overriding durations to
0msusingprefers-reduced-motion. - Match duration to distance: Larger movements need longer durations. A tooltip fade needs less time than a full-page transition.
as const to ensure the object is treated as a constant type. This helps TypeScript infer the return type of the composables and provides better type safety and autocomplete support.FAQ
Follow these guidelines based on animation type:
- Micro-interactions (hover, focus, ripples): 50-150ms
- UI transitions (modals, dropdowns, tooltips): 200-300ms
- Page transitions: 300-500ms
- Spring/bounce effects: 400-600ms (they need time to settle)
When in doubt, start with normal (250ms) and adjust from there.
transition-duration and transition-delay utilities can reference duration tokens using the @ prefix (e.g., @fast).Use longer durations (500ms+) when:
- The animation covers a large distance on screen
- You're using spring or bounce easing that needs time to settle
- The animation is decorative and meant to be noticed (onboarding, celebrations)
- Multiple elements are animating in sequence (staggered animations)
Avoid long durations for interactive feedback — users expect immediate response to their actions.
Override duration tokens to 0ms using the prefers-reduced-motion media query:
selector('@media (prefers-reduced-motion: reduce)', {
'--duration': '0ms',
'--duration--fast': '0ms',
'--duration--normal': '0ms',
'--duration--slow': '0ms',
});
This instantly disables all animations that reference duration tokens.