MinhVo

Minh Vo

rss feed

Slaying code & making it lit fr fr 🔥 tagline

Hey there 👋 I'm an AI Engineer with 7 years of experience building scalable web and mobile applications. Currently at Neurond AI (May 2025 — present), architecting an Enterprise AI Assistant Platform with multi-tenant RAG on pgvector, multi-provider LLM orchestration, and Azure-native infrastructure. Previously spent 5+ years at SNAPTEC (Sep 2019 — Apr 2025), leading SaaS themes, admin dashboards, and e-commerce platforms — earned the Hero of the Year award in 2021. I specialize in TypeScript, React, Next.js, and AI-Native engineering with Claude Code and Cursor.bio

Back to blogs

The State of CSS in 2024: New Features and Browser Support

Review CSS advances in 2024: anchor positioning, scroll-driven animations, @starting-style.

CSSFrontendBrowserStyling

By MinhVo

Introduction

CSS in 2024 has undergone the most significant transformation since Flexbox and Grid became mainstream. Features that were previously impossible without JavaScript—popover positioning, scroll-driven animations, container queries, cascade layers, and native nesting—are now available in all major browsers. The CSS Working Group has delivered a wave of specifications that eliminate entire categories of JavaScript libraries, reduce bundle sizes, and enable design patterns that were previously impractical.

This guide examines every major CSS feature that shipped or reached broad browser support in 2024, with practical examples, browser compatibility data, and migration strategies from JavaScript-based alternatives. Whether you're building design systems, complex animations, or responsive layouts, these features fundamentally change what's possible with pure CSS.

CSS Features

Understanding CSS 2024: Core Concepts

The 2024 CSS feature set addresses three long-standing developer pain points: layout control that required JavaScript (anchor positioning, container queries), animation capabilities that required libraries like GSAP (scroll-driven animations, view transitions), and authoring ergonomics that required preprocessors like Sass (nesting, cascade layers, custom properties improvements).

The browser support story has matured significantly. Chrome, Firefox, Safari, and Edge now ship features within months of each other rather than years. The Baseline initiative—tracking when features become available across all major browsers—gives developers confidence to adopt new CSS without waiting for "the one browser" that lagged behind.

The Cascade Layers Revolution

Cascade layers (@layer) fundamentally change how CSS specificity works, solving the oldest problem in CSS: third-party styles overriding your custom styles. Layers let you define explicit ordering of style sources, making specificity conflicts predictable and resolvable.

/* Define layer order — later layers win over earlier ones */
@layer reset, base, components, utilities;
 
@layer reset {
  * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }
}
 
@layer base {
  body {
    font-family: system-ui, sans-serif;
    line-height: 1.6;
    color: var(--text-primary);
  }
  
  h1, h2, h3 {
    line-height: 1.2;
    font-weight: 700;
  }
}
 
@layer components {
  .button {
    padding: 0.75rem 1.5rem;
    border-radius: 0.5rem;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.2s ease;
  }
  
  .button-primary {
    background: var(--color-primary);
    color: white;
  }
}
 
@layer utilities {
  .sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    border: 0;
  }
}

The practical impact: third-party CSS (component libraries, analytics widgets, CMS-injected styles) can be placed in a named layer, guaranteeing that your custom styles always win without resorting to !important hacks or specificity wars.

Native CSS Nesting

CSS nesting eliminates the primary reason developers adopted Sass and other preprocessors. Native nesting supports all selector combinators, pseudo-classes, and media queries without requiring a build step:

/* Native CSS nesting — no preprocessor needed */
.card {
  background: white;
  border-radius: 1rem;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  padding: 1.5rem;
  
  /* Nested selectors */
  & .card-title {
    font-size: 1.25rem;
    font-weight: 700;
    margin-bottom: 0.5rem;
  }
  
  & .card-body {
    color: var(--text-secondary);
    line-height: 1.6;
  }
  
  /* Pseudo-classes */
  &:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    transform: translateY(-2px);
  }
  
  /* Media queries */
  @media (max-width: 768px) {
    padding: 1rem;
    
    & .card-title {
      font-size: 1.125rem;
    }
  }
  
  /* Attribute selectors */
  &[data-variant="highlighted"] {
    border: 2px solid var(--color-primary);
  }
}

Architecture and Design Patterns

Container Queries: Responsive Components

Container queries enable components to respond to their container's size rather than the viewport's size—the most requested CSS feature for over a decade. This eliminates the need for JavaScript resize observers and complex responsive class patterns:

/* Container query setup */
.card-container {
  container-type: inline-size;
  container-name: card;
}
 
