← Back to Articles

Modern CSS Features in 2025

The CSS landscape has transformed dramatically over the past two years. What once required JavaScript workarounds or complex media query gymnastics can now be achieved with elegant, native CSS solutions. Whether you're building component libraries or one-off projects, understanding these features will fundamentally change how you approach styling.

Container Queries: Responsive Components

Container queries represent one of the most significant CSS innovations in years. While media queries respond to viewport dimensions, container queries respond to the size of a parent container. This shift from viewport-centric to component-centric design changes everything.

The syntax is straightforward. First, designate a container:

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

Then, write styles that adapt based on that container's width:

@container (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 200px 1fr;
  }
}

This approach solves a fundamental problem: components need to be responsive regardless of where they're placed. A sidebar card, a hero card, and a grid card all require different layouts. Container queries let each component define its own responsive behavior without knowing its context.

The :has() Selector

The CSS :has() pseudo-class is a parent selector. It lets you style an element based on its descendants, something web developers have wanted for over a decade.

/* Style a list item if it contains a link */
li:has(> a) {
  font-weight: bold;
}

/* Style a form if it has an invalid input */
form:has(input:invalid) {
  border-color: #ff6b6b;
}

Beyond styling, :has() enables powerful patterns. You can create toggle switches, accordion menus, and tabs entirely with CSS. A checkbox state can trigger layout changes throughout the entire component tree.

Real-world example: disable form submission buttons when the form contains invalid inputs:

form:has(input:invalid) button[type="submit"] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

Cascade Layers

Cascade layers explicitly organize CSS specificity. They let you define the priority of different rule sets without relying on specificity hacks.

@layer reset, base, theme, components, utilities;

@layer base {
  body { font-family: system-ui; }
  h1 { font-size: 2rem; }
}

@layer components {
  .button { padding: 0.5rem 1rem; }
}

The layering order determines specificity. A rule in the utilities layer beats a rule in the components layer, regardless of selector specificity. This is a game-changer for scalable CSS architecture.

Developers using Tailwind benefit from this immediately. Tailwind already organizes layers. Explicit cascade layers mean your custom CSS won't be overridden by utility classes when placed in the correct layer.

The :is() and :where() Selectors

Both reduce selector repetition, but with a crucial difference in specificity.

/* Without :is() */
h1, h2, h3, h4, h5, h6 {
  line-height: 1.2;
}

/* With :is() */
:is(h1, h2, h3, h4, h5, h6) {
  line-height: 1.2;
}

/* :where() also works, but with 0 specificity */
:where(h1, h2, h3, h4, h5, h6) {
  line-height: 1.2;
}

Use :is() when you want normal specificity. Use :where() in frameworks or resets where you don't want to increase specificity.

@supports and Feature Detection

The @supports rule lets you write conditional CSS based on browser support:

@supports (display: grid) {
  .layout {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
  }
}

@supports not (display: grid) {
  .layout {
    display: flex;
    flex-wrap: wrap;
  }
}

Advanced Color Functions

Modern CSS supports the color() function and relative color syntax:

/* Define colors relative to a base */
:root {
  --primary: oklch(60% 0.15 240);
}

button {
  background: var(--primary);
}

button:hover {
  background: oklch(from var(--primary) calc(l + 5%) c h);
}

OKLCH color space provides perceptually uniform color transitions, making hover states and variations look more natural.

Performance Considerations

While these features are powerful, use them judiciously. Container queries can increase layout thrashing if overused. The :has() selector, particularly with complex selectors, can impact performance if used in hot paths.

Always measure. Browser DevTools now show you when CSS is causing layout shifts. Profile your actual usage patterns before optimizing.

Browser Support in 2025

Container queries: 97% of users. The :has() selector: 95% of users. Cascade layers: 93% of users. These features are widely supported across modern browsers. For older browser support, use @supports queries to provide fallbacks.

Getting Started

Start with one feature. Try container queries on your next component library. Use :has() to simplify form validation styles. Organize a stylesheet with cascade layers. These features compound in power as you master them.

Modern CSS is remarkably mature. What we build today will be faster, more maintainable, and more elegant than what we could build two years ago.

🛠 Recommended Resources

Level up your CSS skills. Affiliate links support this site at no cost to you.

CSS: The Definitive Guide (5th Ed)

The most comprehensive modern CSS reference. Covers container queries, layers, and everything here.

Debugging CSS (Ahmad Shadeed)

Learn to debug CSS problems systematically. Every frontend dev needs this.