Learn

Common CSS anti-patterns

Most CSS problems are architectural problems rather than syntax problems. They often start as small shortcuts that compound over time. Use the checklist and examples below to recognise those patterns early.

Common patterns at a glance

  • specificity spirals
  • deep selector nesting
  • global leakage
  • utility soup
  • component coupling
  • ID selectors
  • layout mixed into components
  • uncontained legacy CSS
  • !important abuse

Why these happen

Teams often create accidental complexity when architecture is missing, inconsistent, or disconnected from platform features.

Recognise the root cause first

Most anti-patterns are symptoms, not root causes. Before fixing the selector, ask what architectural decision created it.

  • Deep selectors usually mean unclear ownership.
  • Important declarations usually mean broken layer order.
  • Utility overload usually means missing components.
  • Random values usually mean missing tokens.
  • Modifier confusion usually means state and variants were mixed.
  • Large z-index values usually mean missing layering rules.

Fixing symptoms without fixing ownership creates cleaner-looking bad CSS. The underlying issue remains.

Deep selectors

Problem

Before, css code example
.page .wrapper .content .card .title {
    margin: 0;
}

Deep selectors couple styles to markup structure and make safe overrides harder.

Fix

After, css code example
.product-card > .title {
    margin: 0;
}

Use shallow scoped component selectors with clear ownership.

Specificity escalation

Problem

Before, css code example
#app .product-list .product-card .title {
    color: red !important;
}

IDs and important declarations create override battles and ongoing debugging cost.

Fix

After, css code example
@layer components, theme;

@layer theme {
    .product-card > .title {
        color: red;
    }
}

Check layer order before increasing specificity.

Utility overload

Problem

Before, html code example
<article class="p-l m-b-xl bg-dark text-light radius-m grid gap-m">
    ...
</article>

Too many utilities hide component intent and turn HTML into a styling spreadsheet.

Fix

After, html code example
<article class="product-card product-card--featured">
    ...
</article>

Use semantic components and keep utilities rare.

Random values everywhere

Problem

Before, css code example
.card {
    padding: 17px;
    border-radius: 13px;
    color: #4a2cff;
}

Magic numbers and random colours destroy consistency and weaken design systems.

Fix

After, css code example
.card {
    padding: var(--space-m);
    border-radius: var(--br-m);
    color: var(--c-accent);
}

Use tokens so spacing, colour, and radius stay predictable.

State disguised as modifiers

Problem

Before, css code example
.button--loading {}
.button--disabled {}
.button--active {}

Temporary UI behaviour gets mixed with reusable component variants.

Fix

After, css code example
.button {}
.button.is_loading {}
.button.is_disabled {}

Use modifiers for variants and .is_* classes for temporary state.

Z-index management

Prefer to avoid z-index whenever you can. Most stacking issues are really ownership and paint-order problems in disguise.

When you do need it, keep values as low as possible. A small, boring scheme is often enough: for example main content at 1, footer at1, and header at 2 so sticky headers (or other awkward layout choices) stay predictable without starting a numeric arms race.

Problem

Before, css code example
.modal {
    z-index: 999999;
}

If your z-index needs six digits, the problem is probably not ambition.

Fix

After, css code example
.modal {
    z-index: var(--layer-modal);
}

Use named layer tokens and predictable stacking rules instead of arbitrary large numbers.

For modals and similar overlays, prefer native patterns when they fit the content and context: <dialog>, the Popover API, or another appropriate built-in. The platform handles focus and the top layer, and you often do not need to think about z-index at all.

Next useful pages