/* Component responds to container, not viewport */
@container card (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 200px 1fr;
    gap: 1.5rem;
  }
  
  .card-image {
    aspect-ratio: 1;
    object-fit: cover;
    border-radius: 0.5rem;
  }
}
 
@container card (min-width: 700px) {
  .card {
    grid-template-columns: 280px 1fr;
    gap: 2rem;
    padding: 2rem;
  }
  
  .card-title {
    font-size: 1.5rem;
  }
}
 
/* Style queries — respond to container's computed styles */
@container style(--theme: dark) {
  .card {
    background: var(--surface-dark);
    color: var(--text-light);
  }
}

Anchor Positioning: Popovers and Tooltips Without JavaScript

Anchor positioning eliminates the need for libraries like Popper.js, Floating UI, or Tippy.js by providing native CSS positioning relative to "anchor" elements:

/* Define an anchor element */
.trigger-button {
  anchor-name: --trigger;
}
 
/* Position a tooltip relative to the anchor */
.tooltip {
  position: fixed;
  position-anchor: --trigger;
  
  /* Position below the anchor, centered */
  top: anchor(bottom);
  left: anchor(center);
  translate: -50% 8px;
  
  /* Fallback positioning if no space below */
  position-try-fallbacks: flip-block, flip-inline;
  
  /* Auto-hide when anchor is hidden */
  position-visibility: anchors-visible;
}
 
/* Popover menu anchored to button */
.dropdown-menu {
  position: fixed;
  position-anchor: --trigger;
  
  top: anchor(bottom);
  left: anchor(right);
  translate: -100% 8px;
  
  /* Handle overflow with fallback positions */
  position-try-fallbacks:
    flip-block,
    flip-inline,
    --bottom-left,
    --top-right;
}
 
@position-try --bottom-left {
  top: anchor(bottom);
  left: anchor(left);
  translate: 0 8px;
}
 
@position-try --top-right {
  bottom: anchor(top);
  right: anchor(right);
  translate: 0 -8px;
}

Scroll-Driven Animations

Scroll-driven animations bind CSS animation progress to scroll position, enabling parallax effects, progress indicators, and reveal animations without JavaScript scroll event listeners:

/* Scroll progress bar at top of page */
.scroll-progress {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 3px;
  background: var(--color-primary);
  transform-origin: left;
  
  animation: grow-progress linear;
  animation-timeline: scroll(root);
}
 
@keyframes grow-progress {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}
 
/* Fade-in elements as they scroll into view */
.reveal-on-scroll {
  opacity: 0;
  transform: translateY(40px);
  
  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);
  }
}
 
/* Parallax effect */
.parallax-element {
  animation: parallax linear;
  animation-timeline: scroll(root);
  animation-range: 0vh 100vh;
}
 
@keyframes parallax {
  from { transform: translateY(0); }
  to { transform: translateY(-200px); }
}

@starting-Style: Entry Animations

The @starting-style rule enables CSS-only entry animations for elements that are dynamically inserted into the DOM—previously requiring JavaScript to trigger animation classes:

/* Dialog entry animation */
dialog {
  opacity: 1;
  transform: scale(1);
  transition:
    opacity 0.3s ease,
    transform 0.3s ease;
  
  /* Starting state when dialog opens */
  @starting-style {
    opacity: 0;
    transform: scale(0.95);
  }
  
  /* Exit animation via dialog[close] */
  &[closing] {
    opacity: 0;
    transform: scale(0.95);
  }
}
 
/* Tooltip entry animation */
.tooltip {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.2s ease, transform 0.2s ease;
  
  @starting-style {
    opacity: 0;
    transform: translateY(8px);
  }
}
 
/* Popover menu entry */
[popover]:popover-open {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.15s ease, transform 0.15s ease;
  
  @starting-style {
    opacity: 0;
    transform: translateY(-8px);
  }
}

Step-by-Step Implementation

Migrating from JavaScript Positioning to Anchor Positioning

Replace Floating UI / Popper.js with native anchor positioning:

// Before: Floating UI (JavaScript)
import { computePosition, flip, shift } from '@floating-ui/dom';
 
async function positionTooltip(trigger: HTMLElement, tooltip: HTMLElement) {
  const { x, y } = await computePosition(trigger, tooltip, {
    placement: 'bottom',
    middleware: [flip(), shift()],
  });
  
  tooltip.style.left = `${x}px`;
  tooltip.style.top = `${y}px`;
}
 
// After: Native anchor positioning (CSS only)
// No JavaScript positioning logic needed
// Just define anchors in CSS

Implementing a Scroll-Driven Progress Indicator

Replace JavaScript scroll listeners with native CSS:

/* Pure CSS reading progress indicator */
.reading-progress {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 4px;
  background: linear-gradient(
    to right,
    var(--color-primary),
    var(--color-secondary)
  );
  transform-origin: left;
  animation: progress linear;
  animation-timeline: scroll(nearest block);
}
 
