CSS Nesting: Native BEM-Style Selectors Without a Preprocessor

Native CSS nesting (now baseline across all major browsers) lets you write component styles with nested selectors — the same ergonomics as Sass/Less BEM nesting — directly in plain CSS, without a build step. The browser resolves nested rules to flat selectors at parse time, and specificity works exactly as it would for the equivalent flat rule.

Problem: CSS BEM-style selectors like .card__title or .nav--dark .nav__link require long class names and stylesheet organisation discipline to avoid unintended specificity conflicts — a preprocessor like Sass is needed for nested syntax.

Solution: Use native CSS Nesting (now supported in all major browsers) — write .card { & .title { } &:hover { } } directly in CSS without a preprocessor. The & references the parent selector, and nested rules work with pseudo-classes, pseudo-elements, and media queries. WordPress's theme.json supports nested styles for blocks.


The examples below show BEM-style component nesting, combining nesting with pseudo-classes and pseudo-elements, nesting media queries inside selectors, and how to write a WordPress block stylesheet using only native CSS nesting.


/* ── 1. BEM component with native nesting ────────────────────────────────── */
.card {
    border: 1px solid var(--border);
    border-radius: 8px;
    overflow: hidden;

    /* __element */
    & .card__image {
        width: 100%;
        aspect-ratio: 16 / 9;
        object-fit: cover;
    }

    & .card__body {
        padding: 1rem;

        & .card__title {
            font-size: 1.1rem;
            text-wrap: balance;
            margin-block-end: .5rem;
        }

        & .card__excerpt {
            color: var(--text-muted);
            text-wrap: pretty;
        }
    }

    & .card__footer {
        padding: .75rem 1rem;
        background: var(--surface-2);
        display: flex;
        justify-content: space-between;
    }

    /* --modifier */
    &.card--featured {
        border-color: var(--accent);
        box-shadow: 0 0 0 2px var(--accent);
    }

    /* pseudo-classes */
    &:hover { box-shadow: var(--shadow-lg); }
    &:focus-within { outline: 2px solid var(--accent); }

    /* pseudo-element */
    &::before {
        content: '';
        display: block;
        height: 4px;
        background: var(--accent);
    }
}

/* ── 2. Nested media queries ────────────────────────────────────────────── */
.hero {
    padding-block: 3rem;
    font-size: 1rem;

    @media (min-width: 768px) {
        padding-block: 5rem;
        font-size: 1.125rem;
    }

    @media (min-width: 1200px) {
        padding-block: 8rem;
    }
}

/* ── 3. WordPress block stylesheet using native nesting ───────────────────
   file: blocks/my-block/style.css  (no Sass needed)                       */
.wp-block-my-plugin-feature-card {
    display: grid;
    gap: 1rem;

    & .feature-card__icon {
        width: 3rem;
        height: 3rem;
        color: var(--wp--preset--color--accent, #0073aa);
    }

    & .feature-card__title {
        font-size: var(--wp--preset--font-size--large);
        text-wrap: balance;
    }

    &:where(.is-style-outlined) {
        border: 2px solid currentColor;
        padding: 1.5rem;
        border-radius: 4px;
    }
}


NOTE: Unlike Sass, native CSS nesting requires the nested selector to start with a valid CSS selector token or & — you cannot nest bare element names like h2 { } directly without prefixing with & h2 { }; the & is mandatory for descendant element selectors in native nesting.