How to Make Realistic Shadows and Depth with CSS Layers

Soft, believable depth lifts a design from flat to tactile. When you want a card or button to feel real, layer shadows in CSS to mimic how light behaves in the physical world. In this guide, I will show you how to make realistic shadows and depth with CSS layers, from the core principles to two copy-and-paste examples you can test right away. The first example is a quick single card. The second is a small interface with cards, a floating action button, and a natural contact shadow.

The idea behind realistic shadows

Realistic shadows usually come in layers, not a single blur. It is in three parts:

  • Ambient shadow, a large, soft, low contrast blur that suggests room light
  • Key shadow, a smaller, darker blur that points away from the light source
  • Contact shadow, a thin shadow right under the object, darkest near the contact point

Stack these with multiple box-shadow values. You can also use filter: drop-shadow for shapes with transparency, and sometimes a pseudo element for a soft contact patch.

The key CSS building blocks

  • Multiple box shadows. You can chain shadows with commas.
box-shadow:
  0 24px 48px rgba(17, 24, 39, 0.16),  /* ambient */
  0 12px 24px rgba(17, 24, 39, 0.10),  /* key */
  0 2px 4px rgba(17, 24, 39, 0.10);    /* contact */
  • Shadow anatomy. The order is offset x, offset y, blur, spread, color. For example:
box-shadow: 0 16px 32px 0 rgba(0, 0, 0, 0.15);

Here 16px moves the shadow down, 32px blurs it, 0 spread keeps the size unchanged, the color is semi transparent.

  • Tinted shadows. Pure black looks harsh, use a deep neutral like rgba(17, 24, 39, 0.15).
  • Pseudo element contact patch. Add an oval under a floating panel.
.panel-wrap::after {
  content: "";
  position: absolute; inset: auto 10% 6% 10%;
  height: 16px;
  background: radial-gradient(ellipse at center, rgba(17,24,39,0.25), transparent 70%);
  filter: blur(6px);
  z-index: 0;
}
  • Filter based shadows. For icons or circular buttons, filter: drop-shadow follows the shape.
.icon { filter: drop-shadow(0 10px 20px rgba(17,24,39,0.25)) drop-shadow(0 4px 8px rgba(17,24,39,0.18)); }

How to make realistic shadows and depth with CSS layers

Follow a few rules and your shadows will read correctly:

  • Y offset is usually larger than X, light tends to come from above, so the shadow pushes downward
  • The farther an object is from the surface, the larger and softer the ambient shadow
  • Keep the contact shadow tight and darker, then fade quickly
  • Use two or more layers to mix ambient and key shadows
  • Match border radius with shadows, rounded cards need softer edges

Tweak values and move in small steps. If a shadow looks muddy, then reduce blur or raise contrast slightly. If it looks like a sticker, then add a wider ambient layer.

Example 1: Layered Shadow Card

