Skip to main content
{/}codefunc
CSS & DesignJuly 20, 2026 · 6 min read

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.

By codefunc

cssgradientsbox-shadoweasinganimationdesign-tokens

Gradients set atmosphere. Shadows create depth. Easing decides whether motion feels premium or cheap. Together they define a product's visual language — and they are easy to overdo. A dozen layered box-shadows on a scrolling list will cost you frames. This guide focuses on tokenizable values you can reuse, plus performance and motion rules that keep interfaces crisp.

Bottom line: Define a short scale of gradients, elevations, and easings as CSS variables. Prefer fewer, softer shadows; use transform and opacity for animation; tune curves with a Cubic Bezier Generator. Build previews in a CSS Gradient Generator and Box Shadow Generator before pasting into the design system.

Gradients as design tokens

Stop inventing a new gradient per component. Ship a small set:

:root {
  --gradient-brand: linear-gradient(135deg, #0f766e 0%, #155e75 100%);
  --gradient-subtle: linear-gradient(180deg, #f8fafc 0%, #e2e8f0 100%);
  --gradient-overlay: linear-gradient(
    to top,
    rgb(15 23 42 / 0.72) 0%,
    rgb(15 23 42 / 0) 55%
  );
}
Token role Typical use
Brand CTAs, hero washes, empty states
Subtle Page backgrounds, section dividers
Overlay Text on photography
Mesh / multi-stop Marketing only — keep out of dense UI

Prototype stops and angles in a CSS Gradient Generator, then freeze the output into variables.

Linear vs radial vs conic

Type When
linear-gradient Most UI — headers, buttons, fades
radial-gradient Spotlights, soft glows behind cards
conic-gradient Pie charts, angular sweeps — rare in product UI

Performance notes for gradients

  • Gradients are generally cheap as backgrounds.
  • Animating gradient stop positions can be expensive — prefer animating opacity of layered elements.
  • Huge fixed backgrounds with multiple gradients on every section increase paint area; reuse tokens and avoid one-off mesh stacks in app chrome.
.hero {
  background-image: var(--gradient-brand);
}

/* Prefer fading a layer over animating color stops */
.hero-glow {
  opacity: 0.35;
  transition: opacity 200ms var(--ease-out);
}
.hero:hover .hero-glow {
  opacity: 0.55;
}

Shadows: elevation, not decoration

Material-style elevation works when the scale is short and consistent:

:root {
  --shadow-sm: 0 1px 2px rgb(15 23 42 / 0.06);
  --shadow-md:
    0 1px 2px rgb(15 23 42 / 0.06),
    0 4px 12px rgb(15 23 42 / 0.08);
  --shadow-lg:
    0 4px 8px rgb(15 23 42 / 0.06),
    0 16px 32px rgb(15 23 42 / 0.12);
}

Tune offsets and blur in a Box Shadow Generator with a live preview, then commit the token — not twenty one-off values.

Rules of thumb

Do Don't
2–4 elevation levels Unique shadow per card variant
Soft, low-opacity darks Harsh black 0 0 20px #000
Pair shadow with slight border (1px solid rgb(0 0 0 / 0.06)) Rely on shadow alone for edge definition in light mode
Darker UI: use lighter / tinted glows sparingly Copy light-mode shadows onto near-black surfaces

Performance of box-shadow

box-shadow is not free:

  • Large blurs on many elements → more paint work during scroll
  • Animating box-shadow → often triggers paint every frame
  • Prefer transform (translateY) + a pre-defined shadow token swap, or animate opacity on a pseudo-element that carries the shadow
.card {
  box-shadow: var(--shadow-sm);
  transition:
    transform 200ms var(--ease-out),
    box-shadow 200ms var(--ease-out);
}

.card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

For long virtualized lists, keep resting state at --shadow-sm or use borders only; reserve --shadow-lg for modals and popovers.

filter: drop-shadow() follows alpha shapes (good for irregular SVGs) but can be costlier than box-shadow on boxes — prefer box-shadow for rectangles.

Easing: the curve is the personality

Linear motion feels mechanical. Real interfaces use asymmetric curves:

Name CSS Feel
Ease-out cubic-bezier(0.16, 1, 0.3, 1) Snappy entrances
Ease-in-out cubic-bezier(0.45, 0, 0.55, 1) Balanced moves
Emphasized cubic-bezier(0.2, 0, 0, 1) Modern "standard" deceleration

Design and export curves with a Cubic Bezier Generator:

:root {
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out: cubic-bezier(0.45, 0, 0.55, 1);
  --ease-emphasized: cubic-bezier(0.2, 0, 0, 1);
}

Duration scale

Token Duration Use
--duration-fast 120–150ms Hover, toggles
--duration-base 200–250ms Panels, fades
--duration-slow 300–400ms Large layout shifts

Pair durations with the CSS Animation Builder when you need keyframes (fade-in, slide-up) instead of transitions.

@keyframes fade-up {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.enter {
  animation: fade-up var(--duration-base) var(--ease-out) both;
}

Accessibility

Respect reduced motion:

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

Keep essential opacity changes if needed for state, but drop large translational motion.

Light and dark surfaces

Shadows and gradients need dual consideration:

:root {
  --shadow-md:
    0 1px 2px rgb(15 23 42 / 0.06),
    0 4px 12px rgb(15 23 42 / 0.08);
  --gradient-subtle: linear-gradient(180deg, #f8fafc 0%, #e2e8f0 100%);
}

[data-theme="dark"] {
  --shadow-md:
    0 1px 2px rgb(0 0 0 / 0.45),
    0 8px 24px rgb(0 0 0 / 0.35);
  --gradient-subtle: linear-gradient(180deg, #0f172a 0%, #1e293b 100%);
}

On dark UIs, deep black shadows disappear into the background — increase opacity slightly or add a faint light border. Brand gradients often need a darker stop set, not the same teal-on-teal that worked on white.

Putting tokens together

A cohesive surface recipe:

.panel {
  background: var(--gradient-subtle);
  border: 1px solid rgb(15 23 42 / 0.08);
  box-shadow: var(--shadow-md);
  border-radius: 12px;
  transition:
    transform var(--duration-fast) var(--ease-out),
    box-shadow var(--duration-fast) var(--ease-out);
}

Checklist for a design-system PR:

Practical takeaway

Codify gradients, shadows, and easings as a tiny token set — not one-off CSS per component. Soft elevation scales and ease-out curves do most of the visual work. Avoid animating heavy shadows and gradient stops; composite with transform and opacity. Build values in the gradient, shadow, bezier, and animation tools, then freeze them into CSS variables your whole UI reuses.

Sources

Try it on codefunc

Related articles

3

Search tools

Find a developer tool