Modern CSS Tricks Every Frontend Developer Should Know in 2026

CSS has changed more in the last two years than in the previous decade. Container queries, native nesting, the :has() parent selector, subgrid, scroll-driven animations — these features replace JavaScript hacks and preprocessor workarounds with clean, declarative CSS.

Container Queries

Container queries are the most significant CSS feature since Flexbox and Grid. While media queries respond to the viewport size, container queries respond to the parent container size. This means components can adapt based on where they are placed, not just what device they are on.

css — Container queries
/* Define a containment context */
.sidebar {
    container-type: inline-size;
    container-name: sidebar;
}

/* Component adapts to its container, not the viewport */
.card {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1rem;
}

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

@container sidebar (min-width: 700px) {
    .card {
        grid-template-columns: 250px 1fr auto;
    }
}

This is transformative for component-based design systems. A card component does not need to know whether it is in a sidebar, a main content area, or a modal. It adapts automatically based on the available space.

Browser Support

Container queries have full support in Chrome 105+, Firefox 110+, Safari 16+, and Edge 105+. Safe to use in production for any project that does not need to support IE11 or very old mobile browsers.

CSS Nesting

Native CSS nesting eliminates the need for Sass or Less just for selector nesting. Write nested rules directly in your stylesheets.

css — Native nesting
/* Before: flat CSS */
.nav { display: flex; }
.nav a { color: #94a3b8; }
.nav a:hover { color: #e2e8f0; }
.nav .active { color: #00d4ff; }

/* After: native nesting */
.nav {
    display: flex;

    & a {
        color: #94a3b8;

        &:hover {
            color: #e2e8f0;
        }
    }

    & .active {
        color: #00d4ff;
    }
}

The & symbol represents the parent selector. You can nest pseudo-classes, pseudo-elements, media queries, and even container queries inside a rule block. This keeps related styles together and makes CSS files easier to navigate.

Nesting Media Queries

css — Nested media queries
.card {
    padding: 1rem;
    font-size: 0.9rem;

    @media (min-width: 768px) {
        padding: 2rem;
        font-size: 1rem;
    }
}

No more scrolling to find the media query that modifies a component. The responsive behavior lives right next to the base styles. Try converting your existing CSS with the CSS Beautifier to see how nesting cleans up your code.

The :has() Parent Selector

For decades, CSS could not select a parent based on its children. The :has() selector changes everything. It is technically a "relational pseudo-class" but everyone calls it the parent selector because that is the problem it solves.

css — The :has() selector
/* Style a form when it contains invalid inputs */
form:has(:invalid) {
    border-color: #ef4444;
}

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

/* Style a label when its associated input is focused */
label:has(+ input:focus) {
    color: #00d4ff;
    font-weight: 600;
}

/* Select a list item that contains a nested list */
li:has(> ul) {
    list-style: none;
    padding-left: 0;
}

/* Style a section only if it has more than 3 paragraphs */
section:has(> p:nth-child(4)) {
    column-count: 2;
}

Each of these examples previously required JavaScript. Now they are pure CSS, with all the performance and maintainability benefits that implies.

CSS Subgrid

Standard CSS Grid creates independent track sizing for each grid. Subgrid lets a nested grid inherit tracks from its parent, so inner elements align with the outer grid.

css — Subgrid for card alignment
/* Parent grid */
.card-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 2rem;
}

/* Each card inherits the parent's row tracks */
.card {
    display: grid;
    grid-row: span 3;
    grid-template-rows: subgrid;
    gap: 0;
}

/* Now .card-title, .card-body, .card-footer align
   across all cards regardless of content length */
.card-title { align-self: start; }
.card-body { align-self: start; }
.card-footer { align-self: end; }

Without subgrid, you need JavaScript or fixed heights to align card titles, descriptions, and buttons across a row. With subgrid, it just works. Build these layouts visually with the CSS Grid Playground.

Scroll-Driven Animations

Scroll-driven animations connect CSS animations to scroll position instead of time. No JavaScript scroll listeners, no IntersectionObserver, no requestAnimationFrame.

css — Scroll-driven animations
/* Progress bar that fills as you scroll the page */
.progress-bar {
    position: fixed;
    top: 0;
    left: 0;
    height: 3px;
    background: #00d4ff;
    transform-origin: left;
    animation: grow-progress linear;
    animation-timeline: scroll();
}

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

/* Fade in elements as they enter the viewport */
.fade-in {
    animation: fade-in linear both;
    animation-timeline: view();
    animation-range: entry 0% entry 100%;
}

@keyframes fade-in {
    from { opacity: 0; transform: translateY(40px); }
    to { opacity: 1; transform: translateY(0); }
}

animation-timeline: scroll() ties the animation to the page scroll position. animation-timeline: view() ties it to when an element enters and exits the viewport. The animation-range property controls exactly when the animation starts and ends relative to the scroll position.

Color Functions: color-mix() and oklch()

Modern CSS color functions give you programmatic control over colors without preprocessors.

css — color-mix() and oklch()
/* Mix two colors */
.button-hover {
    background: color-mix(in srgb, var(--primary) 80%, white);
}

/* Create consistent color scales with oklch */
:root {
    --blue-50: oklch(0.97 0.01 250);
    --blue-100: oklch(0.93 0.03 250);
    --blue-200: oklch(0.87 0.06 250);
    --blue-500: oklch(0.62 0.18 250);
    --blue-900: oklch(0.28 0.10 250);
}

/* Automatic dark mode with color-mix */
.surface {
    background: color-mix(
        in oklch,
        var(--bg-base),
        black var(--darkness, 0%)
    );
}

.dark { --darkness: 40%; }

OKLCH provides perceptually uniform color manipulation. Unlike HSL, changing the lightness in OKLCH produces colors that actually look uniformly lighter or darker to human eyes. Experiment with these in the Color Converter tool.

Custom Properties: Beyond Variables

CSS custom properties (variables) go far beyond simple value storage. Combined with calc(), container queries, and the cascade, they enable dynamic design systems.

css — Advanced custom properties
/* Responsive spacing scale */
:root {
    --space-unit: clamp(0.5rem, 1vw, 1rem);
    --space-xs: calc(var(--space-unit) * 0.5);
    --space-sm: var(--space-unit);
    --space-md: calc(var(--space-unit) * 2);
    --space-lg: calc(var(--space-unit) * 4);
    --space-xl: calc(var(--space-unit) * 8);
}

/* Component-scoped theming */
.card {
    --card-padding: var(--space-md);
    --card-radius: 12px;
    --card-bg: var(--surface);

    padding: var(--card-padding);
    border-radius: var(--card-radius);
    background: var(--card-bg);
}

.card.compact {
    --card-padding: var(--space-sm);
    --card-radius: 8px;
}

/* Type scale with a single ratio */
:root {
    --type-ratio: 1.25;
    --type-base: 1rem;
    --type-sm: calc(var(--type-base) / var(--type-ratio));
    --type-lg: calc(var(--type-base) * var(--type-ratio));
    --type-xl: calc(var(--type-lg) * var(--type-ratio));
    --type-2xl: calc(var(--type-xl) * var(--type-ratio));
}

Generate comprehensive custom property systems with the CSS Variable Generator tool.

5 More Techniques Worth Knowing

1. Logical Properties

Replace margin-left with margin-inline-start for layouts that work in both LTR and RTL languages automatically. Logical properties map to the inline (horizontal) and block (vertical) axes rather than physical directions.

2. The accent-color Property

css — accent-color
/* Theme all form controls with one line */
:root {
    accent-color: #00d4ff;
}

/* Now checkboxes, radio buttons, range sliders,
   and progress bars all use your brand color */

3. text-wrap: balance and pretty

The text-wrap: balance property distributes text evenly across lines in headings, preventing the "orphan word on a line by itself" problem. text-wrap: pretty optimizes paragraph text to avoid orphans at the end of paragraphs.

4. @layer for Cascade Management

css — Cascade layers
@layer reset, base, components, utilities;

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

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

@layer utilities {
    .mt-4 { margin-top: 1rem; }
}

Cascade layers give you explicit control over specificity order. Styles in later layers always override earlier layers, regardless of selector specificity. This solves the "utility class overridden by component style" problem.

5. View Transitions API

The View Transitions API enables smooth animated transitions between page states or even between pages in a multi-page application. While the full API requires minimal JavaScript to trigger, the animation itself is pure CSS.

css — View transition
::view-transition-old(root) {
    animation: fade-out 0.3s ease;
}

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

CSS Tools


Frequently Asked Questions

CSS container queries let a component adapt its styles based on the size of its parent container, not the viewport. This is different from media queries which respond to the browser window size. Use container queries when you have reusable components that appear in different layout contexts: a card component that needs to be compact in a sidebar but expanded in a main content area. Define a containment context on the parent with container-type: inline-size, then use @container to write size-based styles. All modern browsers support container queries as of 2024.

The :has() selector is a parent selector that matches an element based on its descendants. For example, article:has(img) selects any article element that contains an image. div:has(> p:first-child) selects any div whose first child is a paragraph. Before :has(), selecting a parent based on its children required JavaScript. Now you can write purely declarative CSS rules like: form:has(:invalid) { border-color: red; } to style a form differently when it contains invalid inputs. It works in all modern browsers and fundamentally changes how we think about CSS selectors.

Yes, CSS nesting has full browser support across Chrome, Firefox, Safari, and Edge since late 2023. You can nest selectors directly in your stylesheets without a preprocessor like Sass or Less. The syntax uses the & symbol to reference the parent selector, similar to Sass. For example: .card { padding: 1rem; & .title { font-size: 1.5rem; } &:hover { background: #f0f0f0; } }. The main advantage over preprocessors is that native nesting works with browser DevTools, shows the actual cascade, and requires no build step.

CSS Grid defines a grid on a parent element. CSS Subgrid allows a child element that is a grid item to inherit the track sizing from its parent grid, so nested grids align with the outer grid's columns and rows. Without subgrid, a nested grid creates its own independent track sizing, which means inner elements cannot align with the outer grid. With subgrid, you set grid-template-columns: subgrid on the child, and its columns align perfectly with the parent. This is essential for card layouts where you want titles, descriptions, and buttons to align across multiple cards regardless of content length.

Scroll-driven animations let you control CSS animations based on scroll position instead of time. Instead of an animation playing over 2 seconds, it plays as the user scrolls through a specific range. You define a scroll timeline with animation-timeline: scroll() for the whole page or animation-timeline: view() for when an element enters and exits the viewport. Combined with @keyframes, this replaces JavaScript scroll listeners for effects like progress bars, parallax backgrounds, fade-in-on-scroll, and reveal animations. All major browsers support scroll-driven animations as of 2024.

NT

Christian Bucher

QTool indexes 269 free tool pages. Many run entirely in the browser; pages that use public APIs or external libraries disclose that network boundary.

Build CSS Visually with Free Tools

Grid playground, flexbox builder, color palette, glassmorphism generator, and more. All free, all browser-based, all producing copy-paste CSS.

Browse CSS Tools More Guides

Related Articles

Built by Miguel

Need a custom tool or website?

From . Delivered in 24-48h. You own the code.

View Services →