// preview in
saved for every page

Scoped overrides

Change one area, one skin or one part without touching the rest. Tokens on an element, @scope rules and nested providers, and how the cml.* layers decide who wins.

// you'll learn

  • Three ways to limit an override: a token on an element, an @scope rule, a nested provider
  • The order of the cml.* layers, and why your CSS wins without !important
  • The pitfalls: islands, states you override by accident, !important, reduced motion

Try it

The card has a "danger zone" (<div className="danger-zone">). Each option restyles only that area; the code shown with it is what runs.

Workspace

Danger zone

Deleting the workspace removes 14 projects for everyone.

settings.tsx
<div className="danger-zone">
  <Text>Danger zone</Text>
  <Button variant="outline">Transfer</Button>
  <Button variant="danger">Delete workspace</Button>
</div>

No override — the skin as it ships.

Pick the narrowest tool

ToolUse it forReaches
A token on an elementValues: color, radius, size, spacingThat element and everything inside it
@scope (…) to (…)Anything a token can't doThe scope root down to the limit you set
A nested StyleProviderA different skin, mode or brandIts subtree, popups included

Start with tokens. A token keeps the skin's states working (hover, focus, disabled) because the skin still writes the rules; you only change the values they read.

app.css
/* Taller controls in the checkout only */
.checkout {
  --cml-control-height: 2.75rem;
  --cml-field-height: 2.75rem;
}
CAREFUL

The element must be inside the provider. Every [data-style] element declares all tokens again, so a class on a wrapper around the StyleProvider is overwritten before it reaches the components.

When the change is not a value (a border that isn't there, a different layout), write a rule inside @scope. Add to ([data-style]) so it stops at nested skins, the way the skin files do:

app.css
@scope (.pricing) to ([data-style]) {
  [data-slot="card"] {
    border-style: dashed;
  }
}

For a whole different look in one area, nest a provider. It is an island: it starts from clean tokens, and its popups keep its skin.

docs-page.tsx
<StyleProvider styleName="minimal">
  <Article />
  <StyleProvider styleName="retro" mode="dark">
    <Terminal />
  </StyleProvider>
</StyleProvider>

The layer order

base.css declares four layers before anything else. Later layers win over earlier ones, and CSS that is not in any layer wins over all of them:

@camaleo/styles/base.css
@layer cml.tokens, cml.components, cml.style, cml.preferences;
LayerHolds
cml.tokensEvery token's default (the minimal values) on :root and [data-style]
cml.componentsThe component rules, mostly inside :where(), so they add no specificity
cml.styleThe skin files: token values on [data-style="name"] and the skin's @scope rules
cml.preferencesReduced motion and forced colors
your unlayered CSSWins over all of the above, whatever the specificity

If your own CSS lives in a layer (a design system, a CSS framework), order the layers once, at the top of your entry stylesheet, before any import. A layer listed after cml wins over all of its sub-layers:

app.css
@layer cml, app;

@import "@camaleo/styles/base.css";
@import "@camaleo/styles/glass.css";

@layer app {
  [data-style] { --cml-radius-surface: 20px; }
}
TIP

Using Tailwind? Its utilities live in layers too, and which side wins depends on the same order. The Tailwind CSS guide has the line to add.

Specificity pitfalls

Before you ship it

  • Tokens first; a rule only when no token covers it
  • Token overrides on an element inside the provider
  • @scope rules end with to ([data-style])
  • No !important; layered CSS listed after cml in one @layer statement
  • Hover, focus and disabled still visible after your change, in light and dark
Was this page helpful? · Edit this page on GitHub