Look at almost any interface you admire — a card that seems to float just above the page, a button that lifts when you hover it, a modal that clearly sits on top of everything else. What gives those elements their sense of depth? Almost always, it’s a shadow. Shadows are the single most effective trick for making a flat screen feel layered, and CSS gives you full control over them with two properties.
Those two are box-shadow, which casts a shadow around any element’s box, and text-shadow, which does the same for text. They share the same basic idea — an offset, a blur, a color — so once one clicks, the other comes for free. By the end of this guide you’ll read any shadow value at a glance and write your own with confidence. Let’s get into it.
How box-shadow works
box-shadow paints a shadow around the rectangular box of an element — following its border-radius, so rounded corners get rounded shadows. The full syntax takes a handful of values in a specific order:
.card {
box-shadow: 0 4px 12px 0 rgba(0, 0, 0, 0.15);
}
That one line covers the most common case: a soft shadow sitting just below a card. But to bend shadows to your will, you need to know what each of those numbers is doing. The order is horizontal offset, vertical offset, blur radius, spread radius, color:
| Value | What it controls |
|---|---|
0 |
Horizontal offset — pushes the shadow left (negative) or right (positive) |
4px |
Vertical offset — pushes it up (negative) or down (positive) |
12px |
Blur radius — how soft and spread out the edge is (bigger = softer) |
0 |
Spread radius — grows (positive) or shrinks (negative) the whole shadow |
rgba(0,0,0,0.15) |
Color — usually a semi-transparent black |
Let’s walk through each one, because understanding them individually is what lets you design a shadow instead of copying one off the internet and hoping.
The offsets: where the shadow falls
The first two numbers decide which direction the shadow is cast. Think of them as the position of a light source. A positive vertical offset pushes the shadow downward, which reads as light coming from above — exactly how light usually behaves in the real world, and why down-shadows look natural:
/* shadow directly below — light from straight above */
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.2);
/* shadow down and to the right — light from top-left */
box-shadow: 4px 4px 12px rgba(0, 0, 0, 0.2);
Most UI shadows use a horizontal offset of 0 and a positive vertical offset, giving that clean “lit from above” look. Offsetting horizontally too is fine for a more dramatic, angled light — just keep it consistent across your design so everything looks lit by the same sun.
Blur radius: soft or sharp
The third number is the blur. At 0, the shadow has a hard, crisp edge like a solid shape. The larger it gets, the softer and more diffuse the edge becomes:
box-shadow: 0 4px 0 rgba(0, 0, 0, 0.3); /* hard edge, no blur */
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); /* soft, gentle glow */
Blur is the value that most controls how realistic a shadow feels. Real shadows in the world are almost never razor-sharp; they fade out at the edges. So for natural-looking depth, you’ll usually want a generous blur — often larger than the offset itself. A tiny offset with a big blur produces that soft, premium “floating” look you see on well-designed cards.
Spread radius: bigger or smaller
The fourth number, the spread, grows or shrinks the entire shadow before the blur is applied. Positive values make it larger than the element; negative values pull it in tighter:
/* shadow slightly larger than the box */
box-shadow: 0 4px 12px 4px rgba(0, 0, 0, 0.15);
/* shadow pulled in, so it peeks out only at the bottom */
box-shadow: 0 8px 12px -6px rgba(0, 0, 0, 0.3);
Spread is the value people forget exists, but it’s quietly useful. A negative spread is a favourite trick: it shrinks the shadow so it only shows where the offset pushes it out, giving a tight, contained shadow that hugs the element instead of bleeding out on all sides.
Spread is optional — and so is half of this
You only need to supply the values you care about. box-shadow: 0 4px 12px black (offsets, blur, color) is perfectly valid — the spread defaults to 0. The absolute minimum is two offsets and a color: box-shadow: 2px 2px black. Everything beyond that is fine-tuning. So don’t feel you must memorize a four-number incantation; start with offset and blur, and add the rest only when a design needs it.
Color: keep it subtle
The last value is the shadow’s color. The single most common mistake beginners make is using solid black (#000), which looks heavy and fake. Real shadows are transparent — they darken what’s behind them rather than painting a solid grey blob. So almost always reach for rgba() with a low alpha:
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); /* tasteful */
box-shadow: 0 4px 12px #000000; /* heavy, avoid */
An alpha somewhere between 0.1 and 0.25 covers most cases. On dark backgrounds you can go a little stronger; on light backgrounds, a little softer. If you’re shaky on rgba() and color formats, our guide to colors and backgrounds walks through them.
Inset shadows: shadows on the inside
Everything so far casts the shadow outside the element. Add the keyword inset and the shadow flips to the inside, making the element look pressed in or carved out instead of raised up:
.input {
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
}
This is the secret behind that subtle “sunken” look on form inputs, pressed buttons, and toggle switches. The inner shadow makes the surface feel recessed, like a real physical slot. It’s the same property and the same values — just inset flips which side the shadow lands on.
/* a button that looks pressed down while clicked */
.button:active {
box-shadow: inset 0 3px 6px rgba(0, 0, 0, 0.2);
}
Combine an outer shadow on the resting state with an inset shadow on :active and you get a button that genuinely feels like it depresses when clicked. Pair that with a quick transition and the effect is wonderfully tactile.
Layering multiple shadows
Here’s where shadows get powerful: you can apply several at once, separated by commas, and they stack from front to back. This is the single biggest upgrade you can make to your shadows, because real-world depth is never one flat shadow — it’s several soft ones layered together:
.card {
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.08),
0 4px 8px rgba(0, 0, 0, 0.08),
0 12px 24px rgba(0, 0, 0, 0.08);
}
That card has three shadows: a tight close one, a medium one, and a wide diffuse one. Together they mimic how light actually scatters, producing a far richer, more believable sense of elevation than any single shadow could. This “layered shadow” technique is the difference between a shadow that looks designed and one that looks bolted on.
A simple elevation system
Designers think of shadows as elevation — how high an element floats above the page. A clean trick is to define a few levels and reuse them. A resting card might sit at a low elevation, and lift to a higher one on hover:
.card {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
transition: box-shadow 0.2s ease;
}
.card:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}The card visibly “rises” toward you on hover. Define three or four such levels once and your whole interface gains a consistent, professional sense of depth.
Glows and colored shadows
Nothing says a shadow has to be grey. Use a colored, transparent value and the shadow becomes a glow — perfect for focus rings, neon effects, and drawing attention to an active element:
/* a soft cyan glow around a focused input */
.input:focus {
box-shadow: 0 0 0 4px rgba(0, 184, 230, 0.4);
outline: none;
}
Notice both offsets are 0 and the blur is 0 here — with only a spread, the shadow becomes an even ring all the way around the element, which is exactly how you build a custom focus highlight. Bump the blur up and that ring softens into a real glow:
.button {
box-shadow: 0 0 20px rgba(0, 184, 230, 0.6);
}
Don't remove focus outlines without replacing them
It’s tempting to write outline: none to kill the default focus ring browsers draw. But that ring is what lets keyboard users see where they are on the page — removing it and leaving nothing breaks accessibility badly. If you set outline: none, you must provide your own visible focus indicator, and a box-shadow ring like the one above is a great way to do it. Never strip the focus style and leave the element with no visible focus at all.
text-shadow: the same idea, for text
Once box-shadow makes sense, text-shadow is almost no extra learning. It casts a shadow behind the text itself, and its syntax is the simpler cousin — horizontal offset, vertical offset, blur, color:
.heading {
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.4);
}
There’s no spread and no inset — just the two offsets, a blur, and a color. That single shadow lifts the text slightly off the background, which can help readability when text sits over a busy image. Used with restraint, it adds a touch of polish:
/* subtle lift for a hero heading over a photo */
.hero-title {
color: #ffffff;
text-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
}
And just like box-shadow, you can layer multiple text shadows with commas to build effects — an outline by stacking shadows in every direction, or a glowing neon look:
/* a glowing neon-style heading */
.neon {
color: #ffffff;
text-shadow:
0 0 8px rgba(0, 184, 230, 0.9),
0 0 20px rgba(0, 184, 230, 0.6);
}
Keep text shadows light
Text shadows are easy to overdo. A heavy, dark shadow behind body text makes it harder to read, not easier — the blur smears the letterforms. Reserve text-shadow for headings, hero text over images, or deliberate decorative effects, and keep the blur and opacity gentle. For plain paragraphs on a solid background, no text shadow at all is almost always the right call.
A real example: a polished card
Let’s pull box-shadow, layering, and a hover transition together into a card you’d genuinely ship:
.card {
background: #ffffff;
border-radius: 12px;
padding: 24px;
box-shadow:
0 1px 3px rgba(0, 0, 0, 0.06),
0 6px 16px rgba(0, 0, 0, 0.08);
transition: box-shadow 0.25s ease, transform 0.25s ease;
}
.card:hover {
transform: translateY(-4px);
box-shadow:
0 2px 6px rgba(0, 0, 0, 0.08),
0 16px 32px rgba(0, 0, 0, 0.12);
}
<div class="card">
<h3>ACY Partner Indonesia</h3>
<p>A floating card with layered shadows that lifts on hover.</p>
</div>
At rest, the card has a gentle two-layer shadow that makes it float just above the page. On hover, it nudges up four pixels and its shadow grows deeper and wider — the classic “lifting toward you” effect. The transition smooths the whole thing out. If you want to dig into that smooth motion, our guide to CSS transitions covers it in full.
Shadows can cost performance if you animate them carelessly
Animating box-shadow directly on every frame can be expensive, because the browser has to re-render the blur each time. For hover lifts, animating transform (as above) is cheap and smooth; the shadow change is small and infrequent enough that it’s usually fine. But if you build something that animates a heavy blurred shadow continuously, watch your frame rate — a common pro trick is to fade a separate pre-rendered shadow layer with opacity instead.
Wrapping up
Shadows are the fastest way to add depth and polish to an interface, and you’ve now got both shadow properties down cold:
box-shadowcasts a shadow around an element: horizontal offset, vertical offset, blur, spread, color.- Most UI shadows use a
0horizontal offset, a positive vertical offset, a generous blur, and a transparentrgba()color — never solid black. - Spread grows or shrinks the shadow; a negative spread keeps it tight and contained.
insetflips the shadow inside the element for a pressed or sunken look.- Layer multiple shadows with commas to mimic real, scattered light and build an elevation system.
- A colored, transparent shadow becomes a glow — great for focus rings, but never remove a focus outline without replacing it.
text-shadowis the same idea for text — offset, blur, color, no spread — and works best kept subtle.
With shadows you can make any element float, sink, or glow. Next up is a property that paints with color in a whole different way — blending one color smoothly into another across a surface: CSS gradients, which take your backgrounds from flat to vivid.