There is a version of frontend development where every problem gets solved by adding a dependency. Scroll animations? Install a library. Complex layout? Install a library. A dropdown? Install a library.
I did that for years. I now think the boring path is almost always the better one, and I have a fairly specific definition of boring.
What "boring" means here
Boring CSS is CSS that has been in every browser since roughly 2015 and will be in every browser until roughly 2100:
gridandflexboxfor layoutclamp()for fluid typeposition: stickyfor the headeraspect-ratioto reserve space- Custom properties for theming
prefers-reduced-motionfor sanity
No build step, no runtime, no polyfill. It renders the same on the server and the client.
A concrete audit
Take a product page. The brief called for a sticky header, a horizontally scrolling gallery, a sticky "buy" rail on desktop, and staggered fade-ins on scroll.
First attempt would have been roughly:
- A scroll-animation library
- A carousel library
- A sticky positioning polyfill
- A class-name utility layer with a build plugin
What shipped instead:
.sticky-rail {
position: sticky;
top: 5rem;
}
/* Fluid type without a single media query */
.headline {
font-size: clamp(1.75rem, 1rem + 3vw, 3.5rem);
line-height: 1.05;
text-wrap: balance;
}
/* Horizontal scroll, done by the browser */
.gallery {
display: grid;
grid-auto-flow: column;
grid-auto-columns: min(22rem, 78vw);
overflow-x: auto;
scroll-snap-type: x mandatory;
overscroll-behavior-x: contain;
}
.gallery > * {
scroll-snap-align: center;
}Native scroll snap is a better carousel than the library version: momentum, keyboard support, touch physics, and accessibility all come free from the platform.
For the fade-ins, the boring answer is the one people find unsatisfying: don't.
@media (prefers-reduced-motion: no-preference) {
.reveal { animation: rise 0.4s steps(6, end) both; }
}If an element animates in on scroll, the user has already scrolled, which means they were in a hurry. Animating at them is a small tax on the most impatient visitors.
The result: 40kb lighter, no layout shift, better a11y, and less code to maintain.
Where libraries earn their keep
I'm not absolutist. Some things genuinely should not be hand-rolled:
- Date parsing and formatting.
Intlis good but the edge cases are endless. - Complex data tables. Virtualisation, sorting, sticky headers, keyboard nav.
- Rich text editing. Do not write an editor.
- Anything with a spec. WebSocket protocols, media codecs.
The rule I'd propose: if it's a layout or styling problem, solve it with CSS. If it's a data or protocol problem, use a library.
The test
Before adding a dependency, I ask: what is the browser already doing that I would be reimplementing?
The answer is usually more than you'd expect. Scroll snapping. Stacking contexts.
content-visibility: auto. View transitions. Container queries. :has(). The gap
selector.
A lot of frontend performance work is really just a willingness to use features that shipped after your last conference talk.
Filed under MAY 21 '26
All posts →