20 CSS Image Hover Effects 06 / 20
CSS Grayscale to Color Image Hover
Team-bio technique where images start in desaturated grayscale and animate to full colour with a slight saturation boost on hover.
The code
<div class="ih-06">
<div class="ih-06__grid">
<div class="ih-06__card">
<div class="ih-06__img ih-06__img--1"><span class="ih-06__emoji">👩💻</span></div>
<div class="ih-06__info">
<p class="ih-06__name">Aria Chen</p>
<p class="ih-06__role">Lead Engineer</p>
</div>
</div>
<div class="ih-06__card">
<div class="ih-06__img ih-06__img--2"><span class="ih-06__emoji">👨🎨</span></div>
<div class="ih-06__info">
<p class="ih-06__name">Marcus Webb</p>
<p class="ih-06__role">Creative Director</p>
</div>
</div>
<div class="ih-06__card">
<div class="ih-06__img ih-06__img--3"><span class="ih-06__emoji">👩🔬</span></div>
<div class="ih-06__info">
<p class="ih-06__name">Priya Nair</p>
<p class="ih-06__role">Research Scientist</p>
</div>
</div>
<div class="ih-06__card">
<div class="ih-06__img ih-06__img--4"><span class="ih-06__emoji">👨💼</span></div>
<div class="ih-06__info">
<p class="ih-06__name">Leo Santos</p>
<p class="ih-06__role">Product Strategist</p>
</div>
</div>
</div>
</div> <div class="ih-06">
<div class="ih-06__grid">
<div class="ih-06__card">
<div class="ih-06__img ih-06__img--1"><span class="ih-06__emoji">👩💻</span></div>
<div class="ih-06__info">
<p class="ih-06__name">Aria Chen</p>
<p class="ih-06__role">Lead Engineer</p>
</div>
</div>
<div class="ih-06__card">
<div class="ih-06__img ih-06__img--2"><span class="ih-06__emoji">👨🎨</span></div>
<div class="ih-06__info">
<p class="ih-06__name">Marcus Webb</p>
<p class="ih-06__role">Creative Director</p>
</div>
</div>
<div class="ih-06__card">
<div class="ih-06__img ih-06__img--3"><span class="ih-06__emoji">👩🔬</span></div>
<div class="ih-06__info">
<p class="ih-06__name">Priya Nair</p>
<p class="ih-06__role">Research Scientist</p>
</div>
</div>
<div class="ih-06__card">
<div class="ih-06__img ih-06__img--4"><span class="ih-06__emoji">👨💼</span></div>
<div class="ih-06__info">
<p class="ih-06__name">Leo Santos</p>
<p class="ih-06__role">Product Strategist</p>
</div>
</div>
</div>
</div>.ih-06,.ih-06 *,.ih-06 *::before,.ih-06 *::after{margin:0;padding:0;box-sizing:border-box}
.ih-06 ::selection{background:#22d3ee;color:#000}
.ih-06{
--accent:#22d3ee;--bg:#09090f;--text:#f1f5f9;--muted:#475569;
--duration:0.55s;--ease:cubic-bezier(0.25,0.46,0.45,0.94);
font-family:system-ui,sans-serif;background:var(--bg);padding:40px 24px;
min-height: 100vh;display:flex;align-items:center;justify-content:center;
}
.ih-06__grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;max-width:780px;width:100%}
.ih-06__card{position:relative;border-radius:10px;overflow:hidden;cursor:pointer;aspect-ratio:3/4}
/* filter:grayscale() + saturate() is the key technique — hardware accelerated */
.ih-06__img{
position:absolute;inset:0;width:100%;height:100%;
display:flex;align-items:center;justify-content:center;
filter:grayscale(1) brightness(0.75) saturate(0);
transition:filter var(--duration) var(--ease), transform var(--duration) var(--ease);
}
.ih-06__card:hover .ih-06__img{filter:grayscale(0) brightness(1) saturate(1.2);transform:scale(1.04)}
.ih-06__img--1{background: linear-gradient(135deg,#1e3a5f,#1e40af,#3b82f6,#93c5fd)}
.ih-06__img--2{background: linear-gradient(135deg,#431407,#9a3412,#ef4444,#fca5a5)}
.ih-06__img--3{background: linear-gradient(135deg,#064e3b,#065f46,#10b981,#6ee7b7)}
.ih-06__img--4{background: linear-gradient(135deg,#3b0764,#7e22ce,#a855f7,#e9d5ff)}
.ih-06__emoji{font-size:52px;opacity:0.6;filter:grayscale(1);transition:filter var(--duration) var(--ease)}
.ih-06__card:hover .ih-06__emoji{filter:grayscale(0)}
.ih-06__info{position:absolute;bottom:0;left:0;right:0;padding:12px;background:linear-gradient(to top,rgba(0,0,0,0.8),transparent)}
.ih-06__name{font-size:12px;font-weight:700;color:var(--text)}
.ih-06__role{font-size:10px;color:var(--muted);margin-top:2px;transition:color var(--duration) var(--ease)}
.ih-06__card:hover .ih-06__role{color:var(--accent)}
/* Coloured border reveal on hover */
.ih-06__card::after{
content:'';position:absolute;inset:0;border-radius:10px;
border:2px solid transparent;
transition:border-color var(--duration) var(--ease);
pointer-events:none;
}
.ih-06__card:hover::after{border-color:rgba(34,211,238,0.4)}
@media(prefers-reduced-motion:reduce){.ih-06__img,.ih-06__emoji,.ih-06__role,.ih-06__card::after{transition:none}.ih-06__img{filter:none}} .ih-06,.ih-06 *,.ih-06 *::before,.ih-06 *::after{margin:0;padding:0;box-sizing:border-box}
.ih-06 ::selection{background:#22d3ee;color:#000}
.ih-06{
--accent:#22d3ee;--bg:#09090f;--text:#f1f5f9;--muted:#475569;
--duration:0.55s;--ease:cubic-bezier(0.25,0.46,0.45,0.94);
font-family:system-ui,sans-serif;background:var(--bg);padding:40px 24px;
min-height: 100vh;display:flex;align-items:center;justify-content:center;
}
.ih-06__grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;max-width:780px;width:100%}
.ih-06__card{position:relative;border-radius:10px;overflow:hidden;cursor:pointer;aspect-ratio:3/4}
/* filter:grayscale() + saturate() is the key technique — hardware accelerated */
.ih-06__img{
position:absolute;inset:0;width:100%;height:100%;
display:flex;align-items:center;justify-content:center;
filter:grayscale(1) brightness(0.75) saturate(0);
transition:filter var(--duration) var(--ease), transform var(--duration) var(--ease);
}
.ih-06__card:hover .ih-06__img{filter:grayscale(0) brightness(1) saturate(1.2);transform:scale(1.04)}
.ih-06__img--1{background: linear-gradient(135deg,#1e3a5f,#1e40af,#3b82f6,#93c5fd)}
.ih-06__img--2{background: linear-gradient(135deg,#431407,#9a3412,#ef4444,#fca5a5)}
.ih-06__img--3{background: linear-gradient(135deg,#064e3b,#065f46,#10b981,#6ee7b7)}
.ih-06__img--4{background: linear-gradient(135deg,#3b0764,#7e22ce,#a855f7,#e9d5ff)}
.ih-06__emoji{font-size:52px;opacity:0.6;filter:grayscale(1);transition:filter var(--duration) var(--ease)}
.ih-06__card:hover .ih-06__emoji{filter:grayscale(0)}
.ih-06__info{position:absolute;bottom:0;left:0;right:0;padding:12px;background:linear-gradient(to top,rgba(0,0,0,0.8),transparent)}
.ih-06__name{font-size:12px;font-weight:700;color:var(--text)}
.ih-06__role{font-size:10px;color:var(--muted);margin-top:2px;transition:color var(--duration) var(--ease)}
.ih-06__card:hover .ih-06__role{color:var(--accent)}
/* Coloured border reveal on hover */
.ih-06__card::after{
content:'';position:absolute;inset:0;border-radius:10px;
border:2px solid transparent;
transition:border-color var(--duration) var(--ease);
pointer-events:none;
}
.ih-06__card:hover::after{border-color:rgba(34,211,238,0.4)}
@media(prefers-reduced-motion:reduce){.ih-06__img,.ih-06__emoji,.ih-06__role,.ih-06__card::after{transition:none}.ih-06__img{filter:none}}How this works
The filter CSS property accepts a space-separated list of functions, so filter: grayscale(1) brightness(0.75) saturate(0) can be transitioned as a single unit to filter: grayscale(0) brightness(1) saturate(1.2) on hover. The browser interpolates all three values simultaneously, giving a smooth monochrome-to-colour transition in a single line of CSS.
The slight initial brightness reduction (0.75) ensures the grayscale version does not appear washed out, while the hover saturate(1.2) overshoots baseline saturation by 20 % to make the colour "pop" more dramatically than the original. A scoped pseudo-element (::after) with a transparent-to-brand-colour border animates in simultaneously, adding a coloured frame effect without extra markup.
Customize
- Combine with a
scale(1.04)transform on hover to reinforce the "activation" feeling alongside the colour reveal. - Set different grayscale percentages per card for a partially-coloured resting state:
grayscale(0.6)keeps some colour while still appearing desaturated. - Add a
transition-delayof0.05son the filter transition when mouse-leave fires, giving a slight hold before fading back to grey. - Use a CSS custom property for the saturation boost:
--sat: 1.2so the intensity can be overridden per card. - Pair with a
::beforepseudo-element colour overlay at low opacity (e.g.,rgba(var(--brand-rgb), 0.08)) that fades in on hover for a branded tint.
Watch out for
- Animating
filterpromotes the element to a new stacking context, which meansz-indexrelationships relative to non-filtered siblings may change unexpectedly on hover. - On Firefox,
filtertransitions can lag by 1–2 frames when combined withtransformon the same element — split them onto parent/child to avoid this. - If the element being filtered contains a
position: fixeddescendant, that descendant will be incorrectly clipped to the filtered ancestor's bounding box in all browsers.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 18+ (prefixed), 53+ (unprefixed) | 6+ (prefixed), 9.1+ (unprefixed) | 35+ | 18+ (prefixed), 53+ (unprefixed) |
Include -webkit-filter as a fallback for older Safari and Chrome versions in production.