Entry 03
Styles that apply only when a scroller overflows
Use it on any row that may or may not fit, such as tabs, a toolbar or a table wrapper. It fades the edges only when there is something to scroll to. It replaces a ResizeObserver that compares scrollWidth with clientWidth.
Code
@keyframes overflowing {
from, to { --fade: 2rem; }
}
.scroller {
--fade: 0px;
overflow-x: auto;
animation: overflowing linear;
animation-timeline: scroll(self inline);
mask-image: linear-gradient(
to right,
transparent,
#000 var(--fade),
#000 calc(100% - var(--fade)),
transparent
);
}To fade only the side that still has content, register the two lengths and give the keyframes different ends:
@property --fade-start {
syntax: "<length>";
inherits: false;
initial-value: 0px;
}
@property --fade-end {
syntax: "<length>";
inherits: false;
initial-value: 0px;
}
@keyframes edges {
from { --fade-start: 0px; --fade-end: 2rem; }
10%, 90% { --fade-start: 2rem; --fade-end: 2rem; }
to { --fade-start: 2rem; --fade-end: 0px; }
}
.scroller-edges {
overflow-x: auto;
animation: edges linear;
animation-timeline: scroll(self inline);
mask-image: linear-gradient(
to right,
transparent,
#000 var(--fade-start),
#000 calc(100% - var(--fade-end)),
transparent
);
}Why it works, the rules and the credits
Why it works
- A scroll timeline is inactive while its scroller has nothing to scroll. An animation on an inactive timeline has no effect, so the keyframes apply only when the content overflows.
- In the first block
fromandtohold the same value. Scroll position changes nothing and the animation is an on/off switch. - The keyframes set a custom property, and custom properties inherit. The scroller and everything inside it can read the result.
- A mask fades the content itself, so it works over any background. An overlay gradient has to match the color behind it.
Rules
- Write
animation-timelineafteranimation. The shorthand resets the timeline. - Declare the default outside the keyframes, as
--fade: 0pxdoes here. - Any value can go in the keyframes, including keywords. Stripe flips its navigation to
row-reversethis way when a translation is too long to fit. - Write no fallback. Firefox has no scroll timelines, so the row scrolls there without the fade.
Support
animation-timeline in Chrome 115 and Safari 26. Firefox has it in Nightly only.
Credits
Borrowed from: Stripe, in the navigation on stripe.com · Bramus Van Damme, Detect if an element can scroll or not with only CSS
Docs: MDN: scroll()