Snippets /
Layered realistic shadow
Stack multiple shadows at different blur radii for natural depth.
Quick implementation
/* Elevation system — store as custom properties */
:root {
--shadow-sm:
0 1px 2px oklch(0 0 0 / 0.06),
0 2px 6px oklch(0 0 0 / 0.08);
--shadow-md:
0 1px 3px oklch(0 0 0 / 0.06),
0 4px 16px oklch(0 0 0 / 0.1);
--shadow-lg:
0 2px 4px oklch(0 0 0 / 0.04),
0 8px 24px oklch(0 0 0 / 0.1),
0 24px 48px oklch(0 0 0 / 0.06);
}
.card { box-shadow: var(--shadow-md); }
.modal { box-shadow: var(--shadow-lg); }
Prompt this to your LLM
Includes role, constraints, and design-system approach.
You are a senior frontend engineer building a design system.
Goal: Create a layered box-shadow elevation system with 3 levels (sm, md, lg).
Technical constraints:
- Each level uses multiple comma-separated shadows for realism.
- Use oklch() for shadow colors — perceptually consistent across backgrounds.
- sm: tight contact shadow (1-2px blur) + subtle ambient (6px blur).
- md: contact shadow + medium ambient (16px blur).
- lg: contact shadow + ambient (24px blur) + wide diffuse (48px blur).
- Store as CSS custom properties on :root.
- Include dark-mode overrides (@media prefers-color-scheme: dark) with ~2x alpha values.
Return CSS custom properties + example usage on .card and .modal.
Why this matters
A single box-shadow looks flat. Real objects cast both a tight contact shadow and a diffuse ambient shadow. Layering two or three shadows at different blur radii creates the depth that makes cards, modals, and dropdowns feel physical. This is the technique used by every modern design system.
The logic
The first shadow (small blur, higher opacity) simulates contact — where the element "touches" the surface. The second shadow (larger blur, lower opacity) simulates ambient occlusion. For the large elevation, a third ultra-wide shadow adds atmospheric depth. Using oklch() ensures shadows look correct on colored backgrounds.
Accessibility & performance
box-shadow triggers paint but not layout — it's relatively cheap. For hover transitions, avoid animating the shadow directly. Instead, place the hover shadow on a ::after pseudo-element and animate its opacity for compositor-only animation. Double your alpha values in dark mode for visible depth.