Paste this into a file and open it. It is one card, one paragraph, and layered shadows that feel natural. The hover lifts the card and adjusts shadows to sell the elevation change.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Realistic Shadows with CSS Layers - Minimal Card</title>
<style>
:root {
--bg: #f4f6fb;
--ink: #0f172a;
--muted: #334155;
--radius: 16px;
--ambient: rgba(17,24,39,0.16);
--key: rgba(17,24,39,0.10);
--contact: rgba(17,24,39,0.12);
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
margin: 0;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Arial, sans-serif;
background:
radial-gradient(50% 50% at 15% 20%, #a78bfa22 0%, transparent 60%),
radial-gradient(60% 60% at 85% 30%, #60a5fa22 0%, transparent 65%),
linear-gradient(180deg, #f8fafc, #eef2ff);
min-height: 100dvh;
color: var(--ink);
display: grid; place-items: center;
padding: 24px;
}
.card {
width: min(92vw, 540px);
padding: 24px 28px;
background: #ffffff;
border-radius: var(--radius);
border: 1px solid rgba(15, 23, 42, 0.06);
box-shadow:
0 24px 48px var(--ambient), /* ambient layer */
0 12px 24px var(--key), /* key layer */
0 2px 4px var(--contact); /* contact layer */
transition: transform 0.18s ease, box-shadow 0.18s ease;
}
.card:hover { transform: translateY(-4px); }
.card:hover {
box-shadow:
0 32px 64px rgba(17,24,39,0.18),
0 18px 28px rgba(17,24,39,0.12),
0 4px 8px rgba(17,24,39,0.14);
}
h1 { margin: 0 0 8px; font-size: clamp(1.4rem, 4vw, 1.9rem); }
p { margin: 0; color: var(--muted); line-height: 1.6; }
</style>
</head>
<body>
<article class="card" aria-label="Layered shadow card">
<h1>Realistic Shadows with CSS Layers</h1>
<p>This card uses three shadows, a large ambient blur, a smaller key shadow, and a tight contact shadow. On hover it lifts slightly, the shadows spread and soften to match the new height.</p>
</article>
</body>
</html>

Output:

White card on a soft gradient background with layered ambient, key, and contact shadows.

What to look at:

  • The first box-shadow line is the ambient layer. The code 0 24px 48px var(--ambient) sets no horizontal shift, a 24px vertical offset, and a wide 48px blur.
  • The second line is the key shadow. The code 0 12px 24px var(--key) adds a darker, closer shadow that shapes the card.
  • The third line is the contact shadow. The code 0 2px 4px var(--contact) keeps the edge attached to the surface.

Example 2: A Complete Layout with Layered Depth and Contact Patches

This page has a header, a grid of cards, and a floating action button. You can use multiple shadow layers for the cards, filter: drop-shadow for the circular button, and a pseudo element to paint a natural oval contact under a “floating” panel.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>How to Make Realistic Shadows and Depth with CSS Layers - Full Demo</title>
<style>
:root{
--ink: #0f172a;
--muted: #475569;
--bg1: #f8fafc;
--bg2: #eef2ff;
--radius: 18px;

/* Shadow colors */
--s-ambient: rgba(17, 24, 39, 0.16);
--s-key: rgba(17, 24, 39, 0.10);
--s-contact: rgba(17, 24, 39, 0.14);
--hairline: rgba(15, 23, 42, 0.06);
--accent: #6366f1;
}

*{ box-sizing: border-box; }
html, body { height: 100%; }
body{
margin: 0;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, Noto Sans, Arial, sans-serif;
color: var(--ink);
background:
radial-gradient(60% 60% at 15% 20%, #22c55e22 0%, transparent 60%),
radial-gradient(50% 50% at 85% 30%, #f59e0b22 0%, transparent 60%),
linear-gradient(180deg, var(--bg1), var(--bg2));
min-height: 100dvh;
display: grid;
grid-template-rows: auto 1fr auto;
}

header{
padding: 16px clamp(14px, 4vw, 28px);
}
.bar{
display: flex; align-items: center; justify-content: space-between;
padding: 12px 16px;
background: #fff;
border-radius: 14px;
border: 1px solid var(--hairline);
box-shadow:
0 18px 36px var(--s-ambient),
0 10px 20px var(--s-key),
0 2px 4px var(--s-contact);
}
.brand{ font-weight: 800; letter-spacing: 0.2px; }
.nav{ display: flex; gap: 12px; }
.nav a{ text-decoration: none; color: var(--ink); opacity: 0.8; font-weight: 600; }

main{ padding: clamp(16px, 4vw, 32px); }
.grid{
max-width: 1100px; margin: 0 auto;
display: grid; gap: 16px;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
}

.card{
position: relative; isolation: isolate;
padding: 18px;
background: #fff;
border-radius: var(--radius);
border: 1px solid var(--hairline);
box-shadow:
0 26px 52px var(--s-ambient),
0 14px 28px var(--s-key),
0 3px 6px var(--s-contact);
display: grid; gap: 8px;
transition: transform 0.18s ease, box-shadow 0.18s ease;
}
.card:hover{ transform: translateY(-4px); }
.card:hover{
box-shadow:
0 34px 68px rgba(17,24,39,0.18),
0 18px 32px rgba(17,24,39,0.12),
0 6px 10px rgba(17,24,39,0.16);
}
.card h3{ margin: 0; font-size: 1.05rem; }
.card p{ margin: 0; color: var(--muted); line-height: 1.55; }

/* Floating panel with contact patch using a pseudo element */
.panel-wrap{ position: relative; }
.panel-wrap::after{
content: "";
position: absolute;
left: 10%; right: 10%;
bottom: 6%;
height: 18px;
background: radial-gradient(ellipse at center, rgba(17,24,39,0.28), transparent 70%);
filter: blur(6px);
z-index: 0;
}
.floating{
position: relative; z-index: 1;
background: #fff;
border-radius: var(--radius);
border: 1px solid var(--hairline);
padding: 20px;
box-shadow:
0 38px 80px rgba(17,24,39,0.20),
0 20px 40px rgba(17,24,39,0.12),
0 6px 12px rgba(17,24,39,0.18);
}

/* Floating action button with drop-shadow */
.fab{
position: fixed; right: 20px; bottom: 20px;
width: 56px; height: 56px; border-radius: 50%;
background: radial-gradient(circle at 30% 30%, #ffffff, #e2e8f0);
color: var(--accent);
display: grid; place-items: center;
border: 1px solid var(--hairline);
filter:
drop-shadow(0 18px 28px rgba(17,24,39,0.20))
drop-shadow(0 8px 12px rgba(17,24,39,0.18));
user-select: none; cursor: pointer;
font-weight: 900; font-size: 22px;
transition: transform 0.15s ease, filter 0.15s ease;
}
.fab:active{ transform: translateY(1px); }

footer{
padding: 18px clamp(16px, 4vw, 28px);
text-align: center;
color: rgba(15, 23, 42, 0.7);
}

@media (max-width: 480px) {
.fab{ right: 12px; bottom: 12px; width: 52px; height: 52px; }
}
</style>
</head>
<body>
<header>
<div class="bar">
<div class="brand">DepthLab</div>
<nav class="nav" aria-label="Main">
<a href="#">Docs</a>
<a href="#">Playground</a>
<a href="#">About</a>
</nav>
</div>
</header>

<main>
<section class="grid" aria-label="Cards with realistic shadows">
<article class="card">
<h3>Layered Ambient</h3>
<p>Large, soft ambient shadow sets the mood. It is wide and low contrast, it suggests distance from the surface.</p>
</article>
<article class="card">
<h3>Key Shadow</h3>
<p>A tighter, darker shadow shapes the object. It defines direction, usually down and slightly to one side.</p>
</article>
<article class="card">
<h3>Contact Shadow</h3>
<p>A thin, sharp shadow near the base holds the element to the surface. Keep it short with small blur.</p>
</article>
</section>

<section class="panel-wrap" aria-label="Floating panel with contact patch" style="margin: 28px auto 0; max-width: 900px;">
<div class="floating">
<h3 style="margin:0 0 8px;">Floating Panel</h3>
<p style="margin:0; color:#475569;">The oval under this panel is a pseudo element. The code uses <code>radial-gradient</code> and <code>filter: blur</code> to paint a natural contact patch independent from the card shadows.</p>
</div>
</section>
</main>

<button class="fab" aria-label="Create">+</button>

<footer>
Built with layered box shadows, drop-shadows, and a blurred contact patch.
</footer>
</body>
</html>

Output:

Three white cards in a responsive grid, each with layered shadows that show depth and direction.

Why these values work

  • Ambient layer. The line 0 26px 52px var(--s-ambient) on cards gives a wide blur and a medium offset, it reads as general room light.
  • Key layer. The line 0 14px 28px var(--s-key) adds shape, it is tighter and a bit darker, so edges look defined.
  • Contact layer. The line 0 3px 6px var(--s-contact) stays close to the base, it prevents a floating sticker look.
  • Contact patch. The pseudo element under .panel-wrap uses radial-gradient plus filter: blur(6px) to create a soft ellipse that does not clip to the border radius.
  • Drop shadow for the button. The two drop-shadow filters stack like layers, which works well for round shapes where box-shadow can look heavy.

Practical tips for shadows and depth

  • Use a neutral dark tint for shadows, not pure black, rgba(17,24,39,0.1 to 0.2) depending on elevation.
  • Blur grows with elevation. If an element lifts on hover, increase blur and offset a bit, then lower opacity to keep it airy.
  • Keep color and radius consistent with your surface. Mismatch can make the shadow feel detached.
  • Test against multiple backgrounds. Shadows that look good on white can be too faint on off white surfaces.
  • Limit heavy layers on low end devices. Three layers per element is a good balance of quality and performance.

Closing thoughts

Realistic shadows in CSS come from combining ambient, key, and contact layers, along with drop shadows for complex shapes and blurred gradients for contact patches. Start with small values and refine until the visual separation feels natural. The goal isn’t heavy contrast—it’s believable, tactile depth.

Vinish Kapoor
Vinish Kapoor

Vinish Kapoor is a seasoned software development professional and a fervent enthusiast of artificial intelligence (AI). His impressive career spans over 25+ years, marked by a relentless pursuit of innovation and excellence in the field of information technology. As an Oracle ACE, Vinish has distinguished himself as a leading expert in Oracle technologies, a title awarded to individuals who have demonstrated their deep commitment, leadership, and expertise in the Oracle community.

guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments