DEV Community

Cover image for 10 CSS Properties That Quietly Deleted My JavaScript
Artclick
Artclick

Posted on

10 CSS Properties That Quietly Deleted My JavaScript

Every few months I go back through an old project and find a chunk of JavaScript solving a problem that CSS now solves on its own — a resize listener recalculating an aspect ratio, a scroll handler smoothing out anchor links, a canvas hack blurring a background. None of it was bad code when it was written. It's just that the platform quietly caught up, and the JavaScript never got removed.

Here are ten CSS properties that, between them, have deleted a genuinely large amount of JavaScript from my projects — grouped by what they actually replace, not just listed in isolation.

Sizing and layout

aspect-ratio

Keeping an image, video, or placeholder box at a fixed proportion used to mean either padding-hack tricks (padding-top: 56.25% and a positioned child) or a resize listener recalculating a pixel height on every viewport change:

function setAspectRatio() {
  const width = container.offsetWidth;
  image.style.height = `${width * (9 / 16)}px`;
}
window.addEventListener('resize', setAspectRatio);
setAspectRatio();
Enter fullscreen mode Exit fullscreen mode

One line replaces all of it:

.image {
  aspect-ratio: 16 / 9;
  object-fit: cover;
}
Enter fullscreen mode Exit fullscreen mode

No resize listener means no layout thrashing on every scroll-triggered resize event either — the browser's layout engine handles the ratio natively, which is a different performance category entirely from anything running in JavaScript on the main thread.

object-fit

This one pairs directly with aspect-ratio above, but it's worth calling out on its own: fitting an image into a fixed box without distorting it used to mean swapping the <img> for a background-image and juggling background-size/background-position — which quietly breaks things like alt text, right-click-to-save, and lazy-loading attributes that only work on real <img> elements.

.card-image {
  height: 200px;
  object-fit: cover; /* or contain, to letterbox instead of crop */
}
Enter fullscreen mode Exit fullscreen mode

Keeping the actual <img> tag and just changing how its content fits inside its box is the whole point — everything else about the element stays intact.

gap

Spacing between flex or grid children used to mean margins on every child except the last one — which is exactly as fiddly as it sounds, and gets worse the moment the layout wraps or the item count changes dynamically.

.container {
  display: flex;
  gap: 16px;
}
Enter fullscreen mode Exit fullscreen mode

gap applies consistently on both axes and doesn't care how many items are in the container or whether the layout wraps — there's no "last child" edge case to special-case anymore.

Visual effects that used to need canvas

backdrop-filter

A frosted-glass modal or header used to mean capturing a screenshot of whatever's behind an element (commonly via html2canvas) and blurring that screenshot manually — expensive, and it goes stale the instant anything behind it moves.

.modal {
  background-color: rgba(255, 255, 255, 0.5);
  backdrop-filter: blur(10px);
}
Enter fullscreen mode Exit fullscreen mode

Because this runs in the compositor rather than in a canvas snapshot, the blur updates live as whatever's behind it scrolls or animates — something the screenshot approach could never do without re-capturing on every frame.

clip-path

Non-rectangular shapes — a diagonal-cut hero image, a hexagonal avatar, a chevron divider — used to mean either an SVG mask asset or manual canvas drawing.

.image {
  clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
}
Enter fullscreen mode Exit fullscreen mode

The genuinely useful part is that clip-path transitions and animates like any other CSS property, so a shape can morph on hover or scroll without touching canvas or SVG animation APIs at all.

Scrolling and interaction

scroll-behavior

Smooth-scrolling to an anchor or back to top used to be window.scrollTo({ top: 0, behavior: 'smooth' }) wired to a click handler, repeated for every scroll trigger on the page.

html {
  scroll-behavior: smooth;
}
Enter fullscreen mode Exit fullscreen mode

One line applies it to every same-page navigation and hash link on the entire site by default — including ones you didn't remember to wire up a click handler for, which is usually the actual source of the "why doesn't this one link scroll smoothly" bug reports.

overscroll-behavior

Stopping a scrollable sidebar or modal from also scrolling the page behind it once it hits its own boundary — the "scroll chaining" problem — used to mean capturing touchstart/touchmove and manually blocking the default behavior at the edges.

.sidebar {
  overscroll-behavior: contain;
}
Enter fullscreen mode Exit fullscreen mode

contain stops scroll from chaining to the parent without disabling scrolling inside the element itself, which is the specific balance that was genuinely fiddly to get right by hand.

pointer-events

Toggling whether an element can be clicked — commonly an overlay that should be visible but not interactive until some condition is met — used to mean a JavaScript branch flipping either a class or the style property directly.

.overlay {
  pointer-events: none;
}
Enter fullscreen mode Exit fullscreen mode

The real value here is that it composes with other CSS selectors for free — pointer-events: none combined with :disabled, a data attribute, or a parent's :has() state means the interactivity toggle can live entirely in your existing CSS logic instead of a separate JavaScript branch.

Text and theming

text-overflow

Truncating text that's too long for its container with a trailing ellipsis used to mean measuring rendered text width in JavaScript and manually slicing the string until it fit — recalculated on every resize and every content change.

.card-title {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
Enter fullscreen mode Exit fullscreen mode

Three lines, and it re-truncates correctly on every resize automatically, because it's the browser's own text-measurement engine doing the work rather than an approximation running in JavaScript.

color-scheme

Before this, even a prefers-color-scheme media query only styled your own elements — native browser UI like form controls, scrollbars, and the page background behind your content stayed light, creating an obvious mismatched flash around anything you hadn't explicitly restyled.

:root {
  color-scheme: light dark;
}
Enter fullscreen mode Exit fullscreen mode

This tells the browser your page actually supports both schemes, so native form controls, scrollbars, and default backgrounds switch to match automatically — it's not a replacement for prefers-color-scheme (you'll still want that for your own custom-styled elements), but it closes the gap on everything you don't control directly.

The pattern underneath all ten

None of these are exotic APIs — they're all comfortably supported and have been for a while. The pattern worth noticing isn't any individual property, it's what they have in common: each one moved something out of a resize or scroll listener running on the main thread and into a browser engine designed specifically to do that one calculation efficiently. That's a different performance category, not just less code — and it's worth periodically re-auditing an older codebase for exactly this kind of JavaScript, written for a real gap that's since closed.


At ArtClick, we build fast, scalable WordPress websites, company websites and custom web systems that balance design, performance and long-term maintainability. Whether you're starting from scratch or improving an existing platform, we'd love to help.

https://artclickdev.com/

Top comments (0)