I’m Kayla. I build sites for real people and real budgets. Last spring, I fixed three headers that felt flat and kind of cheap. A tiny shadow made them feel solid. Funny how one line of CSS can change the mood, right? (Want the extended play-by-play? I put it all in my case study here.)
Let me show you what I used, where it looked good, and where it got weird. I’ll share my exact code, too. Need a quick navigation stub to test your shadows? CSS Menu Tools lets you spin up a menu in seconds so you can focus on the polish. While you’re at it, here’s how I overlay dropdown menus with CSS—handy if your header nav needs to float over hero images.
The Quick Win: Simple Box-Shadow on the Header
This is my go-to. It’s fast. It looks clean. I used it on a school blog with a white header over a white page.
header.site-header {
position: sticky; /* or fixed */
top: 0;
background: #fff;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
z-index: 50;
}
How it felt: light and crisp. The header didn’t scream. It just separated the nav from the page.
When it broke: on a low-end Android, a huge blur made scroll feel choppy. So keep the blur small. I like 2px to 8px spread and a soft alpha.
Only Show the Shadow After You Scroll
A client wanted a flat header at the top, but a shadow once you move. I gave two paths.
Small JavaScript. It’s dead simple and works everywhere.
<header class="site-header">...</header>
<script>
const header = document.querySelector('.site-header');
let last = 0;
window.addEventListener('scroll', () => {
const y = window.scrollY || window.pageYOffset;
if (y > 2 && last <= 2) header.classList.add('scrolled');
if (y <= 2 && last > 2) header.classList.remove('scrolled');
last = y;
});
</script>
.site-header {
transition: box-shadow 160ms ease;
}
.site-header.scrolled {
box-shadow: 0 6px 18px rgba(0,0,0,0.12);
}
Want to push the effect further with keyframes? I learned a ton from this guide to animating box-shadows—it covers everything from easing tricks to GPU considerations.
No JavaScript version using modern CSS. Worked for me in up-to-date Chrome, Safari, and Firefox.
<header class="site-header">...</header>
<div class="scroll-sentinel"></div>
<main> ...lots of content... </main>
/* Shadow only when .scroll-sentinel is not in view */
.site-header {
position: sticky;
top: 0;
background: #fff;
transition: box-shadow 160ms ease;
}
body:has(.scroll-sentinel:not(:in-view)) .site-header {
box-shadow: 0 6px 18px rgba(0,0,0,0.12);
}
.scroll-sentinel {
block-size: 1px;
}
You know what? The CSS trick felt neat. But I still ship the small JS for older stacks or when teams worry about browser edge cases.
Text Shadow for Big Titles (Use a Light Hand)
On a hero image, plain white text can glow too hard. I soften it with a tiny text shadow. If headline styling is on your radar, my hands-on review of title CSS design (full breakdown) walks through more patterns you can steal.
.hero h1 {
color: #fff;
text-shadow: 0 1px 2px rgba(0,0,0,0.35);
letter-spacing: 0.2px;
}
I used this on a travel site header over a beach photo. It read well on phone and laptop. If you push the blur, it starts to look like a sticker. So keep it tight.
When Your Header Is Transparent: Try Drop-Shadow
Box-shadow sits on the box. If your header has rounded corners or a cutout, filter: drop-shadow can look better.
header.glass {
position: sticky;
top: 0;
background: rgba(255,255,255,0.7);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px); /* iOS Safari */
/* A faint border helps on light pages */
border-bottom: 1px solid rgba(0,0,0,0.06);
/* No box-shadow here */
filter: drop-shadow(0 2px 10px rgba(0,0,0,0.10));
}
I used this on a food blog with photos behind the header. It felt “airy,” but still grounded. Small note: backdrop-filter is heavier on older phones. I saw a tiny stutter on a Moto G. Not awful, but it’s there.
Dark Mode Without Tears (Use Variables)
I switch shadow color by theme with CSS variables. It saves time and keeps contrast sane.
:root {
--header-bg: #fff;
--header-shadow: rgba(0,0,0,0.10);
--hairline: rgba(0,0,0,0.06);
}
@media (prefers-color-scheme: dark) {
:root {
--header-bg: #101114;
--header-shadow: rgba(0,0,0,0.50);
--hairline: rgba(255,255,255,0.08);
}
}
header.smart {
background: var(--header-bg);
box-shadow: 0 4px 14px var(--header-shadow);
border-bottom: 1px solid var(--hairline);
}
In dark mode, shadows need more alpha, not more blur. That keeps it sharp and not muddy.
Tailwind, Bootstrap, or Plain CSS? I’ve Used All Three
On a small shop site with Tailwind, I just did:
<header class="sticky top-0 bg-white shadow-md z-50">...</header>
Wanted a softer look?
<header class="sticky top-0 bg-white shadow-[0_2px_8px_rgba(0,0,0,0.08)]">...</header>
Bootstrap has classes too (shadow-sm, shadow). Plain CSS still gives me the most control. I mix them as needed.
Performance Notes From Actual Devices
I tested on:
- iPhone 12, Safari
- Pixel 7, Chrome
- A very tired Moto G
For a deeper dive into how box-shadow can impact repaint times and scrolling, the benchmarks in SitePoint’s performance roundup are eye-opening.
What helped:
- Small blur values (2–8px). Big blurs repaint more.
- Avoid stacking many shadows on one element.
- For sticky headers, keep the paint area simple. A soft box-shadow plus a 1px border is usually enough.
- Turn off heavy drop-shadow on old phones if you can.
What hurt:
- Box-shadows with 40px blur on a sticky header. Scrolling felt gummy.
- Backdrop-filter plus huge blur plus PNG logo glow. Pretty, but slow.
Little Accessibility Things I Watch
- I add a faint 1px border under the header. The shadow then can stay light.
- Don’t rely only on a shadow for separation. Some users have custom colors.
- On busy images, text needs a shadow or a subtle overlay to read well.
Here’s a tiny overlay that saved me more than once:
.hero::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(
to bottom,
rgba(0,0,0,0.30),
rgba(0,0,0,0.00) 50%
);
pointer-events: none;
}
Debug Tricks I Actually Use
- I crank the shadow to something wild, like 0 20px 50px rgba(0,0,0,0.5), just to see bounds. Then I tune it back down.
- I check light and dark mode back to back.
- I scroll fast on phone. If it janks, I shave blur first.
Three Recipes You Can Paste
Minimal and classy:
header {
position: sticky; top: 0; background: #fff;
border-bottom: 1px solid rgba(0,0,0,0.06);
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
Soft “glass” header:
“`css
header {
position: sticky; top: 0;
background: