iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Sass + BEM

BEM (Block, Element, Modifier) is a naming convention that keeps CSS predictable at scale: .block__element--modifier. Sass’s nesting plus & selector composition makes BEM ergonomic to write without exploding selectors.

Block, element, modifier, nesting, &

EXAMPLE
// 1) The shape
// .block                                 — independent component
// .block__element                        — child piece of a block (double underscore)
// .block--modifier                       — variant or state of the block (double dash)
// .block__element--modifier              — modifier on an element

// 2) Plain CSS — verbose
.card { /* … */ }
.card__title { /* … */ }
.card__body { /* … */ }
.card--featured { /* … */ }
.card--featured .card__title { /* … */ }

// 3) Sass — same idea with & nesting
.card {
    padding: 1rem;
    border-radius: 0.5rem;
    background: var(--surface);

    &__title {
        font-size: 1.25rem;
        font-weight: 600;
    }

    &__body {
        margin-top: 0.5rem;
        color: var(--text-muted);
    }

    &__action {
        margin-top: 1rem;
        display: inline-block;
    }

    &--featured {
        background: var(--accent);
        color: var(--accent-fg);

        & .card__title {
            color: inherit;
        }
    }

    &--compact {
        padding: 0.5rem;
        .card__body { font-size: 0.875rem; }
    }
}

// 4) Resulting CSS
// .card { … }
// .card__title { … }
// .card__body { … }
// .card--featured { … }
// .card--featured .card__title { … }
// .card--compact { … }
// .card--compact .card__body { … }

// 5) Modifier patterns
// • Boolean — .card--featured (style depends on the modifier)
// • Key/value — .card--variant-success, .card--size-lg
// • State — .card--is-open, .card--is-loading

.button {
    padding: 0.5rem 1rem;

    &--variant-primary {
        background: var(--accent);
        color: var(--accent-fg);
    }
    &--variant-secondary {
        background: transparent;
        border: 1px solid var(--border);
    }
    &--size-sm { padding: 0.25rem 0.5rem; font-size: 0.875rem; }
    &--size-lg { padding: 0.75rem 1.25rem; font-size: 1.125rem; }
}

// 6) Don't nest elements inside other elements
// .card__body__title is WRONG.
// Either it's another element of .card (.card__subtitle) or you've found a sub-component.

// 7) State classes — separate from BEM, lowercase prefix
.is-open  { display: block; }
.is-hidden { display: none; }
.is-loading { opacity: 0.5; pointer-events: none; }

.card {
    &.is-loading { /* … */ }
    .card__title { /* … */ }
}

// State classes describe transient state (added/removed by JS); modifiers describe variants.

// 8) Mixins for repeated modifier groups
@mixin button-variant($bg, $fg, $hover-bg) {
    background: $bg;
    color: $fg;
    &:hover, &:focus-visible { background: $hover-bg; }
}

.button {
    &--primary  { @include button-variant(#4f46e5, #fff, #4338ca); }
    &--success  { @include button-variant(#16a34a, #fff, #15803d); }
    &--danger   { @include button-variant(#dc2626, #fff, #b91c1c); }
}

// 9) BEM + responsive
.card {
    padding: 1rem;

    @media (min-width: 768px) {
        padding: 1.5rem;
        &__title { font-size: 1.5rem; }
    }
}

// 10) BEM + utility classes — pragmatic mix
// BEM for COMPONENTS (.card, .nav, .form)
// Utilities for ONE-OFFS (.mt-4, .text-center)
// Avoid utility soup AND avoid hyper-componenting everything.

<div class="card card--featured mt-4">
    <h3 class="card__title text-center">Title</h3>
    <p class="card__body">Body</p>
</div>

// 11) Avoiding the deep-nesting trap
// Sass nesting > 3 levels is a smell — output selectors get long, specificity explodes.
// BEM helps because each piece is one class, so & only nests one level for elements.

// 12) File organisation
// scss/
//   components/
//     _card.scss              — .card block
//     _button.scss            — .button block
//     _navbar.scss            — .navbar block
//   utilities/
//     _spacing.scss
//   abstracts/
//     _tokens.scss
//     _mixins.scss
//   main.scss
//     @use 'abstracts/tokens';
//     @use 'components/card';
//     @use 'components/button';
//     ...

// 13) BEM vs other methodologies
// BEM           — explicit naming, predictable
// OOCSS         — composition by single-purpose classes
// SMACSS        — categorisation (base, layout, module, state)
// Utility-first — Tailwind: skip naming entirely
// CSS Modules    — automatic scoping; no naming needed
// CSS-in-JS     — JS-scoped styles, dynamic
// Pick one, document the convention, enforce in code review.

// 14) Common bugs
// • Long selectors (.foo .bar .baz) from over-nesting → BEM keeps it flat
// • Block-on-block coupling (.card-list .card__title) → bad coupling; use mix-ins instead
// • Re-defining BEM separators per file — settle on one (.block__element--modifier)
// • Confusing element with sub-block — if it has its own meaningful state, it's a new block
// • Using BEM AND Tailwind in the same project without rules — pick one for components
// • Underscore typos (.card_title) — linter (stylelint-bem-pattern) catches these

Why it matters

BEM keeps CSS predictable: one class per piece, no specificity wars, no surprise overrides. Sass nesting with & makes the convention pleasant to write, but resist nesting more than three levels deep — BEM’s point is FLAT selectors that don’t depend on DOM structure.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
.block {
    &__element { color: red; }
    &--modifier { font-weight: bold; }
}
// Outputs: .block__element, .block--modifier
Try it Yourself »

Discussion

Loading…