Anti-pattern gallery

CSS anti-pattern gallery

Most CSS problems do not arrive dramatically. They often start as small shortcuts and quick fixes that compound over time. This gallery helps you recognize those patterns early.

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(--colour-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 at 1, 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