Modifiers and state

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.

TEXT code example
Ask:
Can this exist as a documented reusable version?
→ Modifier

Is this temporary UI behaviour?
→ .is_*

Is this describing present content or condition?
→ .has_*

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.

CSS code example
.product-card--featured
.product-card--compact
.site-header--minimal

If the state disappears after interaction, it is probably not a modifier.

State classes

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

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.

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.