Manual Style Overriding

Overriding

elements man style/overriding Read as markdown

The system is designed to be overridden cleanly. The cascade layer order guarantees your app's CSS wins, and semantic tokens let you re-skin everything from one place. You never need !important, and you never need to out-specify a component selector.

The cascade contract

The root entry declares one layer order:

@layer reset, tokens, base, components, utilities, overrides;

Later layers beat earlier layers regardless of selector specificity. That single fact is the override contract:

  • A low-specificity body rule of yours in a later position beats a high-specificity component selector, because layer order outranks specificity.
  • The overrides layer is reserved for your app and sits last, so anything you put there wins over everything the package ships.
  • Unlayered CSS beats all layers. Any rule you write outside a layer automatically wins over the entire package. So even app CSS that isn't in a layer at all takes precedence.

This is why bare-element styling and components never need !important to be overridden. You're never fighting specificity, only layer position, and yours is higher.

Three altitudes of override

Pick the lowest altitude that does the job.

1. Retarget a token (system-wide). Change a value once and every component that reads it follows. This is the primary mechanism, so reach for it first.

:root {
  --accent:  #4a7bc4;   /* rebrand every button, link, ring, selection */
  --radius:  8px;       /* rounder controls and surfaces everywhere */
  --ink:     #0f172a;   /* shift the primary text color */
}

In a scaffolded app, app/shared/styles/vars.css is exactly this file. It's imported by both the page and email baselines, so a token you set there applies across the whole app.

2. Restyle a component family (targeted). Write a rule against the component class in the overrides layer (or unlayered). Because you're in a later layer, your rule wins:

@layer overrides {
  .card {
    box-shadow: var(--shadow-md);
    border-radius: var(--radius-lg);
  }
}

3. One-off tweaks (local). Use a utility class, an is- modifier, or a page-scoped rule for a single element. Utilities already sit above components, so they win; a page's own style.css rule wins as app CSS.

<div class="card shadow-lg rounded-xl"></div>

Always use semantic tokens, not hex

When you override, reach for the semantic token, not a raw value or ramp step:

/* Do this: stays correct in light and dark mode */
.note { color: var(--ink-soft); background: var(--bg-soft); }

/* Not this: a hardcoded value opts out of theming and breaks dark mode */
.note { color: #475569; background: #f8fafc; }

A hardcoded color won't flip in dark mode and won't follow a rebrand. The semantic tokens (--ink*, --bg*, --rule*, --accent*, the status triples) exist so your overrides inherit theming for free.

Where app CSS goes

  • App-wide tokensapp/shared/styles/vars.css (imported by the page and email baselines).

  • App-wide rulesapp/shared/styles/page.css, after @import "@elements/style".

  • A full brand theme → copy themes/customer-example.css into app/shared/styles/ and import it from page.css after the package. See branding.

  • Page-specific rules → the page's own style.css, which imports #app/shared/styles/page.css and adds .<page-name>-* rules built on tokens.

Because all of these load after the package and either sit in overrides or run unlayered, they win cleanly, without !important.