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.
Can this exist as a documented reusable version?
YesModifier
NoNext question
Is this temporary UI behaviour?
Yes.is_* state class
NoNext question
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:
<!-- 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.
/* 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:
/* 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:
/* 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.
<!-- 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">.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).
.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
.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
/* 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.