A practical look at CSS features that can simplify responsive layouts. Start with the problem you need to solve, then check browser support.

Whether you're a seasoned frontend developer or someone who mainly works on the backend and wants to level up — these are the CSS techniques you should have in your toolkit right now.

Container Queries: The Layout Revolution

Media queries respond to the viewport. Container queries respond to the parent element. This is a fundamental shift in how we think about responsive design.

Instead of asking "how wide is the screen?", you can now ask "how much space does my component actually have?"

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

/* Style based on the container's width, not the viewport */
@container card (min-width: 400px) {
    .card {
        display: grid;
        grid-template-columns: 200px 1fr;
        gap: 1.5rem;
    }
}

@container card (max-width: 399px) {
    .card {
        display: flex;
        flex-direction: column;
    }
    .card img {
        aspect-ratio: 16 / 9;
        object-fit: cover;
    }
}

This is especially powerful for component libraries and design systems where components need to adapt regardless of where they're placed on the page.

Cascade Layers: Taming Specificity

Specificity wars are one of the most frustrating aspects of CSS at scale. Cascade layers (@layer) give you explicit control over which styles take priority:

/* Define layer order — last layer wins */
@layer reset, base, components, utilities;

@layer reset {
    * { margin: 0; padding: 0; box-sizing: border-box; }
}

@layer base {
    body { font-family: 'Inter', sans-serif; line-height: 1.6; }
    h1 { font-size: 2.5rem; font-weight: 800; }
}

@layer components {
    .btn { 
        padding: 0.75rem 1.5rem; 
        border-radius: 0.5rem; 
        font-weight: 600; 
    }
    .card { 
        border: 1px solid #e2e8f0; 
        border-radius: 1rem; 
    }
}

@layer utilities {
    .text-center { text-align: center; }
    .hidden { display: none; }
}

No more !important hacks. No more artificially inflated selectors. Layers let you define a clear hierarchy that's easy to reason about.

The :has() Selector: CSS Gets Superpowers

The :has() selector is arguably the most exciting CSS addition in years. It's essentially a "parent selector" — something developers have wanted for over a decade.

/* Style a card differently when it contains an image */
.card:has(img) {
    grid-template-rows: 200px auto;
}

/* Highlight form labels when their input has focus */
label:has(+ input:focus) {
    color: #0ea5e9;
    font-weight: 600;
}

/* Add spacing when a section has more than 3 children */
.grid:has(:nth-child(4)) {
    grid-template-columns: repeat(2, 1fr);
}

/* Style the body when a modal is open */
body:has(dialog[open]) {
    overflow: hidden;
}

The :has() selector turns CSS from a language that styles elements into a language that understands relationships between elements.

Scroll-Driven Animations

Forget JavaScript-heavy scroll libraries. CSS now has native scroll-driven animations that are buttery smooth and incredibly performant:

/* Animate elements as they enter the viewport */
@keyframes fade-in {
    from { opacity: 0; transform: translateY(2rem); }
    to { opacity: 1; transform: translateY(0); }
}

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

/* Progress bar that fills as user scrolls the page */
.scroll-progress {
    position: fixed;
    top: 0;
    left: 0;
    height: 3px;
    background: linear-gradient(to right, #38bdf8, #818cf8);
    transform-origin: left;
    animation: grow-progress linear;
    animation-timeline: scroll();
}

@keyframes grow-progress {
    from { transform: scaleX(0); }
    to { transform: scaleX(1); }
}

This is a massive win for performance — these animations run on the compositor thread, meaning they won't cause layout shifts or jank.

Subgrid: Nested Grid Alignment

Subgrid solves one of CSS Grid's biggest pain points — aligning nested grid items with their parent grid:

.product-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 2rem;
}

.product-card {
    display: grid;
    grid-template-rows: subgrid;
    grid-row: span 4;  /* image, title, description, price */
}

/* Now ALL card titles align perfectly across the row,
   regardless of image height or description length */

No more hacky fixed heights or flexbox workarounds. Subgrid gives you perfect alignment with minimal code.

Modern Color: OKLCH & Color-Mix

The new oklch() color space produces more perceptually uniform colors than HSL, and color-mix() lets you blend colors right in your CSS:

:root {
    --brand: oklch(0.7 0.15 220);
    --brand-light: color-mix(in oklch, var(--brand) 30%, white);
    --brand-dark: color-mix(in oklch, var(--brand) 70%, black);
    --brand-subtle: color-mix(in oklch, var(--brand) 10%, transparent);
}

.btn-primary {
    background: var(--brand);
    border-color: var(--brand-dark);
}

.btn-primary:hover {
    background: var(--brand-dark);
}

This is how modern design systems handle color — programmatically, without maintaining dozens of hand-picked hex values.

View Transitions API

Smooth page transitions used to require JavaScript frameworks. Now, it's a single CSS rule plus a meta tag:

/* Enable view transitions globally */
@view-transition {
    navigation: auto;
}

/* Customize the transition */
::view-transition-old(root) {
    animation: fade-out 0.2s ease-out;
}

::view-transition-new(root) {
    animation: fade-in 0.3s ease-in;
}

/* Give specific elements unique transitions */
.hero-image {
    view-transition-name: hero;
}

::view-transition-old(hero) {
    animation: scale-down 0.3s ease-out;
}
::view-transition-new(hero) {
    animation: scale-up 0.3s ease-in;
}

Practical Takeaways

You don't need to use all of these at once. Here's my recommended priority:

  1. Container queries — Start using them for any reusable component. They're well-supported and immediately useful.
  2. :has() selector — Replace JavaScript-based conditional styling with this wherever possible.
  3. Cascade layers — Essential for any project with third-party CSS or a growing stylesheet.
  4. Scroll-driven animations — Replace scroll libraries like AOS or ScrollReveal with native CSS.
  5. OKLCH colors — Adopt for new projects, especially design systems.

The future of CSS isn't about writing less of it — it's about writing smarter CSS that does more with less JavaScript. And honestly? That future is already here.

Where to start

Pick the part of this guide that addresses your current problem. Make one change, check the result, and decide what to do next. If you need help applying it to a Django website or app, tell me about your project.