Natcore LogoNatCorev2 — In Progress
v2 — In Progress

Theming

Themes provide palette identity while Natcore's adaptive color tokens keep the same UI working in light and dark schemes.

How themes work

A theme is a set of semantic color anchors. Components keep using token classes, and the active theme changes the values underneath.
01Choose semantic roles

Themes set palette roles like primary, secondary, accent, surface, danger, and success.

02Resolve anchors

Each role can provide 50, 500, and 950 anchors. If a role only provides one color, that color becomes the 500 anchor.

03Derive shade ramps

Natcore derives missing 50 and 950 anchors, then interpolates the full shade ramp and matching on-color text values.

04Mirror dark colors

Dark-mode tokens point to the opposite side of the light ramp, so contrast flips without new markup.

05Resolve adaptive tokens

Component and utility classes use adaptive tokens like color-primary-500, which resolve through the active scheme.

Theme identity

Use data-theme to select a named palette. The selector defines role values Natcore uses to build the rest of the token system.
Codecss
:root[data-theme="citrine-reef"] {
--theme-primary: #087f8c;
--theme-secondary: #f59e0b;
--theme-accent: #ef476f;

--theme-surface-50: #f4fbf7;
--theme-surface-500: #678f82;
--theme-surface-950: #071c18;

--theme-danger: #c2415d;
--theme-success: #26966f;
}
Stable markup

Classes like btn-solid/primary and card-soft/surface stay the same when the selected theme changes.

Anchor system

Every palette role can define explicit 50, 500, and 950 anchors. If those anchors are not provided, the role color becomes the 500 anchor and Natcore derives 50 and 950 from it.
Explicit anchors

Use this when a brand palette already has intentional light, middle, and dark stops.

Codecss
:root[data-theme="custom"] {
--theme-primary-50: #eef8ff;
--theme-primary-500: #087f8c;
--theme-primary-950: #061a21;
}
Single color fallback

Use this when you only know the main role color. Natcore treats it as 500 and derives the outer anchors.

Codecss
:root[data-theme="custom"] {
--theme-primary: #087f8c;
}

/* Equivalent anchor behavior:
primary-500 uses #087f8c.
primary-50 and primary-950 are derived. */
50

The light anchor. Used for soft fills, subtle surfaces, and the light end of the role ramp.

500

The center anchor. A bare role variable like --theme-primary resolves here.

950

The dark anchor. Used for strong contrast, deep fills, and the mirrored side of dark mode.

Light and dark

Natcore treats theme choice and light/dark choice as separate concerns. data-theme selects the palette; the scheme class controls which side of each adaptive token is used.
Codehtml
<html class="scheme-light-dark" data-theme="citrine-reef">
<body>
<button class="btn-solid/primary">
Create project
</button>
</body>
</html>
scheme-light

Always resolves adaptive tokens to the light color ramp.

scheme-dark

Always resolves adaptive tokens to the dark color ramp.

scheme-light-dark

Lets the browser resolve light-dark tokens from the user's color scheme.

Adaptive token resolution

For each palette and shade, Natcore exposes explicit light and dark variables plus an adaptive variable that chooses between them.
Codecss
@theme {
--color-primary-50: light-dark(
var(--color-light-primary-50),
var(--color-dark-primary-50)
);

--color-dark-primary-50: var(--color-light-primary-950);
}
Contrast travels with the token

On-color tokens follow the same pattern. A surface can use bg-primary-500 while text uses text-on-primary-500, and both resolve together.

Authoring guidance

Theme authors can provide one main color for speed or explicit anchors for control, then let Natcore produce the adaptive values consumed by components.
Anchor only what matters

Use explicit 50, 500, and 950 anchors for roles where the exact ramp matters. Use a single role color when derived anchors are enough.

Keep roles semantic

Primary should mean primary action across every theme, even when its actual hue changes.

Test both schemes

Check light and dark because dark mode uses mirrored shade relationships, not a separate set of component classes.

On this page