Skip to content

CSS Layers

remCSS uses @layer to create a deterministic, predictable cascade — without !important.

The Layer Order

Declared once, at the top of src/remcss.css, never changed:

css
@layer base, components, utilities;

Layers listed later win over layers listed earlier. The order means:

LayerWins overLoses to
baseBrowser defaultscomponents, utilities
componentsbaseutilities
utilitiesbase, components

Why This Matters

Without layers, specificity wars force you to write !important or invent elaborate selectors. With layers, you always know who wins — it's the layer order, not specificity.

css
/* Without layers — this .button rule loses to a 3-part selector elsewhere */
.button {
	color: red;
}
nav .site-header .button {
	color: blue;
} /* wins */

/* With layers — utilities always win regardless of selector complexity */
@layer utilities {
	.text-danger {
		color: var(--color-danger);
	} /* always wins over components */
}

What Goes in Each Layer

@layer base

  • CSS reset (box model, margin/padding reset, reduced motion)
  • Root font-size calibration (html { font-size: 62.5% })
  • All :root custom properties (tokens for scale, color, typography)
  • Font stack declarations
  • Heading size defaults

No component-specific rules belong here.

@layer components

  • .button, .button-group
  • .form, .field, .label, .input
  • .table, .table-wrapper
  • .grid, .stack, .cluster, .sidebar
  • Every rule is scoped with @scope (progressive enhancement)

@layer utilities

  • .m-*, .p-*, .px-*, .py-*, .mt-* — spacing
  • .flex, .flex-col, .items-center, .gap-* — flexbox
  • .grid-auto, .grid-sidebar — grid helpers

Utilities are intentionally single-purpose and override everything else in the cascade.

@scope Inside Components

All component rules are wrapped in @scope for additional isolation:

css
/* Fallback for browsers without @scope */
.button {
	padding-block: var(--step-n1);
	padding-inline: var(--step-1);
}

/* Progressive enhancement */
@supports selector(:scope) {
	@scope (.button) {
		:scope {
			padding-block: var(--step-n1);
			padding-inline: var(--step-1);
		}
	}
}

@scope prevents styles from leaking out of the component root. A .button inside a .card cannot accidentally affect a .button outside of it.

Unlayered Tokens

CSS custom properties (tokens) are declared on :root outside of any layer. This is intentional — custom properties must resolve before any layer rule references them. CSSOM processes unlayered rules first, making them always available.

css
/* Tokens — no @layer, always resolve first */
:root {
	--step-1: calc(1rem * 1.618);
	--color-accent: oklch(62% 0.22 27);
}

/* Components can safely reference them */
@layer components {
	.button {
		background: var(--color-accent); /* resolves correctly */
	}
}