CSS course menu
CSS Shadows, Opacity and Filters: box-shadow, text-shadow, filter and backdrop-filter
Add depth and visual effects with CSS: box-shadow (offsets, blur, spread, inset, layered shadows), text-shadow, opacity, filter functions such as blur, grayscale and brightness, drop-shadow, and frosted glass with backdrop-filter.
On this page
Shadows suggest depth, and filters change how an element is drawn. Used lightly, they make interfaces feel layered and polished.
box-shadow#
box-shadow: offset-x offset-y blur spread color;.c { display: inline-block; width: 120px; padding: 20px 0; margin: 14px; text-align: center; background: white; border-radius: 10px; font: 13px monospace; }<div class="c" style="box-shadow: 0 1px 3px rgb(0 0 0 / 0.2)">subtle</div>
<div class="c" style="box-shadow: 0 8px 24px rgb(0 0 0 / 0.15)">raised</div>
<div class="c" style="box-shadow: 0 0 0 3px #2f5fe0">ring</div>
<div class="c" style="box-shadow: inset 0 2px 6px rgb(0 0 0 / 0.25)">inset</div>A shadow with no offset and no blur but some spread (0 0 0 3px) draws a ring that follows border-radius without changing the element's size, which is useful for focus styles.
Layered shadows#
Real shadows are soft near the object and fade out gradually. Several comma-separated layers look more natural than one:
.card {
box-shadow:
0 1px 2px rgb(0 0 0 / 0.06),
0 4px 8px rgb(0 0 0 / 0.06),
0 16px 32px rgb(0 0 0 / 0.08);
}text-shadow#
text-shadow has no spread or inset: offset-x offset-y blur color.
.hero { padding: 16px; background: url("/lesson-assets/landscape-600.svg") center / cover; }
.hero h2 { margin: 0; color: white; font: 800 30px system-ui, sans-serif; text-shadow: 0 2px 6px rgb(0 0 0 / 0.6); }<div class="hero"><h2>Readable over a photo</h2></div>opacity#
opacity from 0 (invisible) to 1 (solid) fades an element and all its children. To fade only the background, use a colour with alpha instead (Colors).
filter#
filter applies graphic effects to an element as it is drawn:
img { width: 120px; height: auto; margin: 4px; border-radius: 6px; }
figure { display: inline-block; margin: 0; text-align: center; font: 12px monospace; }<figure><img src="/lesson-assets/landscape-300.svg" alt="" width="300" height="200"><figcaption>none</figcaption></figure>
<figure><img src="/lesson-assets/landscape-300.svg" alt="" width="300" height="200" style="filter: grayscale(1)"><figcaption>grayscale(1)</figcaption></figure>
<figure><img src="/lesson-assets/landscape-300.svg" alt="" width="300" height="200" style="filter: blur(3px)"><figcaption>blur(3px)</figcaption></figure>
<figure><img src="/lesson-assets/landscape-300.svg" alt="" width="300" height="200" style="filter: brightness(0.6)"><figcaption>brightness(0.6)</figcaption></figure>
<figure><img src="/lesson-assets/landscape-300.svg" alt="" width="300" height="200" style="filter: hue-rotate(180deg)"><figcaption>hue-rotate(180deg)</figcaption></figure>Other functions include contrast(), saturate(), sepia(), invert() and opacity(). Several can be chained: filter: grayscale(1) brightness(1.2);.
drop-shadow#
filter: drop-shadow() follows the shape of the content, including transparent areas, while box-shadow always follows the rectangle:
img { margin: 12px; }<img src="/lesson-assets/mango.svg" alt="" width="100" height="100" style="box-shadow: 6px 6px 8px rgb(0 0 0 / 0.4)">
<img src="/lesson-assets/mango.svg" alt="" width="100" height="100" style="filter: drop-shadow(6px 6px 4px rgb(0 0 0 / 0.4))">backdrop-filter: frosted glass#
backdrop-filter applies filters to whatever is behind an element, which needs a partly transparent background to be visible:
.scene { height: 170px; padding: 20px; background: url("/lesson-assets/landscape-600.svg") center / cover; }
.glass {
max-width: 240px;
padding: 14px;
background: rgb(255 255 255 / 0.35);
backdrop-filter: blur(8px);
border: 1px solid rgb(255 255 255 / 0.5);
border-radius: 12px;
font-family: system-ui, sans-serif;
}<div class="scene"><div class="glass"><strong>Frosted glass</strong><p>The hills behind this card are blurred.</p></div></div>Always check that text on a blurred background still has enough contrast.
Performance and stacking#
Large blurs, many shadows and filters on big areas can make scrolling and animation slower, especially on low-end phones. Also note that filter, backdrop-filter and opacity below 1 create a new stacking context (z-index).
Practice#
Practice exercise
Create a card with a soft two-layer shadow that grows larger when hovered, and a greyscale image inside it that turns to full colour on hover.
Show solution
.card {
width: 220px;
padding: 10px;
border-radius: 12px;
background: white;
box-shadow: 0 1px 2px rgb(0 0 0 / 0.08), 0 4px 10px rgb(0 0 0 / 0.08);
transition: box-shadow 0.2s;
}
.card:hover { box-shadow: 0 2px 4px rgb(0 0 0 / 0.08), 0 12px 28px rgb(0 0 0 / 0.16); }
.card img { width: 100%; height: auto; border-radius: 8px; filter: grayscale(1); transition: filter 0.3s; }
.card:hover img { filter: none; }<div class="card">
<img src="/lesson-assets/landscape-300.svg" alt="Green hills" width="300" height="200">
<p>Hover over this card.</p>
</div>Key takeaway#
Finished this lesson?