Apply

Modifiers and state classes

A featured card and a loading card are different problems. One is a reusable component variant. One is temporary behaviour. Mixing them creates unclear CSS and JavaScript with trust issues.

Quick decision rules

Before naming anything, decide what problem it is solving.

  1. Can this exist as a documented reusable version?

    YesModifier

    NoNext question

  2. Is this temporary UI behaviour?

    Yes.is_* state class

    NoNext question

  3. Is this describing present content or condition?

    Yes.has_* condition class

    No

    If none of these fit, check ownership first — variant, behaviour, and content are different problems.

For the shorter modifier vs state flow, seedecision trees. This prevents modifiers becoming state classes in disguise and keeps component behaviour predictable.

Modifiers

Use modifiers for deliberate reusable variants. A modifier should be something design can document and another developer can understand without reading JavaScript.

A modifier is an extra class on the same element as the component root — not a replacement for it. The base class (product-card) still defines what the thing is; the modifier (product-card--featured) documents which documented variant applies. Markup needs both:

HTML code example
<!-- Base component + modifier class on the same element -->
<article class="product-card product-card--featured">
    <h2 class="title">Product title</h2>
</article>

In the stylesheet, keep modifier rules in the component partial for that root. Layers, one-file-per-component, and naming conventions are the main safety nets — so.product-card--featured in product-card.cssis usually enough. You do not have to repeat.product-card in every selector unless the project needs extra guarding.

CSS code example
/* components/product-card.css — usual pattern in the components layer */
.product-card {}

.product-card--featured {}
.product-card--compact {}

Chained selectors such as.product-card.product-card--featured are optional when collision risk is higher (shared files, weak layer discipline, or migration). Use them deliberately, not by default:

CSS code example
/* Optional extra guard when collision risk is higher */
.product-card.product-card--featured {}
.site-header.site-header--minimal {}

Teams that use native nesting often write&--featured inside .product-card — seeCSS nesting on the architecture page:

CSS code example
/* Same ownership when the team nests in component partials */
.product-card {
    &--featured {}
    &--compact {}
}

If you only add product-card--featured to the HTML without product-card, you lose the base component context in markup. If the behaviour disappears after interaction, it is probably not a modifier — use a state class instead.

State classes

Use .is_* for temporary UI state created by interaction, validation, loading, or progressive enhancement.

LSCSS uses two punctuation habits on purpose. Component and modifier names use hyphens(product-card, product-card--featured). State and condition names use an underscore only after the is or has prefix (is_loading, has_error). In markup, a double hyphen reads as a documented variant of the component; anis_ or has_ class reads as current behaviour or condition — not another BEM block. That reducesproduct-card--loading orproduct-card_loading when someone meant temporary state. Reserve underscores for state and condition class names, not for component or modifier roots.

HTML code example
<!-- Variant: hyphenated component + double-hyphen modifier -->
<article class="product-card product-card--featured">

<!-- Behaviour: separate is_* class (underscore after the prefix) -->
<article class="product-card is_loading">

CSS code example
.is_active {}
.is_disabled {}
.is_expanded {}
.is_loading {}

State should stay visible and explicit. Do not hide behaviour inside deep selectors and make debugging harder than necessary.

Condition classes

Use .has_* when the component has content, children, or a condition that affects styling but is not itself a reusable variant. Use the same underscore-after-prefix pattern asstate classes(has_error, not has-error orform-field--error for a validation condition).

CSS code example
.has_error {}
.has_results {}
.has_child {}
.has_media {}

A form field with an error is not a different form field model. It is the same field with a current condition.

Combined patterns

CSS code example
.product-card--featured.is_loading {}
.form-field.has_error {}
.disclosure.is_expanded {}

These classes work together because they describe different truths. A featured card can be loading. A disclosure can be expanded. Keep those concerns separate.

Avoid vague names

CSS code example
/* Avoid unclear state names */
.active {}
.open {}
.error {}

/* Prefer explicit state or condition names */
.is_active {}
.is_open {}
.has_error {}

Names like .active and .open spread fast and explain nothing. Prefixes make intent obvious and prevent reuse becoming accidental architecture.