Greyscale to Color Saturate Fade
Three profile cards transition from filter: saturate(0) brightness(0.7) to full color, creating a greyscale-to-vivid reveal with cascading animation delays.
Published
The code
<div class="fi-13">
<div class="fi-13__photo">
<div class="fi-13__dot"></div>
<div class="fi-13__overlay">
<div class="fi-13__pname">Alex Chen</div>
<div class="fi-13__prole">Lead Designer</div>
</div>
</div>
<div class="fi-13__photo">
<div class="fi-13__dot"></div>
<div class="fi-13__overlay">
<div class="fi-13__pname">Mia Torres</div>
<div class="fi-13__prole">Engineering Lead</div>
</div>
</div>
<div class="fi-13__photo">
<div class="fi-13__dot"></div>
<div class="fi-13__overlay">
<div class="fi-13__pname">Sam Osei</div>
<div class="fi-13__prole">Product Manager</div>
</div>
</div>
</div><div class="fi-13">
<div class="fi-13__photo">
<div class="fi-13__dot"></div>
<div class="fi-13__overlay">
<div class="fi-13__pname">Alex Chen</div>
<div class="fi-13__prole">Lead Designer</div>
</div>
</div>
<div class="fi-13__photo">
<div class="fi-13__dot"></div>
<div class="fi-13__overlay">
<div class="fi-13__pname">Mia Torres</div>
<div class="fi-13__prole">Engineering Lead</div>
</div>
</div>
<div class="fi-13__photo">
<div class="fi-13__dot"></div>
<div class="fi-13__overlay">
<div class="fi-13__pname">Sam Osei</div>
<div class="fi-13__prole">Product Manager</div>
</div>
</div>
</div>.fi-13 {
--bg: #111;
--text: #fafafa;
font-family: 'Plus Jakarta Sans',sans-serif;
min-height: 340px;
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
flex-wrap: wrap;
padding: 40px;
overflow: hidden;
}
.fi-13 *,
.fi-13 *::before,
.fi-13 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.fi-13 ::selection {
background: #888;
color: #fff;
}
/* Greyscale → color: filter:saturate(0) opacity:0 → saturate(1) opacity:1 */
.fi-13__photo {
position: relative;
border-radius: 16px;
overflow: hidden;
flex: 1;
min-width: 140px;
max-width: 170px;
aspect-ratio: 3/4;
opacity: 0;
filter: saturate(0) brightness(.7);
animation: fi-13-color-in 1s ease-out forwards;
cursor: pointer;
}
.fi-13__photo:nth-child(1) {
--d: .1s;
}
.fi-13__photo:nth-child(2) {
--d: .3s;
}
.fi-13__photo:nth-child(3) {
--d: .5s;
}
.fi-13__photo {
animation-delay: var(--d);
}
.fi-13__photo:hover {
filter: saturate(1.2) brightness(1.05)!important;
transform: scale(1.03);
transition: all .3s;
}
/* Colorful gradient "photos" */
.fi-13__photo:nth-child(1) {
background: linear-gradient(160deg,#f97316,#ec4899,#8b5cf6);
}
.fi-13__photo:nth-child(2) {
background: linear-gradient(160deg,#06b6d4,#3b82f6,#a855f7);
}
.fi-13__photo:nth-child(3) {
background: linear-gradient(160deg,#10b981,#14b8a6,#0ea5e9);
}
.fi-13__overlay {
position: absolute;
inset: 0;
background: linear-gradient(to top,rgba(0,0,0,.7) 0%,transparent 60%);
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 16px;
}
.fi-13__pname {
font-size: .85rem;
font-weight: 700;
color: #fff;
margin-bottom: 3px;
}
.fi-13__prole {
font-size: .7rem;
color: rgba(255,255,255,.6);
}
.fi-13__dot {
position: absolute;
top: 14px;
right: 14px;
width: 8px;
height: 8px;
border-radius: 50%;
background: #4ade80;
box-shadow: 0 0 6px #4ade80;
}
@keyframes fi-13-color-in {
to {
opacity: 1;
filter: saturate(1) brightness(1);
}
}
@media (prefers-reduced-motion: reduce) {
.fi-13 * {
animation: none!important;
opacity: 1!important;
filter: none!important;
}
}.fi-13 {
--bg: #111;
--text: #fafafa;
font-family: 'Plus Jakarta Sans',sans-serif;
min-height: 340px;
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
flex-wrap: wrap;
padding: 40px;
overflow: hidden;
}
.fi-13 *,
.fi-13 *::before,
.fi-13 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.fi-13 ::selection {
background: #888;
color: #fff;
}
/* Greyscale → color: filter:saturate(0) opacity:0 → saturate(1) opacity:1 */
.fi-13__photo {
position: relative;
border-radius: 16px;
overflow: hidden;
flex: 1;
min-width: 140px;
max-width: 170px;
aspect-ratio: 3/4;
opacity: 0;
filter: saturate(0) brightness(.7);
animation: fi-13-color-in 1s ease-out forwards;
cursor: pointer;
}
.fi-13__photo:nth-child(1) {
--d: .1s;
}
.fi-13__photo:nth-child(2) {
--d: .3s;
}
.fi-13__photo:nth-child(3) {
--d: .5s;
}
.fi-13__photo {
animation-delay: var(--d);
}
.fi-13__photo:hover {
filter: saturate(1.2) brightness(1.05)!important;
transform: scale(1.03);
transition: all .3s;
}
/* Colorful gradient "photos" */
.fi-13__photo:nth-child(1) {
background: linear-gradient(160deg,#f97316,#ec4899,#8b5cf6);
}
.fi-13__photo:nth-child(2) {
background: linear-gradient(160deg,#06b6d4,#3b82f6,#a855f7);
}
.fi-13__photo:nth-child(3) {
background: linear-gradient(160deg,#10b981,#14b8a6,#0ea5e9);
}
.fi-13__overlay {
position: absolute;
inset: 0;
background: linear-gradient(to top,rgba(0,0,0,.7) 0%,transparent 60%);
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 16px;
}
.fi-13__pname {
font-size: .85rem;
font-weight: 700;
color: #fff;
margin-bottom: 3px;
}
.fi-13__prole {
font-size: .7rem;
color: rgba(255,255,255,.6);
}
.fi-13__dot {
position: absolute;
top: 14px;
right: 14px;
width: 8px;
height: 8px;
border-radius: 50%;
background: #4ade80;
box-shadow: 0 0 6px #4ade80;
}
@keyframes fi-13-color-in {
to {
opacity: 1;
filter: saturate(1) brightness(1);
}
}
@media (prefers-reduced-motion: reduce) {
.fi-13 * {
animation: none!important;
opacity: 1!important;
filter: none!important;
}
}Here's a working CSS Fade In Animation Design from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Greyscale to Color Saturate Fade
Source: https://codefronts.com/motion/css-fade-in-animation/greyscale-to-color-saturate-fade/
Three profile cards transition from filter: saturate(0) brightness(0.7) to full color, creating a greyscale-to-vivid reveal with cascading animation delays.
## HTML
```html
<div class="fi-13">
<div class="fi-13__photo">
<div class="fi-13__dot"></div>
<div class="fi-13__overlay">
<div class="fi-13__pname">Alex Chen</div>
<div class="fi-13__prole">Lead Designer</div>
</div>
</div>
<div class="fi-13__photo">
<div class="fi-13__dot"></div>
<div class="fi-13__overlay">
<div class="fi-13__pname">Mia Torres</div>
<div class="fi-13__prole">Engineering Lead</div>
</div>
</div>
<div class="fi-13__photo">
<div class="fi-13__dot"></div>
<div class="fi-13__overlay">
<div class="fi-13__pname">Sam Osei</div>
<div class="fi-13__prole">Product Manager</div>
</div>
</div>
</div>
```
## CSS
```css
.fi-13 {
--bg: #111;
--text: #fafafa;
font-family: 'Plus Jakarta Sans',sans-serif;
min-height: 340px;
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
flex-wrap: wrap;
padding: 40px;
overflow: hidden;
}
.fi-13 *,
.fi-13 *::before,
.fi-13 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.fi-13 ::selection {
background: #888;
color: #fff;
}
/* Greyscale → color: filter:saturate(0) opacity:0 → saturate(1) opacity:1 */
.fi-13__photo {
position: relative;
border-radius: 16px;
overflow: hidden;
flex: 1;
min-width: 140px;
max-width: 170px;
aspect-ratio: 3/4;
opacity: 0;
filter: saturate(0) brightness(.7);
animation: fi-13-color-in 1s ease-out forwards;
cursor: pointer;
}
.fi-13__photo:nth-child(1) {
--d: .1s;
}
.fi-13__photo:nth-child(2) {
--d: .3s;
}
.fi-13__photo:nth-child(3) {
--d: .5s;
}
.fi-13__photo {
animation-delay: var(--d);
}
.fi-13__photo:hover {
filter: saturate(1.2) brightness(1.05)!important;
transform: scale(1.03);
transition: all .3s;
}
/* Colorful gradient "photos" */
.fi-13__photo:nth-child(1) {
background: linear-gradient(160deg,#f97316,#ec4899,#8b5cf6);
}
.fi-13__photo:nth-child(2) {
background: linear-gradient(160deg,#06b6d4,#3b82f6,#a855f7);
}
.fi-13__photo:nth-child(3) {
background: linear-gradient(160deg,#10b981,#14b8a6,#0ea5e9);
}
.fi-13__overlay {
position: absolute;
inset: 0;
background: linear-gradient(to top,rgba(0,0,0,.7) 0%,transparent 60%);
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 16px;
}
.fi-13__pname {
font-size: .85rem;
font-weight: 700;
color: #fff;
margin-bottom: 3px;
}
.fi-13__prole {
font-size: .7rem;
color: rgba(255,255,255,.6);
}
.fi-13__dot {
position: absolute;
top: 14px;
right: 14px;
width: 8px;
height: 8px;
border-radius: 50%;
background: #4ade80;
box-shadow: 0 0 6px #4ade80;
}
@keyframes fi-13-color-in {
to {
opacity: 1;
filter: saturate(1) brightness(1);
}
}
@media (prefers-reduced-motion: reduce) {
.fi-13 * {
animation: none!important;
opacity: 1!important;
filter: none!important;
}
}
```Here's a working CSS Fade In Animation Design from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Greyscale to Color Saturate Fade
Source: https://codefronts.com/motion/css-fade-in-animation/greyscale-to-color-saturate-fade/
Three profile cards transition from filter: saturate(0) brightness(0.7) to full color, creating a greyscale-to-vivid reveal with cascading animation delays.
## HTML
```html
<div class="fi-13">
<div class="fi-13__photo">
<div class="fi-13__dot"></div>
<div class="fi-13__overlay">
<div class="fi-13__pname">Alex Chen</div>
<div class="fi-13__prole">Lead Designer</div>
</div>
</div>
<div class="fi-13__photo">
<div class="fi-13__dot"></div>
<div class="fi-13__overlay">
<div class="fi-13__pname">Mia Torres</div>
<div class="fi-13__prole">Engineering Lead</div>
</div>
</div>
<div class="fi-13__photo">
<div class="fi-13__dot"></div>
<div class="fi-13__overlay">
<div class="fi-13__pname">Sam Osei</div>
<div class="fi-13__prole">Product Manager</div>
</div>
</div>
</div>
```
## CSS
```css
.fi-13 {
--bg: #111;
--text: #fafafa;
font-family: 'Plus Jakarta Sans',sans-serif;
min-height: 340px;
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
flex-wrap: wrap;
padding: 40px;
overflow: hidden;
}
.fi-13 *,
.fi-13 *::before,
.fi-13 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.fi-13 ::selection {
background: #888;
color: #fff;
}
/* Greyscale → color: filter:saturate(0) opacity:0 → saturate(1) opacity:1 */
.fi-13__photo {
position: relative;
border-radius: 16px;
overflow: hidden;
flex: 1;
min-width: 140px;
max-width: 170px;
aspect-ratio: 3/4;
opacity: 0;
filter: saturate(0) brightness(.7);
animation: fi-13-color-in 1s ease-out forwards;
cursor: pointer;
}
.fi-13__photo:nth-child(1) {
--d: .1s;
}
.fi-13__photo:nth-child(2) {
--d: .3s;
}
.fi-13__photo:nth-child(3) {
--d: .5s;
}
.fi-13__photo {
animation-delay: var(--d);
}
.fi-13__photo:hover {
filter: saturate(1.2) brightness(1.05)!important;
transform: scale(1.03);
transition: all .3s;
}
/* Colorful gradient "photos" */
.fi-13__photo:nth-child(1) {
background: linear-gradient(160deg,#f97316,#ec4899,#8b5cf6);
}
.fi-13__photo:nth-child(2) {
background: linear-gradient(160deg,#06b6d4,#3b82f6,#a855f7);
}
.fi-13__photo:nth-child(3) {
background: linear-gradient(160deg,#10b981,#14b8a6,#0ea5e9);
}
.fi-13__overlay {
position: absolute;
inset: 0;
background: linear-gradient(to top,rgba(0,0,0,.7) 0%,transparent 60%);
display: flex;
flex-direction: column;
justify-content: flex-end;
padding: 16px;
}
.fi-13__pname {
font-size: .85rem;
font-weight: 700;
color: #fff;
margin-bottom: 3px;
}
.fi-13__prole {
font-size: .7rem;
color: rgba(255,255,255,.6);
}
.fi-13__dot {
position: absolute;
top: 14px;
right: 14px;
width: 8px;
height: 8px;
border-radius: 50%;
background: #4ade80;
box-shadow: 0 0 6px #4ade80;
}
@keyframes fi-13-color-in {
to {
opacity: 1;
filter: saturate(1) brightness(1);
}
}
@media (prefers-reduced-motion: reduce) {
.fi-13 * {
animation: none!important;
opacity: 1!important;
filter: none!important;
}
}
```How this works
Each photo element starts at opacity: 0; filter: saturate(0) brightness(.7). The keyframe fi-13-color-in transitions to opacity: 1; filter: saturate(1) brightness(1). CSS can animate multiple filter functions in a single filter property as long as both the from and to states declare the same function list in the same order — mismatched lists cause an abrupt jump rather than an interpolation.
The greyscale-to-color effect reads as a meaningful narrative: the UI presents a world in greyscale that gradually gains colour as it loads, evoking a film developing or a scene coming to life. Each card is delayed 200ms from its predecessor. Hover boosts saturation beyond 1.0 (saturate(1.2)) and adds a small scale, providing an active state that contrasts with both the initial greyscale and the settled colour.
Make it yours
- Adjust color transition speed: change duration from
1sto1.5sfor a slower, more dramatic color emergence. - Add
filter: contrast(1.1)to the final state for photos that 'pop' with extra vibrancy. - Trigger the animation on hover instead of load: move the animation to a
:hoverrule for on-demand colorization. - Change cascade stagger from
.2sto.4sbetween items for a more deliberate reveal sequence.
Gotchas — read before shipping
filterfunctions must match exactly between keyframe states —saturate(0)andbrightness(.7)infrommust both appear intoassaturate(1)andbrightness(1). Mismatched function lists cause an instant jump rather than smooth interpolation.- Hover applies
filter: saturate(1.2)— this overrides the animation's forwarded state. If the user mouses over during the animation, the filter snaps to the hover value. Prevent this by only applying hover after aanimation-durationdelay viapointer-events: nonethat you remove in JS. - Animating
filteris composited on the GPU but still creates a new stacking context — elements inside a filtered ancestor cannot appear above siblings outside it usingz-index.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 56+ | 9+ | 35+ | 56+ |
filter: saturate() + brightness() combined in one property interpolates correctly in all evergreen browsers