DEV Community

Cover image for 10 CSS Features That Replace JavaScript
Adrian | Creator of BigDevSoon
Adrian | Creator of BigDevSoon

Posted on • Originally published at bigdevsoon.me

10 CSS Features That Replace JavaScript

CSS can now do things that used to need JavaScript libraries, build tools, or hundreds of lines of manual DOM code. Most of these features have shipped in the last 2-3 years — but a lot of developers haven't caught up yet.

I built 10 interactive demos to show what's possible. Every demo runs in a live code editor — edit the code and see results instantly. No signup, no install.

1. Scroll Progress Bar — No JavaScript

A reading progress bar that tracks scroll position using animation-timeline: scroll(). No event listeners, no requestAnimationFrame.

.progress-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 4px;
  background: linear-gradient(90deg, #9F7AEA, #ED8936);
  transform-origin: left;
  animation: grow-bar linear;
  animation-timeline: scroll();
}

@keyframes grow-bar {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}
Enter fullscreen mode Exit fullscreen mode

What it replaces: scroll event listener + requestAnimationFrame + manual percentage math.

Browser support: Chrome 115+ (Jul 2023), Edge 115+, Safari 18+ (Sep 2024). Firefox: behind flag.

Try it live


2. Scroll Reveal Animations — No JavaScript

Cards fade in and slide up as they enter the viewport using animation-timeline: view().

.card {
  animation: reveal linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 100%;
}

@keyframes reveal {
  from { opacity: 0; transform: translateY(40px); }
  to { opacity: 1; transform: translateY(0); }
}
Enter fullscreen mode Exit fullscreen mode

What it replaces: AOS.js (14KB), GSAP ScrollTrigger, or a custom IntersectionObserver.

Browser support: Chrome 115+ (Jul 2023), Edge 115+, Safari 18+ (Sep 2024). Firefox: behind flag.

Try it live


3. Anchor-Positioned Popovers — No JavaScript

Position popovers relative to any trigger element using CSS anchor-name and anchor() functions.

.trigger { anchor-name: --btn; }

[popover] {
  position-anchor: --btn;
  top: anchor(bottom);
  left: anchor(left);
  margin-top: 8px;
}

@starting-style {
  .popover:popover-open {
    opacity: 0;
    transform: translateY(-8px);
  }
}
Enter fullscreen mode Exit fullscreen mode

What it replaces: Popper.js (8KB), Floating UI, or manual getBoundingClientRect() — 200+ lines of JS.

Browser support: Chrome 125+ (May 2024), Edge 125+, Safari 18+ (Sep 2024). Firefox: behind flag.

Try it live


4. Form Validation Styles with :has()

The form border, button state, and labels react to input validity in real time — zero JavaScript.

/* Green border when all inputs valid */
.form:has(input:valid:not(:placeholder-shown)):not(:has(input:invalid:not(:placeholder-shown))) {
  border-color: #22c55e;
}

/* Dim submit when invalid */
.form:has(input:invalid:not(:placeholder-shown)) .submit {
  opacity: 0.5;
  pointer-events: none;
}
Enter fullscreen mode Exit fullscreen mode

What it replaces: input event listeners on every field + classList toggling + validity checking.

Browser support: Chrome 105+ (Aug 2022), Safari 15.4+ (Mar 2022), Firefox 121+ (Dec 2023). Fully cross-browser.

Try it live


5. Container Queries — Component-Level Responsive

The same card switches layout based on its container width — not the viewport.

.card-wrapper {
  container-type: inline-size;
}

@container (min-width: 420px) {
  .card {
    grid-template-columns: 180px 1fr;
    grid-template-rows: none;
  }
}
Enter fullscreen mode Exit fullscreen mode

What it replaces: ResizeObserver for component-level responsiveness. Media queries only check viewport width.

Browser support: Chrome 105+ (Aug 2022), Safari 16+ (Sep 2022), Firefox 110+ (Feb 2023). Fully cross-browser.

Try it live


6. Animated Accordion — No JavaScript

Smooth open/close on native <details> elements. The browser animates between height: 0 and height: auto.

body {
  interpolate-size: allow-keywords;
}

.content {
  height: 0;
  overflow: hidden;
  opacity: 0;
  transition: height 0.4s ease, opacity 0.4s ease;
}

details[open] .content {
  height: auto;
  opacity: 1;
}
Enter fullscreen mode Exit fullscreen mode

What it replaces: Measuring scrollHeight + requestAnimationFrame. Or Headless UI, Radix Accordion, React Collapse.

Browser support: Chrome 129+ (Sep 2024), Edge 129+. Safari and Firefox: not yet supported. Progressive enhancement — accordion still works, just without animation.

