CSS Gradients, Shadows, and Easing: Visual Tokens That Perform
Build practical gradient and shadow design tokens, pick easing curves that feel right, and avoid paint-heavy effects that tank scroll performance.
Choose transitions vs @keyframes, pick units that scale, and build reusable motion snippets — with performance and accessibility rules that keep UI snappy.
By codefunc
Motion clarifies state: a panel opens, a toast arrives, a button acknowledges a press. Uncontrolled motion distracts and costs frames. CSS gives you two primary tools — transitions (A → B when a property changes) and keyframes (multi-step timelines) — plus a unit system that decides whether sizes scale with user font settings. This guide ties those pieces together so motion tokens stay consistent across a codebase.
Bottom line: Use transitions for simple state changes; use @keyframes for sequenced or looping motion. Animate transform and opacity first. Size with rem for UI chrome. Prototype with a CSS Transition Builder, CSS Keyframes Generator, and CSS Unit Converter; assemble fuller clips in a CSS Animation Builder.
| Tool | When to use |
|---|---|
transition |
Hover, focus, open/close, color/opacity toggles driven by a class or state |
@keyframes + animation |
Entrances with several stages, loaders, attention pulses, continuous loops |
| JS animation libraries | Gesture-driven spring physics, orchestrating many elements — out of scope here |
If a single property interpolates between two values when a class is added, start with a transition. If you need "0% → 40% → 100%" choreography, write keyframes.
.button {
transform: translateY(0);
transition:
transform 160ms var(--ease-out),
background-color 160ms var(--ease-out);
}
.button:hover {
transform: translateY(-1px);
}
| Prefer | Avoid animating (often) |
|---|---|
transform |
top / left (layout) |
opacity |
height on large subtrees (use transforms or discrete steps) |
background-color (small areas) |
Huge box-shadow spreads every frame |
clip-path (carefully) |
Animating expensive filters on scroll |
Generate timing and easing candidates in the CSS Transition Builder, then promote winners to CSS variables:
:root {
--duration-fast: 120ms;
--duration-base: 200ms;
--ease-out: cubic-bezier(0.22, 1, 0.36, 1);
}
| Gotcha | Detail |
|---|---|
transition: all |
Surprises you when width or color join the party |
Entering from display: none |
No transition — toggle visibility/opacity/transform instead |
| Inconsistent lists | Transitioning 3 properties out but 1 back feels broken — keep lists matched |
| Zero duration in reduced motion | Honor prefers-reduced-motion (below) |
@keyframes toast-in {
0% {
opacity: 0;
transform: translateY(8px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.toast {
animation: toast-in 220ms var(--ease-out) both;
}
| Keyword | Role |
|---|---|
animation-name |
Which @keyframes |
duration |
How long one cycle lasts |
timing-function |
Easing between keyframe stops |
delay |
Stagger starts |
iteration-count |
1 or infinite |
direction |
normal, alternate, … |
fill-mode |
both / forwards keeps end state |
Build step lists visually with the CSS Keyframes Generator. For multi-property clips and previews, use the CSS Animation Builder, then copy into your design tokens file.
width on a progress parent that reflows children.fill-mode and class removal order so the element is not display-killed mid-flight.Motion values often mix distances (translateY(0.5rem)) with durations (200ms). Layout units deserve the same discipline as color tokens.
| Unit | Meaning | Typical use |
|---|---|---|
px |
Absolute CSS pixels | Hairlines, borders, icon snugness |
rem |
Relative to root font size | Component spacing, UI type, many offsets |
em |
Relative to element font size | Padding that scales with local type |
% |
Relative to containing block | Widths; transforms % refer to the element itself |
vh / vw |
Viewport | Full-bleed sections — careful on mobile toolbars |
ms / s |
Time | Durations and delays |
Convert during refactors with a CSS Unit Converter (set your root size — often 16px).
If a user raises root font size, translateY(8px) stays 8px while type grows — offsets can feel tight. translateY(0.5rem) scales with their preference. Pair that with prefers-reduced-motion so scaled motion can also shorten or disable.
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
Prefer a subtler approach in design systems: swap long animations for instant state changes on the media query, without !important hammers when you can scope tokens instead.
Ship a short scale — not a new curve per component:
:root {
--motion-fast: 120ms;
--motion-base: 200ms;
--motion-slow: 320ms;
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
--ease-emphasized: cubic-bezier(0.2, 0, 0, 1);
--space-nudge: 0.25rem;
}
| Token | Use |
|---|---|
| Fast | Buttons, toggles |
| Base | Panels, toasts |
| Slow | Page-level reveals (rare) |
| Nudge | translateY(var(--space-nudge)) |
Prototype durations and easings in the CSS Transition Builder; convert nudge distances from legacy px mocks with the CSS Unit Converter.
| Do | Don't |
|---|---|
Animate transform / opacity |
Animate layout props on scroll-linked handlers |
| Keep animated layers small | Blur + shadow + scale on huge images simultaneously |
Use will-change sparingly and temporarily |
Leave will-change: transform on everything forever |
| Pause offscreen infinite animations | Run infinite spinners in hidden tabs unchecked |
Infinite animations are fine for a single loader. Ten decorative loops in a dashboard are not.
Transitions handle state flips; keyframes handle choreography and loops. Animate transform and opacity, express offsets in rem, and keep a short motion scale in CSS variables. Prototype with the CSS Transition Builder and CSS Keyframes Generator, refine in the CSS Animation Builder, and convert legacy pixel values with the CSS Unit Converter. Honor prefers-reduced-motion so clarity never depends on animation alone.
Build practical gradient and shadow design tokens, pick easing curves that feel right, and avoid paint-heavy effects that tank scroll performance.
A practical guide to Vanilla Extract — type-safe styles at build time, theming with CSS variables, recipes and sprinkles, and when to pick it over Tailwind or CSS Modules.
A practical guide to modern CSS layout — flexbox for one-dimensional alignment, grid for two-dimensional structure, and patterns for combining them in production components.
Find a developer tool