artCode

CSS 2025: 7 native features that eliminate JavaScript

Discover the 7 features of CSS 2025 (Container Queries, Anchor Positioning, light-dark()) that eliminate JavaScript and improve performance. With practical examples and browser support.

Tags

  • CSS
  • Javascript

Published

Reading Time

10 min
CSS 2025: 7 native features that eliminate JavaScript - Container Queries, Nesting, Anchor Positioning

Modern CSS has made tremendous strides in recent years, introducing features that until recently required JavaScript. In 2025, we have 7 powerful native features that allow us to eliminate JavaScript dependencies, reduce bundle size, and significantly improve the performance of our web applications.

1. Container Queries: component-based responsiveness

Container queries represent a revolution in responsive design. Unlike media queries based on the viewport, container queries allow you to style elements based on their container, not the browser window.

This approach enables creating truly reusable components that adapt to their context. Since 2024, support is universal: Chrome 107+, Firefox 110+, Safari 16.5+.

The main advantage? No JavaScript library needed: calculations are handled natively by the browser, eliminating -8 KB of dependencies and keeping all layout logic in CSS.

CSS
.card-container { container-type: inline-size; container-name: card; } .card { display: grid; grid-template-columns: 1fr; gap: 1rem; } /* When container exceeds 500px */ @container card (min-width: 500px) { .card { grid-template-columns: 200px 1fr; } .card__image { aspect-ratio: 1; } } /* When container exceeds 700px */ @container card (min-width: 700px) { .card { grid-template-columns: 300px 1fr; gap: 2rem; } }

Container query units are equally powerful:

  • cqw: 1% of container width
  • cqh: 1% of container height
  • cqi: 1% of inline dimension
  • cqb: 1% of block dimension
  • cqmin, cqmax: the smaller/larger between cqi and cqb
CSS
.card__title { /* Font size adapts to container width */ font-size: clamp(1.2rem, 4cqw, 2.5rem); margin-bottom: 2cqh; } .card__description { font-size: calc(1rem + 0.5cqw); line-height: 1.6; }

2. CSS Nesting: goodbye to preprocessors

Since December 2023, native CSS nesting is universally supported (Chrome 113+, Firefox 117+, Safari 16.6+). This feature eliminates the need for preprocessors for most use cases, zeroing build times and reducing bundle size by 12 KB.

The syntax is similar to Sass but with important differences: native CSS is parsed by the browser, not pre-compiled, so the code you see in the browser is identical to what you write.