Try it live


7. Native CSS Nesting — Drop Your Preprocessor

The & selector works like Sass — child rules, pseudo-classes, modifiers, all nested in one block.

.card {
  background: #1e1b2e;
  padding: 24px;
  border-radius: 12px;

  & h3 { color: #c4b5fd; }
  & p { color: #94a3b8; }

  &:hover {
    transform: translateY(-3px);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
  }

  &.featured {
    border-left: 3px solid #7c3aed;
  }
}
Enter fullscreen mode Exit fullscreen mode

What it replaces: Sass/SCSS preprocessor + node-sass/dart-sass dependency + build step. If you only used Sass for nesting, delete the dependency.

Browser support: Chrome 112+ (Apr 2023), Safari 16.5+ (May 2023), Firefox 117+ (Aug 2023). Fully cross-browser.

Try it live


8. Dynamic Color Palette with color-mix()

Derive an entire color system from one base color. color-mix() in OKLCH space — lighter, darker, and transparent variants from one variable.

:root {
  --brand: oklch(0.65 0.25 275);
  --brand-light: color-mix(in oklch, var(--brand), white 30%);
  --brand-dark: color-mix(in oklch, var(--brand), black 25%);
  --brand-subtle: color-mix(in oklch, var(--brand), transparent 85%);
}

.btn { background: var(--brand); }
.btn:hover { background: var(--brand-light); }
Enter fullscreen mode Exit fullscreen mode

What it replaces: Sass lighten()/darken()/mix(), JS color libraries, or manually hardcoded shades.

Browser support: Chrome 111+ (Mar 2023), Safari 16.2+ (Dec 2022), Firefox 113+ (May 2023). Fully cross-browser.

Try it live


9. View Transitions — State Morphing

Toggle between grid and list layout with a smooth morph. JS only toggles a class — the animation is pure CSS.

toggle.addEventListener('click', () => {
  document.startViewTransition(() => {
    container.classList.toggle('grid-view');
    container.classList.toggle('list-view');
  });
});
Enter fullscreen mode Exit fullscreen mode
.item-1 { view-transition-name: item-1; }
.item-2 { view-transition-name: item-2; }
::view-transition-group(*) { animation-duration: 0.35s; }
Enter fullscreen mode Exit fullscreen mode

What it replaces: FLIP animations, React Transition Group, Framer Motion, or manual position calculations.

Browser support: Chrome 111+ (Mar 2023), Safari 18+ (Sep 2024), Firefox 144+ (Oct 2025). Cross-browser since late 2025.

Try it live


10. Entry Animations from display: none

Animate elements from display: none using @starting-style. Dialogs fade in, cards slide up — no JS timing hacks.

.dialog {
  opacity: 1;
  transform: scale(1) translateY(0);
  transition: opacity 0.3s ease, transform 0.3s ease,
    display 0.3s allow-discrete, overlay 0.3s allow-discrete;
}

.dialog:not([open]) {
  opacity: 0;
  transform: scale(0.9) translateY(10px);
}

@starting-style {
  .dialog[open] {
    opacity: 0;
    transform: scale(0.9) translateY(10px);
  }
}
Enter fullscreen mode Exit fullscreen mode

What it replaces: The setTimeout(0) or double requestAnimationFrame hack — set display, wait a frame, add the animation class.

Browser support: Chrome 117+ (Sep 2023), Safari 17.5+ (May 2024), Firefox 129+ (Aug 2024). Fully cross-browser.

Try it live

Browser Support Cheat Sheet

Feature Chrome Safari Firefox Cross-browser?
Scroll-driven animations 115+ (2023) 18+ (2024) Behind flag Partial
Anchor positioning 125+ (2024) 18+ (2024) Behind flag Partial
:has() 105+ (2022) 15.4+ (2022) 121+ (2023) Yes
Container queries 105+ (2022) 16+ (2022) 110+ (2023) Yes
interpolate-size 129+ (2024) Not yet Not yet Chromium only
CSS nesting 112+ (2023) 16.5+ (2023) 117+ (2023) Yes
color-mix() 111+ (2023) 16.2+ (2022) 113+ (2023) Yes
View Transitions 111+ (2023) 18+ (2024) 144+ (2025) Yes
@starting-style 117+ (2023) 17.5+ (2024) 129+ (2024) Yes

Try the Editor

Every demo runs in BigDevSoon's free code editor. HTML, CSS, JavaScript, React, Vue, TypeScript, SCSS, npm packages — all in the browser. No signup required.

Which demo surprised you the most? Drop it in the comments.

Top comments (0)