Skip to content

Cascade layers (@layer)

Opt-in CSS cascade layers for predictable specificity against global CSS

By default, TypeStyles emits flat rules (no @layer), matching legacy behavior and keeping the API surface small.

When you opt in with a layers tuple on createStyles, createTokens, or the unified createTypeStyles factory, TypeStyles:

  1. Registers a single @layer a, b, c; preamble (once per distinct stack) so order is deterministic.
  2. Wraps each emitted rule block in @layer <name> { … } for the layer you pass on each style call.
  3. When createTokens({ layers, tokenLayer }) is used, :root custom properties and theme rules go into tokenLayer so utilities and components can override tokens predictably.

One config object gives you matching scopeId, layers, and tokenLayer for both class CSS and token/theme CSS:

ts
import { createTypeStyles } from 'typestyles';

const { styles, tokens } = createTypeStyles({
  scopeId: 'ds',
  mode: 'semantic',
  layers: ['reset', 'tokens', 'components', 'utilities'] as const,
  tokenLayer: 'tokens',
});

const reset = styles.class('reset', { margin: 0, padding: 0 }, { layer: 'reset' });

const button = styles.component(
  'button',
  {
    base: { padding: '8px 16px' },
    variants: {
      intent: { primary: { backgroundColor: '#0066ff' } },
    },
    defaultVariants: { intent: 'primary' },
  },
  { layer: 'components' },
);

tokens.create('color', { primary: '#0066ff' });

Conceptual output:

css
@layer reset, tokens, components, utilities;
@layer tokens {
  :root {
    /* --ds-color-* */
  }
}
@layer reset {
  .reset {
    margin: 0;
    padding: 0;
  }
}
@layer components {
  .button-base {
    padding: 8px 16px;
  }
}

createStyles only

Pass layers as a const tuple (or as { order, prependFrameworkLayers? }). Every styles.class, styles.hashClass, and styles.component call must include { layer: '…' }. Layer names must appear in the tuple (not in prependFrameworkLayers, which only affects ordering against external frameworks).

ts
const styles = createStyles({
  layers: { order: ['components'], prependFrameworkLayers: ['bootstrap'] },
});

styles.class('card', { padding: '1rem' }, { layer: 'components' });

createTokens with layers

When layers is set, tokenLayer is required. Token and theme CSS is wrapped in that layer.

ts
const tokens = createTokens({
  scopeId: 'app',
  layers: ['tokens', 'components'] as const,
  tokenLayer: 'tokens',
});

Default (no layers)

Omit layers everywhere: no @layer in output, and there is no layer option on style APIs.

Notes

  • Multiple instances: Each factory that passes layers owns its own stack; preamble keys include scopeId and full order so different design systems on one page stay distinct.
  • Build / SSR: Preamble rules are inserted at the front of the virtual sheet so getRegisteredCss() and extraction see ordering before wrapped blocks.
  • jsdom: Older parsers may log warnings for @layer; real browsers support cascade layers.

See also Class naming.