The box shadows article explains the anatomy: two offsets, a blur, a spread, a colour, and the optional inset keyword that flips the shadow inward. This workbook makes each part visible. You will build up from a hard flat shadow to layered, realistic depth, an elevation system, hover lifts, glows, and a pressed button, ending with a card that puts it all together. Change one value at a time and reload; shadows teach best when you can see exactly what each number does.
1. The basic shadow: two offsets and a colour
The minimum shadow is an x offset, a y offset, and a colour. With no blur it has a hard edge, which makes the offsets easy to see.
<html lang="en"><head> <meta charset="utf-8"> <link rel="stylesheet" href="styles.css"></head><body> <div class="box">Hard shadow, 10px right and 10px down.</div></body></html>
.box { /* x y colour */ box-shadow: 10px 10px #6f7580; background: #e8eef5; padding: 1.5rem; margin: 3rem; max-width: 280px; font-family: sans-serif; border-radius: 8px;}
Positive x pushes the shadow right, positive y pushes it down. Try -10px -10px and it jumps to the top-left, as if the light source moved to the bottom-right. The offsets are the shadow’s direction; everything else refines its character.
2. Blur radius: from edge to haze
The third value is the blur radius. Zero is a hard edge; bigger numbers diffuse the shadow into a soft haze. Three boxes with identical offsets show the progression.
<html lang="en"><head> <meta charset="utf-8"> <link rel="stylesheet" href="styles.css"></head><body> <div class="box blur-0">blur: 0</div> <div class="box blur-8">blur: 8px</div> <div class="box blur-24">blur: 24px</div></body></html>
.box { background: #e8eef5; padding: 1.5rem; margin: 2.5rem; max-width: 200px; font-family: sans-serif; border-radius: 8px;}.blur-0 { box-shadow: 6px 6px 0 rgba(0, 0, 0, 0.35); }.blur-8 { box-shadow: 6px 6px 8px rgba(0, 0, 0, 0.35); }.blur-24 { box-shadow: 6px 6px 24px rgba(0, 0, 0, 0.35); }
Same offsets, three levels of softness. Blur is the difference between a retro cut-out look and the gentle depth of a modern interface. Most real UI shadows live in the 4px to 24px blur range.
3. Spread radius: growing and shrinking the shadow
The fourth value, spread, resizes the shadow before the blur is applied. Positive spread makes it larger than the element; negative pulls it tighter underneath.
<html lang="en"><head> <meta charset="utf-8"> <link rel="stylesheet" href="styles.css"></head><body> <div class="box grow">spread: 8px</div> <div class="box none">spread: 0</div> <div class="box shrink">spread: -8px</div></body></html>
.box { background: #e8eef5; padding: 1.5rem; margin: 2.5rem; max-width: 200px; font-family: sans-serif; border-radius: 8px;}.grow { box-shadow: 0 8px 16px 8px rgba(0, 0, 0, 0.25); }.none { box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.25); }.shrink { box-shadow: 0 8px 16px -8px rgba(0, 0, 0, 0.25); }
The negative-spread box looks like it floats with a shadow tucked neatly beneath it, which is a very common trick: a generous blur paired with negative spread keeps a soft shadow from bleeding out sideways.
4. Colour and opacity with rgba
Shadows are rarely pure black. rgba gives you an alpha channel, and the alpha, more than anything else, controls how heavy the shadow feels.
<html lang="en"><head> <meta charset="utf-8"> <link rel="stylesheet" href="styles.css"></head><body> <div class="box heavy">alpha 0.6: heavy</div> <div class="box medium">alpha 0.3: balanced</div> <div class="box light">alpha 0.12: subtle</div></body></html>
body { background: #f7f9fc; }.box { background: #ffffff; padding: 1.5rem; margin: 2.5rem; max-width: 200px; font-family: sans-serif; border-radius: 8px;}.heavy { box-shadow: 0 8px 20px rgba(17, 35, 58, 0.6); }.medium { box-shadow: 0 8px 20px rgba(17, 35, 58, 0.3); }.light { box-shadow: 0 8px 20px rgba(17, 35, 58, 0.12); }
Notice the colour is a dark blue-grey rather than black, tinted toward the page’s own palette, which reads more naturally than rgba(0,0,0,...). For everyday cards, alphas between 0.08 and 0.25 are where subtle lives.
5. Inset: the pressed look
The inset keyword flips the shadow inside the element, so it looks recessed rather than raised. It is the standard treatment for wells, inputs, and pressed states.
<html lang="en"><head> <meta charset="utf-8"> <link rel="stylesheet" href="styles.css"></head><body> <div class="well">An inset well, sunk into the page.</div> <input class="field" placeholder="An input with a soft inner shadow"></body></html>
body { background: #f0f4f8; font-family: sans-serif; }.well { background: #e2e8f0; padding: 1.5rem; margin: 2.5rem; max-width: 320px; border-radius: 8px; box-shadow: inset 0 2px 8px rgba(17, 35, 58, 0.25);}.field { display: block; margin: 2.5rem; padding: 0.8rem 1rem; width: 320px; border: 1px solid #cbd5e1; border-radius: 6px; box-shadow: inset 0 1px 3px rgba(17, 35, 58, 0.12);}
Same syntax, one keyword, opposite illusion: the shadow now falls on the element’s inner top edge, as if it sits below the surface. Outer shadows say “raised”; inset says “carved in.”
6. Layered shadows: realistic depth
Real shadows are not one blur; they are a tight dark contact shadow plus a wide soft ambient one. Comma-separated layers reproduce that, and it is the single biggest upgrade to shadow realism.
<html lang="en"><head> <meta charset="utf-8"> <link rel="stylesheet" href="styles.css"></head><body> <div class="card single">One flat shadow</div> <div class="card layered">Two layered shadows</div></body></html>
body { background: #f7f9fc; font-family: sans-serif; }.card { background: #ffffff; padding: 1.75rem; margin: 2.5rem; max-width: 260px; border-radius: 10px;}.single { box-shadow: 0 10px 15px rgba(17, 35, 58, 0.3);}.layered { box-shadow: 0 1px 3px rgba(17, 35, 58, 0.24), /* tight contact shadow */ 0 12px 32px rgba(17, 35, 58, 0.16); /* wide ambient shadow */}
Put the two cards side by side: the layered one looks like it genuinely sits above the page, while the single shadow looks painted on. Layers stack in order, first on top, and each carries its own offsets, blur, and colour.
7. An elevation system
Consistent interfaces do not invent a new shadow per component; they define a small scale of elevations and reuse it. Here is a three-step system built with custom properties.
<html lang="en"><head> <meta charset="utf-8"> <link rel="stylesheet" href="styles.css"></head><body> <div class="card elevation-1">Elevation 1: resting</div> <div class="card elevation-2">Elevation 2: raised</div> <div class="card elevation-3">Elevation 3: floating</div></body></html>
:root { --shadow-1: 0 1px 3px rgba(17, 35, 58, 0.16); --shadow-2: 0 4px 10px rgba(17, 35, 58, 0.18); --shadow-3: 0 12px 32px rgba(17, 35, 58, 0.22);}body { background: #f7f9fc; font-family: sans-serif; }.card { background: #ffffff; padding: 1.5rem; margin: 2.5rem; max-width: 240px; border-radius: 10px;}.elevation-1 { box-shadow: var(--shadow-1); }.elevation-2 { box-shadow: var(--shadow-2); }.elevation-3 { box-shadow: var(--shadow-3); }
Higher elevation means a longer y offset, a bigger blur, and slightly more alpha, mimicking objects lifting further from the surface. Defining the scale once in :root is what keeps every card, menu, and dialog in the interface consistent.
8. Hover lift with a transition
Shadows shine on interaction. Pairing a shadow change with a small translateY and a transition produces the classic card lift.
<html lang="en"><head> <meta charset="utf-8"> <link rel="stylesheet" href="styles.css"></head><body> <div class="card">Hover me: I lift toward you.</div></body></html>
body { background: #f7f9fc; font-family: sans-serif; }.card { background: #ffffff; padding: 1.75rem; margin: 3rem; max-width: 260px; border-radius: 10px; box-shadow: 0 1px 3px rgba(17, 35, 58, 0.16); transition: box-shadow 0.25s ease-out, transform 0.25s ease-out;}.card:hover { box-shadow: 0 12px 32px rgba(17, 35, 58, 0.22); transform: translateY(-4px);}
The lift works because both cues agree: the element moves up while its shadow grows longer and softer, exactly what a real object does when raised. The transition makes the change feel physical rather than instant.
9. Coloured shadows: glows and focus rings
A shadow does not have to be grey. A brand-coloured shadow reads as a glow, and a zero-blur spread shadow makes an accessible focus ring that follows rounded corners.
<html lang="en"><head> <meta charset="utf-8"> <link rel="stylesheet" href="styles.css"></head><body> <button class="glow">Glowing button</button> <button class="ring">Tab to me for a focus ring</button></body></html>
body { background: #f7f9fc; font-family: sans-serif; }button { margin: 2.5rem; padding: 0.9rem 1.6rem; border: 0; border-radius: 8px; background: #1f4e78; color: white; font-size: 1rem;}.glow { box-shadow: 0 6px 24px rgba(31, 78, 120, 0.55); /* the brand colour, diffused */}.ring:focus-visible { outline: none; /* zero blur + spread = a crisp ring hugging the border-radius */ box-shadow: 0 0 0 4px rgba(31, 78, 120, 0.35);}
The glow is just the button’s own colour with blur and transparency. The focus ring trick, 0 0 0 4px, uses spread with no offset or blur, producing a ring that follows the rounded corners better than a default outline. Keep the contrast strong, since that ring is doing accessibility work.
10. Putting it together: a profile card
The final example combines the workbook: layered resting shadows from the elevation scale, an inset avatar well, a hover lift, and a glowing action button, all on one card.
<html lang="en"><head> <meta charset="utf-8"> <link rel="stylesheet" href="styles.css"></head><body> <div class="profile"> <div class="avatar"></div> <h3>Ada Lovelace</h3> <p>Compiler pioneer.</p> <button>Follow</button> </div></body></html>
body { background: #f0f4f8; font-family: sans-serif; }.profile { background: #ffffff; max-width: 260px; margin: 3rem; padding: 2rem; border-radius: 12px; text-align: center; box-shadow: 0 1px 3px rgba(17, 35, 58, 0.2), 0 10px 28px rgba(17, 35, 58, 0.14); transition: box-shadow 0.25s ease-out, transform 0.25s ease-out;}.profile:hover { transform: translateY(-4px); box-shadow: 0 2px 4px rgba(17, 35, 58, 0.2), 0 18px 44px rgba(17, 35, 58, 0.18);}.avatar { width: 72px; height: 72px; margin: 0 auto 1rem; border-radius: 50%; background: #d9e2ec; box-shadow: inset 0 2px 6px rgba(17, 35, 58, 0.25); /* recessed well */}.profile button { margin-top: 0.75rem; padding: 0.7rem 1.4rem; border: 0; border-radius: 8px; background: #1f4e78; color: white; box-shadow: 0 6px 18px rgba(31, 78, 120, 0.45); /* brand glow */}
Every shadow on this card has a job: the layered pair gives resting depth, the inset well recesses the avatar, the glow draws the eye to the action, and the hover lift confirms the card is interactive. None of them shouts, which is the guide’s closing advice in practice: shadows work best when consistent, subtle, and shared across similar elements rather than reinvented each time.
See you soon.
[…] CSS Box Shadows: 10 Code-Along Examples […]
[…] CSS Box Shadows: 10 Code-Along Examples […]
[…] For the full background, read the guide to CSS box shadows. To practise, work through the 10 code-along examples. […]