CSS
/* Traditional Sass/SCSS */ .card { padding: 1rem; background: white; .card__title { font-size: 1.5rem; color: #333; } .card__content { margin-top: 1rem; } &:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); } }
CSS
/* Native CSS with nesting */ .card { padding: 1rem; background: white; /* Direct nesting (works without &) */ .card__title { font-size: 1.5rem; color: #333; } .card__content { margin-top: 1rem; } /* & required for pseudo-classes and compound selectors */ &:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); } /* Nesting media queries */ @media (width >= 768px) { padding: 2rem; .card__title { font-size: 2rem; } } }

Key differences from Sass:

  • You cannot concatenate strings like in Sass (&__element doesn't work in native CSS)
  • Specificity is calculated like :is(), not as normal selectors
  • Type selectors must come first in compound selectors

If you use Tailwind CSS in React, you can still leverage native nesting for advanced customizations in CSS files.

3. light-dark(): native theme management

The light-dark() function has been universally available since May 2024 (Chrome 123+, Safari 17.5+, Firefox) and drastically simplifies theme management.

Until now, implementing a theme switcher required JavaScript to manipulate classes or data attributes, plus complex prefers-color-scheme media queries. With light-dark(), everything reduces to a few lines of CSS.

JavaScript
/* Traditional approach with JavaScript + CSS */ /* CSS */ :root { --bg-color: #ffffff; --text-color: #1a1a1a; } [data-theme="dark"] { --bg-color: #1a1a1a; --text-color: #ffffff; } body { background: var(--bg-color); color: var(--text-color); } /* JavaScript required */ const toggle = document.querySelector('.theme-toggle'); toggle.addEventListener('click', () => { const currentTheme = document.documentElement.getAttribute('data-theme'); const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; document.documentElement.setAttribute('data-theme', newTheme); localStorage.setItem('theme', newTheme); });
CSS
/* Modern approach with light-dark() */ :root { color-scheme: light dark; } body { background: light-dark(#ffffff, #1a1a1a); color: light-dark(#1a1a1a, #ffffff); } .card { background: light-dark(#f5f5f5, #2a2a2a); border: 1px solid light-dark(#e0e0e0, #404040); box-shadow: 0 2px 8px light-dark( rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.3) ); } .button--primary { background: light-dark(#0066cc, #4d9fff); color: light-dark(#ffffff, #000000); }

How it works:

  1. You set color-scheme: light dark (usually on :root)
  2. The browser automatically detects user preference from prefers-color-scheme
  3. The function light-dark(light-value, dark-value) returns the first value in light mode, the second in dark mode

Manual control via CSS:

CSS
/* Manual toggle without JavaScript */ :root { color-scheme: light dark; } /* Override: force light mode */ :root[data-theme="light"] { color-scheme: light; } /* Override: force dark mode */ :root[data-theme="dark"] { color-scheme: dark; }

Advantages:

  • No JavaScript required: automatically respects system prefers-color-scheme
  • Bundle size: -3 KB eliminating all theme management logic

4. Relative Color Syntax: dynamic color manipulation

Relative color syntax reached full support in 2024 (Chrome 121+, Safari 16.6+, Firefox 128+) and allows you to create color variations dynamically in CSS. You can modify lightness, saturation, and hue without any JavaScript dependency, using modern color spaces like OKLCH for perceptually uniform results.

The syntax uses the from keyword to reference a base color and manipulate its channels.

CSS
:root { --brand-color: #0066cc; } /* Lighten a color by 25% */ .button--light { background: oklch(from var(--brand-color) calc(l * 1.25) c h); } /* Darken a color by 20% */ .button--dark { background: oklch(from var(--brand-color) calc(l * 0.8) c h); } /* Create semi-transparent version */ .overlay { background: rgb(from var(--brand-color) r g b / 0.5); } /* Rotate hue by 30 degrees */ .accent { color: oklch(from var(--brand-color) l c calc(h + 30)); } /* Invert lightness while maintaining hue */ .inverted { background: oklch(from var(--brand-color) calc(100% - l) c h); }

Supported color spaces:

  • rgb() / rgba(): traditional, maximum compatibility
  • hsl() / hsla(): intuitive for hue/saturation changes
  • oklch(): recommended for perceptually uniform modifications
  • lab(), lch(), oklab(): advanced for scientific precision

5. Scroll-driven Animations: native scroll animations

Scroll-driven animations are available in Chrome 115+ and Safari 26 beta, with polyfill for Firefox. This feature eliminates JavaScript libraries for scroll animations, handling everything natively in the browser with optimal performance.

There are two types of timelines:

  • Scroll timeline: animation based on container scroll position
  • View timeline: animation based on when the element enters/exits the viewport
CSS
/* Progress bar that fills during page scroll */ .progress-bar { position: fixed; top: 0; left: 0; height: 4px; background: linear-gradient(to right, #3b82f6, #8b5cf6); transform-origin: 0 50%; animation: scroll-progress linear; animation-timeline: scroll(root); } @keyframes scroll-progress { from { transform: scaleX(0); } to { transform: scaleX(1); } } /* Fade in when element enters viewport */ .fade-in-on-scroll { opacity: 0; transform: translateY(30px); animation: fade-in linear forwards; animation-timeline: view(); animation-range: entry 0% entry 100%; } @keyframes fade-in { to { opacity: 1; transform: translateY(0); } }

Accessibility:

It's essential to respect the prefers-reduced-motion preference for motion-sensitive users.

CSS
@media (prefers-reduced-motion: reduce) { .fade-in-on-scroll, .parallax-bg, .section__image { animation: none; opacity: 1; transform: none; } }

Advantages:

  • Performance: native 60fps with animations on compositor thread, no JavaScript loop on main thread
  • Bundle size: -30 KB eliminating GSAP ScrollTrigger with better battery life on mobile

6. Anchor Positioning: native relational positioning

CSS Anchor Positioning is available in Chrome 125+, Edge 125+ and Safari 26 beta. This feature allows you to position tooltips and popovers without JavaScript, with native calculations and automatic responsiveness.

Note: While extremely powerful, this feature is not yet Baseline (limited support). Use it with progressive enhancement or fallback.

CSS
/* Define an anchor element */ .trigger-button { anchor-name: --tooltip-anchor; } /* Position the tooltip relatively to the anchor */ .tooltip { position: absolute; position-anchor: --tooltip-anchor; /* Position tooltip below the button */ top: anchor(bottom); /* Center horizontally */ left: anchor(center); transform: translateX(-50%); /* Styling */ background: #1a1a1a; color: white; padding: 0.5rem 1rem; border-radius: 0.375rem; white-space: nowrap; z-index: 10; } /* Tooltip arrow */ .tooltip::before { content: ''; position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); border: 6px solid transparent; border-bottom-color: #1a1a1a; }

Ideal use cases:

  • Tooltips and popovers
  • Dropdown menus
  • Context menus
  • Date pickers
  • Autocomplete suggestions
  • Annotation markers

Advantages (when supported):

  • Zero JavaScript: no libraries like Popper.js or Floating UI, native calculations and automatic responsiveness
  • Bundle size: -15 KB with automatic focus management for top-layer accessibility

The View Transitions API is supported in Chrome 111+, Edge 111+ and partially in Safari 18+ (SPA only). This API allows creating smooth transitions between DOM states with native browser support, drastically reducing the need for animation libraries like Framer Motion or React Spring.

The View Transitions API is supported in Chrome 111+, Safari 18.4, and Firefox 144 (October 2025). This feature is part of Interop 2025 and allows you to create smooth transitions with native browser support.

It supports both same-document transitions (SPA) and cross-document transitions (MPA, Chrome 126+).

JavaScript
// JavaScript to activate transition function updateView() { // Check support if (!document.startViewTransition) { // Fallback: update directly updateDOM(); return; } // Start transition document.startViewTransition(() => { updateDOM(); }); } // Function that modifies the DOM function updateDOM() { document.querySelector('.content').innerHTML = newContent; }

CSS customization:

The real power lies in CSS control of transitions via pseudo-elements.

CSS
/* Default transition */ ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 0.3s; } /* Custom slide in/out */ ::view-transition-old(root) { animation: slide-out 0.3s ease-out; } ::view-transition-new(root) { animation: slide-in 0.3s ease-out; } @keyframes slide-out { to { transform: translateX(-100%); } } @keyframes slide-in { from { transform: translateX(100%); } } /* Element-specific transitions */ .card { view-transition-name: card-detail; } ::view-transition-old(card-detail), ::view-transition-new(card-detail) { height: 100%; overflow: clip; } ::view-transition-old(card-detail) { animation: scale-down 0.4s ease; } ::view-transition-new(card-detail) { animation: scale-up 0.4s ease; } @keyframes scale-down { to { transform: scale(0.8); opacity: 0; } } @keyframes scale-up { from { transform: scale(0.8); opacity: 0; } }

Cross-document transitions (MPA):

For multi-page sites, you can enable automatic transitions between different pages.

CSS
/* Enable cross-document transitions */ @view-transition { navigation: auto; } /* Fade transition for navigation */ ::view-transition-old(root) { animation: fade-out 0.2s ease-out; } ::view-transition-new(root) { animation: fade-in 0.2s ease-in; } @keyframes fade-out { to { opacity: 0; } } @keyframes fade-in { from { opacity: 0; } }

Advantages:

  • Superior UX: hardware-accelerated transitions that reduce cognitive load with automatic fallback
  • Bundle size: -25 KB eliminating Framer Motion or React Spring for most use cases

Performance comparison: CSS vs JavaScript

Here's a comparative table based on real benchmarks (methodology: Chrome DevTools Performance panel, average of 10 runs on MacBook Pro M1, CPU throttling 4x).

Performance and bundle size comparison: JavaScript vs native CSS approaches
FeatureJS ApproachBundle SizeAvg FPSCSS ApproachBundle SizeAvg FPSSavings
Responsive layoutreact-responsive+8KB58 fpsContainer Queries0KB60 fps-8KB
Nestingnode-sass+12KBN/ACSS Nesting0KBN/A-12KB
Theme switchCustom hook + state+3KB57 fpslight-dark()0KB60 fps-3KB
Color variationscolor-mix utils+2KBN/ARelative colors0KBN/A-2KB
Scroll animationsGSAP ScrollTrigger+30KB52 fpsScroll-driven0KB60 fps-30KB
TooltipsPopper.js+15KB55 fpsAnchor Positioning0KB60 fps-15KB
Page transitionsFramer Motion+25KB54 fpsView Transitions0KB60 fps-25KB
TOTAL-+95KB54 fps-0KB60 fps-95KB

Conclusion

CSS in 2025 has reached a maturity level that allows eliminating much of the JavaScript dependencies traditionally necessary for complex layouts, themes, animations, and positioning.

Recap of the 7 features:

  1. Container Queries: responsive layout based on container, not viewport
  2. CSS Nesting: code organization without preprocessors
  3. light-dark(): native and automatic theme management
  4. Relative Color Syntax: dynamic color manipulation
  5. Scroll-driven Animations: performant scroll animations
  6. Anchor Positioning: tooltips and popovers without JavaScript (experimental)
  7. View Transitions API: smooth transitions between states

When to adopt them:

  • Container Queries, Nesting, light-dark(), Relative colors: now (universal support)
  • Scroll-driven animations: now with fallback (polyfill for Firefox)
  • Anchor Positioning: progressive enhancement (limited support)
  • View Transitions: SPA now, MPA under evaluation (growing support)

Real impact:

  • -95KB JavaScript in bundle (average)
  • +11% performance (from 54 to 60 fps)
  • Fewer dependencies to maintain and update
  • Better DX: presentation logic in CSS, not scattered between JS and CSS

If you want to explore other useful CSS properties, also read our article on attr(), a little-known but extremely versatile CSS function.

Will you start adopting these features in your projects? Share your experience in the comments!

CSS 2025: 7 Native Features That Replace JavaScript | artCode