@keyframes progress {
  from { transform: scaleX(0); }
  to { transform: scaleX(1); }
}

Building a Responsive Dashboard with Container Queries

.dashboard-widget {
  container-type: inline-size;
  container-name: widget;
}
 
@container widget (max-width: 300px) {
  .widget-chart {
    display: none;
  }
  
  .widget-value {
    font-size: 2rem;
  }
  
  .widget-label {
    font-size: 0.875rem;
  }
}
 
@container widget (min-width: 301px) and (max-width: 500px) {
  .widget-chart {
    display: block;
    height: 80px;
  }
  
  .widget-value {
    font-size: 1.5rem;
  }
}
 
@container widget (min-width: 501px) {
  .widget-chart {
    display: block;
    height: 200px;
  }
  
  .widget-value {
    font-size: 2.5rem;
  }
  
  .widget-details {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 1rem;
  }
}

Real-World Use Cases

Use Case 1: Design System with Cascade Layers

A design system team adopted cascade layers to manage styles from their component library, product-specific overrides, and third-party integrations. The layer structure (reset → tokens → components → overrides → utilities) eliminated specificity conflicts that previously required weekly triage meetings. Third-party widget styles placed in a third-party layer never override component library styles, reducing CSS debugging time by 70%.

An e-commerce platform replaced a 15KB JavaScript positioning library with native anchor positioning for product quick-view popovers, variant selectors, and size guides. The CSS-only implementation reduced JavaScript bundle size, eliminated positioning jank during scroll, and simplified the codebase by removing complex resize observer logic.

Use Case 3: Marketing Landing Page Animations

A marketing team built a scrolling landing page with parallax effects, element reveals, and a reading progress bar—all using scroll-driven animations. The previous implementation used GSAP (45KB) with JavaScript scroll listeners that caused layout thrashing. The CSS-only version is zero JavaScript, runs on the compositor thread for 60fps performance, and works correctly with browser scroll optimizations.

Best Practices for Production

  1. Use Cascade Layers Strategically: Define your layer order once at the top of your CSS and stick to it. Common order: reset → base → components → utilities. Third-party CSS should always be in a named layer to prevent specificity conflicts.

  2. Container Queries for All Responsive Components: Use container queries instead of media queries for component-level responsiveness. Reserve media queries for page-level layout changes (sidebar visibility, navigation pattern changes).

  3. Progressive Enhancement for New Features: Features like anchor positioning and scroll-driven animations should enhance existing functionality, not gate it. Provide JavaScript fallbacks for critical functionality and use @supports for optional enhancements.

  4. Test Across Browsers Early: While 2024 browser support is excellent, test new CSS features across Chrome, Firefox, and Safari during development. Use feature detection rather than browser detection for fallbacks.

  5. Measure Performance Impact: Scroll-driven animations run on the compositor thread, but complex animations with many elements can still cause performance issues. Profile with browser DevTools and limit the number of simultaneously animated elements.

  6. Combine Features for Maximum Impact: The real power of 2024 CSS comes from combining features—container queries with nesting, anchor positioning with @starting-style, scroll-driven animations with cascade layers. Design patterns that leverage multiple new features produce the most elegant solutions.

  7. Update Your CSS Reset: Modern CSS resets should account for new features. Include @layer declarations in your reset, set sensible defaults for container query behavior, and ensure anchor positioning doesn't conflict with existing layout patterns.

  8. Document Your Layer Architecture: As cascade layers become central to your CSS architecture, document the layer order, what belongs in each layer, and rules for adding new layers. This documentation prevents layer misuse as the codebase grows.

Common Pitfalls and Solutions

PitfallImpactSolution
Overusing cascade layersConfusion about which layer winsKeep layer structure simple (4-5 layers max); document conventions
Container queries on overflow containersUnexpected scrollbars or clippingTest container queries with various content sizes; use contain: layout inline-size carefully
Anchor positioning without fallbacksBroken layout in older browsersUse @supports to provide JavaScript positioning fallback
Scroll-driven animations causing jankPoor scrolling performanceLimit animated elements; use will-change sparingly; prefer compositor-friendly properties
Native nesting depthOverly specific nested selectorsKeep nesting to 3 levels max; prefer flat selectors for performance
@starting-style flashBrief flash of starting state on page loadApply starting styles only to dynamically inserted elements, not static content

Performance Optimization

CSS-only implementations of features previously requiring JavaScript deliver measurable performance improvements. Removing JavaScript scroll listeners eliminates layout thrashing and forced reflows. Anchor positioning runs on the compositor thread, avoiding main-thread layout calculations. Container queries reduce JavaScript resize observer overhead and eliminate layout-triggering reads.

