What Is a CSS Overlay
A CSS overlay is when you place something “on top of” something else—usually to tint an image, dim a background, highlight a region, or show text on top of media.
There’s no single overlay: property in CSS. Instead, overlays are built using a few reliable patterns:
-
Pseudo-element overlays (most common):
::before/::afterpositioned over a box. -
Extra overlay element: a child
<div class="overlay">that sits on top of content. -
Background-layer overlays: multiple backgrounds like
linear-gradient(...), url(...). -
Filter overlays: blur or tint the background under a panel using
backdrop-filter.
In this tutorial, we’ll build overlays that are readable, clickable, and not secretly broken by stacking contexts (CSS has a talent for that).
CSS Overlay Color Basics
The classic overlay is a semi-transparent layer of color on top of a box. The most flexible approach is a pseudo-element:
position: relativeon the parent so the overlay can anchor to itposition: absoluteandinset: 0on the pseudo-element so it fills the parentbackground+opacity(or anrgba()/hsl(... / ...)color)
.card::before {
opacity: 0.15;
}
.card::before {
opacity: 0.55;
}
.card::before {
background: #0077b6;
opacity: 0.45;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.card {
position: relative;
border: 3px solid #111;
border-radius: 18px;
padding: 18px;
background: #fff;
box-shadow: 0 12px 0 #111;
overflow: hidden;
display: grid;
gap: 10px;
min-height: 220px;
}
.card::before {
content: "";
position: absolute;
inset: 0;
background: #ef233c;
}
.card > * {
position: relative;
z-index: 1;
}
.badge {
width: fit-content;
padding: 6px 10px;
border: 2px solid #111;
border-radius: 999px;
background: #fff;
font-size: 14px;
font-weight: 650;
}
.card p {
margin: 0;
max-width: 52ch;
line-height: 1.45;
}
Overlay: pseudo-elementReadable content stays above the overlay
The overlay is on
::before, while the content is lifted withz-index: 1.
Opacity vs RGBA: a common beginner gotcha
If you put opacity: 0.5; on the parent, you’ll fade everything inside it (text, buttons, your
hopes). That’s why we fade only the overlay layer (the pseudo-element).
CSS Overlay Color on Image
When you want a colored overlay on top of an image, you have two common approaches:
-
Use an
<img>and add a pseudo-element overlay on the container (great when the image is content). -
Use layered backgrounds:
linear-gradient(...), url(...)(great when the image is purely decorative).
Here’s the container + <img> pattern with a gradient overlay:
.hero {
isolation: isolate;
}
.hero::before {
background: rgba(0, 0, 0, 0.55);
}
.hero img {
z-index: -1;
}
.hero {
isolation: isolate;
}
.hero::before {
background: linear-gradient(90deg, rgba(239, 35, 60, 0.55), rgba(0, 119, 182, 0.55));
}
.hero img {
z-index: -1;
}
.hero {
isolation: isolate;
}
.hero::before {
background: radial-gradient(circle at 70% 20%, rgba(255, 255, 255, 0.2), rgba(0, 0, 0, 0.75));
}
.hero img {
z-index: -1;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.hero {
position: relative;
border: 3px solid #111;
border-radius: 18px;
overflow: hidden;
box-shadow: 0 12px 0 #111;
min-height: 280px;
display: grid;
align-content: end;
padding: 18px;
}
.hero img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
.hero::before {
content: "";
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.55);
}
.hero > * {
position: relative;
z-index: 1;
color: #fff;
}
.hero h3 {
margin: 0 0 6px;
line-height: 1.1;
}
.hero p {
margin: 0;
max-width: 55ch;
line-height: 1.45;
}
.pill {
width: fit-content;
margin-bottom: 10px;
padding: 6px 10px;
border: 2px solid rgba(255, 255, 255, 0.7);
border-radius: 999px;
background: rgba(0, 0, 0, 0.15);
font-size: 13px;
font-weight: 650;
}
Overlay on an imageMake text readable on photos
The overlay is a separate layer. Your image stays crisp, your text stays readable.
Accessibility note
If text sits on an image, your overlay isn’t just decoration: it’s often the difference between readable and “why is this gray on gray?”. Aim for good contrast and consider checking with a contrast tool.
CSS Overlay Image and Text on Image
Sometimes you want an overlay that is itself an image (like a texture, pattern, or watermark) and still keep text on top. We’ll stack layers like this:
- Base image (
<img>) - Overlay image (pseudo-element with
background-image) - Optional tint (another pseudo-element)
- Text content on top
You can do this with two pseudo-elements: ::before for the texture and ::after for the
tint.
.banner::before {
opacity: 0.18;
}
.banner::after {
opacity: 0.25;
}
.banner::before {
opacity: 0.35;
}
.banner::after {
opacity: 0.55;
}
.banner::before {
mix-blend-mode: overlay;
opacity: 0.55;
}
.banner::after {
opacity: 0.35;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.banner {
position: relative;
border: 3px solid #111;
border-radius: 18px;
overflow: hidden;
box-shadow: 0 12px 0 #111;
min-height: 300px;
padding: 18px;
display: grid;
align-content: end;
isolation: isolate;
}
.banner img {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
display: block;
object-fit: cover;
transform: scale(1.02);
}
.banner::before {
content: "";
position: absolute;
inset: 0;
z-index: 1;
background-image: url("https://picsum.photos/900/600");
background-size: cover;
background-position: center;
opacity: 0.18;
pointer-events: none;
}
.banner::after {
content: "";
position: absolute;
inset: 0;
z-index: 2;
background: rgba(0, 0, 0, 0.55);
opacity: 0.25;
pointer-events: none;
}
.banner > * {
position: relative;
z-index: 3;
color: #fff;
}
.banner h3 {
margin: 0 0 6px;
}
.banner p {
margin: 0;
max-width: 56ch;
line-height: 1.45;
}
.kicker {
width: fit-content;
margin-bottom: 10px;
padding: 6px 10px;
border: 2px solid rgba(255, 255, 255, 0.7);
border-radius: 999px;
background: rgba(0, 0, 0, 0.2);
font-size: 13px;
font-weight: 650;
}
See the entire CSS needed here by switching to the "Default CSS" tab in the playground.
Layer order and z-index
A simple rule: background/image layers go first, overlays go next, content goes last.
In CSS terms, that usually means: pseudo-elements are absolutely positioned, and content gets
position: relative + z-index: 3.
CSS Overlay Effects
“Effects” often means your overlay changes on hover, reveals something, or blends in a dramatic way. Let’s build a hover-reveal overlay that:
- dims the image by default
- reveals more brightness on hover
- slides a caption up
.tile::before {
opacity: 0.65;
}
.tile .caption {
transform: translateY(0);
opacity: 1;
}
.tile:hover::before {
opacity: 0.25;
}
.tile .caption {
transform: translateY(18px);
opacity: 0.2;
}
.tile:hover .caption {
transform: translateY(0);
opacity: 1;
}
.tile:hover::before {
opacity: 0.1;
}
.tile:hover .caption {
transform: translateY(-6px);
opacity: 1;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.tile {
position: relative;
border: 3px solid #111;
border-radius: 18px;
overflow: hidden;
box-shadow: 0 12px 0 #111;
width: min(640px, 100%);
min-height: 310px;
display: grid;
align-content: end;
padding: 18px;
isolation: isolate;
}
.tile img {
position: absolute;
inset: 0;
z-index: 0;
width: 100%;
height: 100%;
display: block;
object-fit: cover;
transform: scale(1.05);
}
.tile::before {
content: "";
position: absolute;
inset: 0;
z-index: 1;
background: rgba(0, 0, 0, 0.55);
opacity: 0.65;
transition: opacity 220ms ease;
pointer-events: none;
}
.tile > * {
position: relative;
z-index: 2;
color: #fff;
}
.caption {
display: grid;
gap: 8px;
transition: transform 220ms ease, opacity 220ms ease;
transform: translateY(0);
opacity: 1;
}
.caption h3 {
margin: 0;
}
.caption p {
margin: 0;
max-width: 55ch;
line-height: 1.45;
}
.hint {
width: fit-content;
padding: 6px 10px;
border: 2px solid rgba(255, 255, 255, 0.7);
border-radius: 999px;
background: rgba(0, 0, 0, 0.2);
font-size: 13px;
font-weight: 650;
}
Hover meOverlay hover reveal
The overlay fades away on hover. The caption animates for extra polish.
Tip: when overlays block clicks
If you ever add buttons or links on top of an overlay and clicks “don’t work”, your overlay might be intercepting
pointer events.
Fix with pointer-events: none; on the overlay layer (usually the pseudo-element).
CSS Overlay Filter
Filters are a fun way to style what’s behind (or the overlay itself), but there are two different concepts:
-
filteraffects the element itself (and its children). Put it on the image layer if you want the image blurred or desaturated. -
backdrop-filteraffects whatever is behind a semi-transparent element. This is the “frosted glass” overlay effect.
Let’s compare them with a card overlay panel.
.photo {
filter: blur(0px) saturate(1);
}
.panel {
backdrop-filter: blur(0px);
background: rgba(255, 255, 255, 0.25);
}
.photo {
filter: blur(3px) saturate(1.2);
}
.panel {
backdrop-filter: blur(0px);
background: rgba(255, 255, 255, 0.25);
}
.photo {
filter: blur(0px) saturate(1);
}
.panel {
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.28);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.scene {
position: relative;
border: 3px solid #111;
border-radius: 18px;
overflow: hidden;
box-shadow: 0 12px 0 #111;
min-height: 310px;
}
.photo {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
display: block;
transform: scale(1.05);
filter: blur(0px) saturate(1);
}
.scene::before {
content: "";
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.25);
}
.panel {
position: absolute;
left: 16px;
right: 16px;
bottom: 16px;
border: 2px solid rgba(255, 255, 255, 0.7);
border-radius: 16px;
padding: 14px;
color: #fff;
background: rgba(255, 255, 255, 0.25);
backdrop-filter: blur(0px);
}
.panel h3 {
margin: 0 0 6px;
color: #111;
background: rgba(255, 255, 255, 0.85);
width: fit-content;
padding: 4px 8px;
border-radius: 10px;
}
.panel p {
margin: 0;
max-width: 60ch;
line-height: 1.45;
color: #111;
background: rgba(255, 255, 255, 0.85);
width: fit-content;
padding: 6px 8px;
border-radius: 10px;
}
![]()
Filter vs backdrop-filter
filterchanges the image itself.backdrop-filterblurs what’s behind the panel.
Browser note
backdrop-filter is widely supported in modern browsers, but not universally in older ones. Always make
sure your panel stays readable even without the blur (the background: rgba(...) is your fallback).
CSS Overlay Div
Pseudo-elements are great, but sometimes you want the overlay to be a real element because:
- it contains interactive UI (buttons, a close icon, a form)
- you want to toggle it with JS later
- you want it to be easier to understand in HTML
Here’s a simple overlay div pattern: a container with an image, plus a positioned overlay panel.
Switch to the HTML tab to see the structure here.
.overlay {
inset: auto 16px 16px 16px;
opacity: 1;
}
.overlay {
inset: 0;
opacity: 1;
}
.overlay {
inset: 0;
opacity: 0;
}
.stage:hover .overlay {
opacity: 1;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.stage {
position: relative;
border: 3px solid #111;
border-radius: 18px;
overflow: hidden;
box-shadow: 0 12px 0 #111;
width: min(700px, 100%);
min-height: 310px;
}
.stage img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
.overlay {
position: absolute;
inset: auto 16px 16px 16px;
border-radius: 16px;
padding: 14px;
background: rgba(255, 255, 255, 0.88);
border: 2px solid #111;
opacity: 1;
transition: opacity 200ms ease;
}
.overlay h3 {
margin: 0 0 6px;
}
.overlay p {
margin: 0;
max-width: 60ch;
line-height: 1.45;
}
.small {
margin-top: 10px;
font-size: 14px;
opacity: 0.8;
}
CSS Overlay Scrollbar
“Overlay scrollbar” can mean two things:
- A scrollbar inside an overlay panel (a scrollable overlay).
- A scrollbar style that looks like it floats over content (browser UI territory, only partially controllable in CSS).
The beginner-friendly, reliable part is the first one: a scrollable overlay panel. We’ll create an overlay that sits on the right and scrolls inside.
.drawer {
width: min(360px, 75%);
}
.drawer {
width: min(420px, 85%);
}
.drawer {
width: min(320px, 70%);
}
.drawer {
background: rgba(255, 255, 255, 0.92);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.panel-scene {
position: relative;
border: 3px solid #111;
border-radius: 18px;
overflow: hidden;
box-shadow: 0 12px 0 #111;
width: min(860px, 100%);
min-height: 360px;
}
.panel-scene img {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: block;
object-fit: cover;
}
.dim {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.35);
}
.drawer {
position: absolute;
top: 12px;
bottom: 12px;
right: 12px;
width: min(360px, 75%);
border-radius: 16px;
background: rgba(255, 255, 255, 0.88);
border: 2px solid #111;
padding: 12px;
overflow: auto;
}
.drawer h3 {
margin: 0 0 10px;
}
.drawer p {
margin: 0 0 10px;
line-height: 1.45;
}
.drawer .block {
border: 2px solid #111;
border-radius: 14px;
padding: 10px;
background: #fff;
margin-bottom: 10px;
}
.drawer code {
background: #f2f2f2;
padding: 2px 6px;
border-radius: 8px;
}
.drawer::-webkit-scrollbar {
width: 12px;
}
.drawer::-webkit-scrollbar-thumb {
background: rgba(17, 17, 17, 0.45);
border-radius: 999px;
border: 3px solid rgba(255, 255, 255, 0.75);
}
Realistic expectations for scrollbars
Scrollbar styling is not fully standardized across all browsers. The most portable technique is simply:
make your overlay panel scroll with overflow: auto, and keep it usable even with default scrollbar
styles.
Common Overlay Pitfalls
1) Forgetting position: relative
If your overlay is position: absolute but your parent isn’t positioned, the overlay may attach to the
wrong ancestor (often the page).
Put position: relative on the container you want to cover.
2) Content hidden behind the overlay
If text looks “muted” or unclickable, make sure content is above the overlay layer:
give the content position: relative and z-index: 3.
3) Overlay blocks clicks
If you’re using a pseudo-element overlay and it sits above your buttons, add:
pointer-events: none; to the overlay pseudo-element.
4) z-index “does nothing”
z-index only works on positioned elements (position not static).
Also, parents can create their own stacking contexts (for example with transform, filter,
or opacity).
If layering looks wrong, check whether a parent is creating a new stacking context. Learn more in the CSS Z-Index Interactive Tutorial.
CSS Overlay Wrap-Up
You now have a full overlay toolkit:
- Color overlays using pseudo-elements (clean and flexible)
- Image overlays and text on images with readable layering
- Overlay effects like hover reveals and blends
- Overlay filters with
filterandbackdrop-filter - Overlay panels with scrollable content and optional scrollbar styling
With these techniques, you can create versatile and visually appealing overlays for various UI patterns.
Further Reading
- Learn more about pseudo-elements in the CSS ::before and ::after Pseudo-Elements Interactive Tutorial.
- Learn more about stacking contexts in the CSS Stacking Context Interactive Tutorial.
- Learn more about filters in the CSS Filters Interactive Tutorial.
-
Learn more about
mix-blend-modein the CSS Mix Blend Mode Interactive Tutorial.
