Easing
Overview
The easing composables help you create consistent animation timing systems with minimal code. They generate easing variables that can be easily referenced throughout your application, enabling flexible theming and consistent motion design.
Why use easing composables?
Easing composables help you:
- Centralize timing functions: Define all your easing curves in one place for easy management.
- Enable flexible theming: Override easing values to instantly update animations across your application.
- Maintain consistency: Use semantic names to ensure consistent motion throughout your design system.
- Access advanced easings: Use spring and bounce effects with the CSS
linear()function.
useEasing
The useEasing() function creates a set of easing variables from a simple object of easing value definitions. It includes comprehensive defaults covering CSS keywords, cubic-bezier curves, and linear() functions for spring and bounce effects.
import { styleframe } from 'styleframe';
import { useEasing } from '@styleframe/theme';
const s = styleframe();
const {
easing,
easingEaseInOut,
easingEaseOutCubic,
easingSpring,
easingBounce,
} = useEasing(s, {
default: '@ease-out-cubic',
'ease-in-out': 'ease-in-out',
'ease-out-cubic': 'cubic-bezier(0.215, 0.61, 0.355, 1)',
spring: 'linear(0, 0.0018, 0.0069 1.15%, ...)',
bounce: 'linear(0, 0.004, 0.016, ...)',
} as const);
export default s;
:root {
--easing--ease-in-out: ease-in-out;
--easing--ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);
--easing--spring: linear(0, 0.0018, 0.0069 1.15%, ...);
--easing--bounce: linear(0, 0.004, 0.016, ...);
--easing: var(--easing--ease-out-cubic);
}
Each key in the object becomes an easing variable with the prefix easing, and the export name is automatically converted to camelCase (e.g., default → easing, ease-out-cubic → easingEaseOutCubic).
default key for your primary easing. It will create a variable named --easing without any suffix, making it the natural choice for standard transitions throughout your application.Default Values
The useEasing() composable comes with comprehensive defaults. You can use them directly without passing any arguments:
import { styleframe } from 'styleframe';
import { useEasing } from '@styleframe/theme';
const s = styleframe();
// Use all defaults
const {
easingLinear,
easingEase,
easingEaseIn,
easingEaseOut,
easingEaseInOut,
easingEaseInSine,
easingEaseOutCubic,
easingSpring,
easingBounce,
// ... and many more
} = useEasing(s);
export default s;
Basic CSS Keywords
| Name | Value |
|---|---|
linear | linear |
ease | ease |
ease-in | ease-in |
ease-out | ease-out |
ease-in-out | ease-in-out |
Cubic-Bezier Easings
Based on easings.net, these provide mathematically precise timing curves:
| Family | Ease In | Ease Out | Ease In-Out |
|---|---|---|---|
| Sine | cubic-bezier(0.47, 0, 0.745, 0.715) | cubic-bezier(0.39, 0.575, 0.565, 1) | cubic-bezier(0.445, 0.05, 0.55, 0.95) |
| Quad | cubic-bezier(0.55, 0.085, 0.68, 0.53) | cubic-bezier(0.25, 0.46, 0.45, 0.94) | cubic-bezier(0.455, 0.03, 0.515, 0.955) |
| Cubic | cubic-bezier(0.55, 0.055, 0.675, 0.19) | cubic-bezier(0.215, 0.61, 0.355, 1) | cubic-bezier(0.645, 0.045, 0.355, 1) |
| Quart | cubic-bezier(0.895, 0.03, 0.685, 0.22) | cubic-bezier(0.165, 0.84, 0.44, 1) | cubic-bezier(0.77, 0, 0.175, 1) |
| Quint | cubic-bezier(0.755, 0.05, 0.855, 0.06) | cubic-bezier(0.23, 1, 0.32, 1) | cubic-bezier(0.86, 0, 0.07, 1) |
| Expo | cubic-bezier(0.95, 0.05, 0.795, 0.035) | cubic-bezier(0.19, 1, 0.22, 1) | cubic-bezier(1, 0, 0, 1) |
| Circ | cubic-bezier(0.6, 0.04, 0.98, 0.335) | cubic-bezier(0.075, 0.82, 0.165, 1) | cubic-bezier(0.785, 0.135, 0.15, 0.86) |
| Back | cubic-bezier(0.6, -0.28, 0.735, 0.045) | cubic-bezier(0.175, 0.885, 0.32, 1.275) | cubic-bezier(0.68, -0.55, 0.265, 1.55) |
Spring and Bounce
Based on Josh Comeau's research, these use the CSS linear() function for physics-based animations:
| Name | Description |
|---|---|
spring | Simulates a spring with overshoot and settling |
bounce | Simulates a bouncing effect like a ball hitting the ground |
linear() function is supported in Chrome/Edge 113+, Firefox 112+, and Safari 17.2+. For older browsers, consider providing a cubic-bezier() fallback.Using Easing Variables
Once created, easing variables can be used anywhere in your styles:
import { styleframe } from 'styleframe';
import { useEasing } from '@styleframe/theme';
const s = styleframe();
const { ref, selector, css } = s;
const { easing, easingEaseOutCubic, easingSpring } = useEasing(s, {
default: '@ease-out-cubic',
'ease-out-cubic': 'cubic-bezier(0.215, 0.61, 0.355, 1)',
spring: 'linear(0, 0.0018, 0.0069 1.15%, 0.026 2.3%, 0.0637, 0.1135 5.18%, 0.2229 7.78%, 0.5977 15.84%, 0.7014, 0.7904, 0.8641, 0.9228, 0.9676 28.8%, 1.0032 31.68%, 1.0225, 1.0352 36.29%, 1.0431 38.88%, 1.046 42.05%, 1.0448 44.35%, 1.0407 47.23%, 1.0118 61.63%, 1.0025 69.41%, 0.9981 80.35%, 0.9992 99.94%)',
} as const);
selector('.button', {
transition: css`transform 0.2s ${ref(easing)}`,
});
selector('.modal', {
transition: css`opacity 0.3s ${ref(easingEaseOutCubic)}, transform 0.5s ${ref(easingSpring)}`,
});
export default s;
:root {
--easing--ease-out-cubic: cubic-bezier(0.215, 0.61, 0.355, 1);
--easing--spring: linear(0, 0.0018, 0.0069 1.15%, ...);
--easing: var(--easing--ease-out-cubic);
}
.button {
transition: transform 0.2s var(--easing);
}
.modal {
transition: opacity 0.3s var(--easing--ease-out-cubic), transform 0.5s var(--easing--spring);
}
Examples
Semantic Easing Names
Use semantic names to make easing intent clear:
import { styleframe } from 'styleframe';
import { useEasing, defaultEasingValues } from '@styleframe/theme';
const s = styleframe();
const {
easing,
easingEnter,
easingExit,
easingEmphasize,
} = useEasing(s, {
default: '@enter',
enter: defaultEasingValues['ease-out-cubic'],
exit: defaultEasingValues['ease-in-cubic'],
emphasize: defaultEasingValues.spring,
} as const);
export default s;
:root {
--easing--enter: cubic-bezier(0.215, 0.61, 0.355, 1);
--easing--exit: cubic-bezier(0.55, 0.055, 0.675, 0.19);
--easing--emphasize: linear(0, 0.0018, ...);
--easing: var(--easing--enter);
}
Animation with Keyframes
Combine easing with keyframes for complex animations:
import { styleframe } from 'styleframe';
import { useEasing } from '@styleframe/theme';
const s = styleframe();
const { ref, selector, keyframes, css } = s;
const { easingBounce, easingSpring } = useEasing(s);
const fadeIn = keyframes('fade-in', {
'0%': { opacity: 0, transform: 'translateY(-20px)' },
'100%': { opacity: 1, transform: 'translateY(0)' },
});
selector('.notification', {
animation: css`${fadeIn} 0.5s ${ref(easingBounce)} forwards`,
});
selector('.tooltip', {
animation: css`${fadeIn} 0.3s ${ref(easingSpring)} forwards`,
});
export default s;
:root {
--easing--bounce: linear(0, 0.004, 0.016, ...);
--easing--spring: linear(0, 0.0018, ...);
}
@keyframes fade-in {
0% { opacity: 0; transform: translateY(-20px); }
100% { opacity: 1; transform: translateY(0); }
}
.notification {
animation: fade-in 0.5s var(--easing--bounce) forwards;
}
.tooltip {
animation: fade-in 0.3s var(--easing--spring) forwards;
}
Best Practices
- Start with sensible defaults: Use
ease-outfor entrances andease-infor exits. - Use the
defaultkey: This creates a clean--easingvariable that's perfect for general-purpose animations. - Choose appropriate curves: Use
ease-out-cubicorease-out-quartfor UI interactions, and reservespringandbouncefor emphasis. - Consider performance: Complex
linear()functions with many stops may impact performance on low-end devices. - Provide fallbacks: For browsers that don't support
linear(), consider providingcubic-bezier()fallbacks. - Keep it subtle: Most UI animations should be quick (150-300ms) with gentle easing.
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
Use spring easing for UI elements that should feel responsive and natural, like modals, tooltips, or interactive components. Spring has a subtle overshoot that adds personality without being distracting.
Use bounce easing for more playful effects, like notifications, badges, or celebratory animations. Bounce creates a more pronounced bouncing effect that draws attention.
Both create a decelerating animation, but quart is more dramatic than cubic:
ease-out-cubic(power of 3): Smooth, professional feel - good for most UI transitionsease-out-quart(power of 4): More pronounced deceleration - good for larger movements
The higher the power (quint, expo), the more extreme the effect.
For the linear() function (spring/bounce), you can use @supports or define a fallback:
selector('.element', {
// Fallback for older browsers
transitionTimingFunction: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
});
selector('.element', {
// Modern browsers with linear() support
'@supports (animation-timing-function: linear(0, 1))': {
transitionTimingFunction: ref(easingSpring),
},
});
ease, ease-in-out) are fine for simple transitions, but custom cubic-bezier() curves give you more control and can make your UI feel more polished. For most production apps, we recommend using at least ease-out-cubic for entrances and ease-in-cubic for exits.Easing is only part of the equation - duration matters too:
- Micro-interactions (hover, focus): 100-150ms
- UI transitions (modals, dropdowns): 200-300ms
- Page transitions: 300-500ms
- Spring/bounce effects: 400-600ms (they need time to settle)
Faster isn't always better - make sure animations are perceivable but not sluggish.
Colors
Create and manage color design tokens with automatic, easily configurable variants, tints, and shades using the oklch color space.
Overview
Create fluid, responsive designs that scale smoothly across all viewport sizes using mathematical precision. Based on the Utopia fluid type scale for optimal readability without breakpoints.