/* Optimize scroll-driven animations for performance */
.optimized-scroll-animation {
  /* Use transform and opacity only — compositor-friendly */
  animation: fade-slide linear both;
  animation-timeline: view();
  animation-range: entry 0% cover 40%;
  
  /* Hint to browser for optimization */
  will-change: transform, opacity;
}
 
@keyframes fade-slide {
  from {
    opacity: 0;
    transform: translateY(30px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

Comparison with Alternatives

FeatureJavaScript ApproachCSS 2024 ApproachPerformance Gain
Element PositioningPopper.js / Floating UI (15KB)Anchor Positioning (0KB JS)Eliminates JS positioning calculations
Scroll ParallaxGSAP ScrollTrigger (45KB)Scroll-Driven Animations (0KB JS)Compositor-thread rendering
Entry AnimationsFramer Motion / React Spring@starting-style (0KB JS)No JS animation frame loop
Responsive ComponentsJS ResizeObserver + classesContainer Queries (0KB JS)Native browser measurement
Style ScopingCSS Modules / Styled ComponentsCascade Layers (0KB build)No runtime CSS-in-JS overhead
CSS NestingSass / PostCSS (build step)Native Nesting (0KB build)No build-time compilation

Advanced Patterns

Combining anchor positioning with popover API creates fully CSS-driven dropdown menus, tooltips, and context menus that handle positioning, overflow, and accessibility without JavaScript:

/* Full CSS dropdown menu */
.menu-trigger {
  anchor-name: --menu-trigger;
}
 
[popover].dropdown-menu {
  position: fixed;
  position-anchor: --menu-trigger;
  top: anchor(bottom);
  left: anchor(center);
  translate: -50% 8px;
  position-try-fallbacks: flip-block, flip-inline;
  
  margin: 0;
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.15s ease, transform 0.15s ease;
  
  @starting-style {
    opacity: 0;
    transform: translateY(-8px);
  }
}

Testing Strategies

Testing CSS 2024 features requires visual regression testing to catch layout differences across browsers and viewport sizes. Tools like Percy, Chromatic, or Playwright's screenshot comparison validate that container queries produce correct layouts at different sizes, anchor positioning handles overflow correctly, and scroll-driven animations render smoothly.

// Visual regression test for container queries
import { test, expect } from '@playwright/test';
 
test('card layout responds to container size', async ({ page }) => {
  await page.goto('/test/card-component');
  
  // Test narrow container
  await page.setViewportSize({ width: 200, height: 400 });
  await expect(page.locator('.card')).toHaveScreenshot('card-narrow.png');
  
  // Test wide container
  await page.setViewportSize({ width: 600, height: 400 });
  await expect(page.locator('.card')).toHaveScreenshot('card-wide.png');
});

Future Outlook

CSS in 2025 and beyond will continue eliminating JavaScript dependencies for layout, animation, and interaction patterns. The CSS Working Group is actively developing features like CSS mixins (conditional styles based on custom properties), scroll-state() queries (responding to scroll position in container queries), and if() function (conditional CSS values). These features will further reduce the need for JavaScript in styling and interaction logic.

The convergence of container queries, anchor positioning, scroll-driven animations, and view transitions creates a CSS platform capable of building complex, interactive UIs without JavaScript libraries. The browser is becoming the styling runtime, eliminating build-time CSS processing and runtime JavaScript styling overhead.

Conclusion

CSS in 2024 represents the most significant advancement in styling capabilities since CSS Grid. Cascade layers solve specificity conflicts, native nesting eliminates preprocessor dependencies, container queries enable truly responsive components, anchor positioning replaces JavaScript positioning libraries, scroll-driven animations eliminate scroll listener overhead, and @starting-style enables entry animations without JavaScript.

Key takeaways for adopting CSS 2024 features:

  1. Adopt cascade layers immediately — they solve the oldest CSS problem (specificity conflicts) with zero runtime cost
  2. Replace media queries with container queries for component-level responsiveness — components should respond to their container, not the viewport
  3. Eliminate positioning libraries — anchor positioning handles tooltips, popovers, and dropdowns natively
  4. Replace scroll animation libraries — scroll-driven animations run on the compositor thread at 60fps without JavaScript
  5. Test across browsers early — 2024 browser support is excellent, but visual regression testing catches subtle differences

The era of JavaScript-dependent styling is ending. Embrace CSS 2024, reduce your JavaScript bundle size, improve rendering performance, and build interfaces that leverage the full power of the modern CSS platform.

For the latest browser support data, consult Can I Use, Baseline, and the CSS Working Group specifications for detailed feature documentation.