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.

Pure CSS MIT licensed
Live Demo Open in tab
Open in playground

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>
.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-delay of 0.05s on 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.2 so the intensity can be overridden per card.
  • Pair with a ::before pseudo-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 filter promotes the element to a new stacking context, which means z-index relationships relative to non-filtered siblings may change unexpectedly on hover.
  • On Firefox, filter transitions can lag by 1–2 frames when combined with transform on the same element — split them onto parent/child to avoid this.
  • If the element being filtered contains a position: fixed descendant, that descendant will be incorrectly clipped to the filtered ancestor's bounding box in all browsers.

Browser support

ChromeSafariFirefoxEdge
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.

Search CodeFronts

Loading…