Skip to content
API

Patterns ​

Common patterns for structuring design tokens with Magnesium.

Global token layer ​

Define all your tokens at :root under a named CSS cascade layer so they land at the bottom of the specificity stack and can be overridden safely anywhere.

scss
@use "@magnesium/theme";

$color-tokens: (
    "primary": #0071d7,
    "on-primary": #fff,
    "surface": #f5f5f5
);

$shape-tokens: (
    "small": 4px,
    "medium": 8px,
    "large": 16px
);

:root {
    @include theme.emit($color-tokens, "color", $layer: "tokens");
    @include theme.emit($shape-tokens, "shape", $layer: "tokens");
}
css
@layer tokens {
    :root {
        --mg-color-primary: #0071d7;
        --mg-color-on-primary: #fff;
        --mg-color-surface: #f5f5f5;
        --mg-shape-small: 4px;
        --mg-shape-medium: 8px;
        --mg-shape-large: 16px;
    }
}

Layer order ​

Cascade layers are ordered by where each name first appears in the compiled CSS, not by how often it is used. Magnesium does not emit an order statement, so declare yours once at the top of your entry file:

scss
@layer reset, tokens, components;

Otherwise the order follows the first $layer Magnesium happens to emit — for instance base before tokens if base is emitted first — and any later emission into an already-seen layer is appended to it.

Component theming ​

The recommended pattern for a themeable component:

  1. Define a reference map with default values.
  2. Emit defaults at :root.
  3. Consume tokens in component rules with variable().
  4. Let consumers override via theme().
scss
@use "@magnesium/theme";

// 1. Default token values
$_defaults: (
    "text-color": #fff,
    "bg-color": #0071d7,
    "border-radius": 4px
);

// 2. Emit defaults at root
:root {
    @include theme.emit($_defaults, "button");
}

// 3. Consume tokens in the component
.button {
    color: theme.variable($_defaults, "text-color", "button");
    background: theme.variable($_defaults, "bg-color", "button");
    border-radius: theme.variable($_defaults, "border-radius", "button");
}
css
:root {
    --mg-button-text-color: #fff;
    --mg-button-bg-color: #0071d7;
    --mg-button-border-radius: 4px;
}

.button {
    color: var(--mg-button-text-color);
    background: var(--mg-button-bg-color);
    border-radius: var(--mg-button-border-radius);
}

A consumer overrides the component theme with theme(), which validates tokens before emitting:

scss
.card .button {
    @include theme.theme($_defaults, (
        "bg-color": darkorange
    ), "button");
}
css
.card .button {
    --mg-button-bg-color: darkorange;
}

Dark mode ​

Media query (automatic) ​

scss
@use "@magnesium/theme";

$dark-tokens: (
    "primary": #00b2e9,
    "surface": #1a1a1a
);

@include theme.scheme("dark") {
    :root {
        @include theme.emit($dark-tokens, "color");
    }
}
css
@media (prefers-color-scheme: dark) {
    :root {
        --mg-color-primary: #00b2e9;
        --mg-color-surface: #1a1a1a;
    }
}

Explicit selector (user-controlled) ​

When using $selector, emit tokens directly inside the scheme() block — do not nest :root inside it, as that would generate an invalid descendant selector.

scss
@include theme.scheme("dark", $selector: "[data-theme='dark']") {
    @include theme.emit($dark-tokens, "color");
}
css
[data-theme=dark] {
    --mg-color-primary: #00b2e9;
    --mg-color-surface: #1a1a1a;
}

Color scheme and flash of wrong theme ​

Declare color-scheme next to the tokens so form controls and scrollbars follow the theme:

scss
@include theme.scheme("dark", $selector: "[data-theme='dark']") {
    color-scheme: dark;
    @include theme.emit($dark-tokens, "color");
}

With a selector-based theme, set the attribute before the first paint, with a small inline script in <head>, to avoid a flash of the light theme:

html
<script>
    document.documentElement.dataset.theme =
        localStorage.getItem('theme') ??
        (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
</script>

Cross-namespace references ​

Use ref() to reference a token from another namespace without hardcoding the prefix. This is useful for aliasing system tokens to component tokens.

scss
@use "@magnesium/theme";

$sys-tokens: (
    "primary": #0071d7
);

:root {
    @include theme.emit($sys-tokens, "sys");
}

// Button aliases the system token via ref()
$button-tokens: (
    "bg-color": theme.ref("sys-primary")
);

:root {
    @include theme.emit($button-tokens, "button");
}
css
:root {
    --mg-sys-primary: #0071d7;
    --mg-button-bg-color: var(--mg-sys-primary);
}

Aliasing a whole token layer ​

ref() aliases one token at a time. To alias an entire layer at once, pass the tokens through refs() and emit the result: every value becomes a var() reference to the source layer, keeping the original value as fallback.

scss
@use "@magnesium/theme";

$sys-tokens: (
    "primary": #0071d7,
    "surface": #f5f5f5
);

// The system layer holds the real values.
:root {
    @include theme.emit($sys-tokens, "sys");
}

// The component layer points at it — one var() per token.
:root {
    @include theme.emit(theme.refs($sys-tokens, "sys"), "button");
}
css
:root {
    --mg-sys-primary: #0071d7;
    --mg-sys-surface: #f5f5f5;
}

:root {
    --mg-button-primary: var(--mg-sys-primary, #0071d7);
    --mg-button-surface: var(--mg-sys-surface, #f5f5f5);
}

The two namespaces play different roles: the one given to refs() builds the names being pointed at, the one given to emit() builds the names being declared.

Overriding --mg-sys-primary then cascades to every component token aliased to it, while the fallback keeps the component working even if the system layer is never emitted.