Natcore LogoNatCore

Palette

A palette is a single color expanded into a full 50-950 shade ramp. Every palette comes from a theme: a theme assigns a color to each role, and Natcore builds the ramp, mirrors it for dark mode, and exposes adaptive tokens. You can also define your own palettes.

Palettes come from themes

A theme is a whole color scheme; a palette is one color within it. The theme assigns a color (or anchors) to each role, and Natcore turns each into a full ramp through the same anchor model — so every palette, built-in or custom, works the same way.
01A theme assigns a color

A theme gives each role — primary, surface, and the rest — a single color or explicit anchors.

02Resolve anchors

A lone color becomes the 500 anchor; 50 and 950 derive from it. Any shade can be anchored explicitly.

03Derive the ramp

Unanchored shades interpolate between the bracketing anchors, and a readable on-color is generated for each.

04Mirror for dark

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

05Expose tokens

The palette surfaces adaptive tokens like color-primary-500 that utilities and components read.

Light and dark

A palette is built once on the light side, then mirrored for dark: dark-{shade} points at the light ramp's opposite end — 50↔950, 100↔900, and so on, with 500 unchanged. A shade number keeps its semantic role but resolves to the other end of the ramp, so bg-surface-50 is a light surface in light mode and a dark surface in dark mode, with no markup change. The on-colors mirror the same way, so contrast flips automatically. Which side is active is the separate scheme axis — see Theming.
Light scheme
--color-light-primary-{shade}
50100200300400500600700800900950
Dark scheme
--color-dark-primary-{shade} — the same ramp, reversed
50100200300400500600700800900950
Codecss
/* Dark is the light ramp, mirrored end-to-end */
--color-dark-primary-50: var(--color-light-primary-950);
--color-dark-primary-100: var(--color-light-primary-900);
/* …200↔800, 300↔700, 400↔600, 500 stays… */
--color-dark-primary-950: var(--color-light-primary-50);

/* The adaptive token picks the active scheme's side */
--color-primary-500: light-dark(
var(--color-light-primary-500),
var(--color-dark-primary-500)
);

Built-in palettes

Natcore ships two families of ready-made palettes: the semantic theme-role palettes and every built-in Tailwind color. Both expand through the same anchor model, flip in dark mode, and auto-contrast their text.

Theme role palettes

A theme defines six semantic roles. Markup references the role, not a hex, so the same components work across every theme — see Theming for what each role is for. Each ramp below is live, rendered from this site's active theme.
primary
bg-primary-500 · palette-primary
50100200300400500600700800900950
secondary
bg-secondary-500 · palette-secondary
50100200300400500600700800900950
accent
bg-accent-500 · palette-accent
50100200300400500600700800900950
surface
bg-surface-500 · palette-surface
50100200300400500600700800900950
success
bg-success-500 · palette-success
50100200300400500600700800900950
danger
bg-danger-500 · palette-danger
50100200300400500600700800900950
What each role means

Theming documents the intent of primary, secondary, accent, surface, success, danger, and disabled.

Tailwind color palettes

Every built-in Tailwind color is available as a palette-{color} class out of the box. Like role palettes, they flip in dark mode (50 <-> 950), auto-contrast their text, and expose a per-shade --theme-{color}-{shade} override.
red
50100200300400500600700800900950
orange
50100200300400500600700800900950
amber
50100200300400500600700800900950
lime
50100200300400500600700800900950
emerald
50100200300400500600700800900950
teal
50100200300400500600700800900950
sky
50100200300400500600700800900950
blue
50100200300400500600700800900950
indigo
50100200300400500600700800900950
violet
50100200300400500600700800900950
fuchsia
50100200300400500600700800900950
rose
50100200300400500600700800900950
Supported colors

Every built-in Tailwind color is available as a palette class.

slate, gray, zinc, neutral, stone, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose

Use aspalette-{color}— for examplepalette-emerald

Using a palette

There are three ways to reach a palette's colors: name it inline on a utility, set it once as the current tone and use shade-only utilities, or pass it as a component modifier.

Named color utilities

Any color utility accepts {palette}-{shade}. On-colors pair with backgrounds so text stays readable.
Codehtml
<!-- Name the palette inline with any color utility -->
<button class="bg-primary-500 text-on-primary-500">
Save
</button>
<span class="text-secondary-700">Secondary label</span>
<div class="border border-accent-500">Accent border</div>
bg-primary-500bg-accent-500text-secondary-700

The palette class & tone utilities

Set the current tone once with palette-{name}, then children use shade-only tone utilities like bg-{shade} and text-on-{shade}. Swap the palette class and the whole subtree re-tones with no other markup changes.
Codehtml
<!-- A design-system role as the current palette -->
<div class="palette-primary">
<button class="bg-500 text-on-500">Solid</button>
<div class="bg-100 text-on-100">Soft fill</div>
<p class="text-700">Body copy in the active palette</p>
</div>

<!-- Same markup, a built-in Tailwind color -->
<div class="palette-emerald">...</div>
palette-primary (role)
bg-100bg-500bg-800
palette-emerald (built-in)
bg-100bg-500bg-800

Component modifiers

Components accept a /{palette} modifier, which sets the current tone for that component instance — a design-system role or a built-in Tailwind color both work.
Codehtml
<!-- A /{palette} modifier accepts a role… -->
<button class="btn-solid/primary">Primary</button>
<button class="btn-soft/accent">Accent</button>

<!-- …or a built-in Tailwind color -->
<button class="btn-solid/emerald">Emerald</button>
<button class="btn-soft/sky">Sky</button>

Custom palettes

Beyond the six built-in roles, you can declare your own named palettes with the @natcore-palettes at-rule. They use the exact same anchor model, utilities, component modifiers, and per-shade tree-shaking — they are palettes in every way.

1. Install the transform

The @natcore-palettes at-rule is expanded by a Natcore plugin that must run BEFORE Tailwind. Add the PostCSS plugin, or the Vite variant for Vite-based setups. This step is required before the at-rule will do anything.
PostCSS

Add it to postcss.config.js ahead of @tailwindcss/postcss.

Codejs
// postcss.config.js — the Natcore transform runs BEFORE Tailwind
module.exports = {
plugins: {
"@nataliebasille/natcore-design-system/postcss": {},
"@tailwindcss/postcss": {},
autoprefixer: {},
},
};
Vite

Add it to vite.config ahead of @tailwindcss/vite.

Codets
// vite.config.ts — use the Vite variant instead
import natcorePalettes from "@nataliebasille/natcore-design-system/vite";
import tailwindcss from "@tailwindcss/vite";

export default defineConfig({
// natcorePalettes() must come before tailwindcss()
plugins: [natcorePalettes(), tailwindcss()],
});

2. Declare palettes

With the plugin installed, declare each palette with the flat {name} / {name}-{shade} syntax. It is the same anchor model a theme uses for its roles: a single color, 50/500/950 anchors, intermediate nudges, or a fully explicit ramp. Order does not matter.
Codecss
@import "tailwindcss";
@import "@nataliebasille/natcore-design-system";

@natcore-palettes {
/* A single color becomes the 500 anchor; 50/950 and the
rest of the ramp are derived — the same anchor model a
theme uses for its roles. */
brand: #f04e23;

/* Or pin explicit anchors / nudge any shade 50-950. */
info-50: #eaf6ff;
info-500: #0099ff;
info-950: #001a33;
}
Single color

brand: #f04e23 becomes the 500 anchor; Natcore derives 50/950 and interpolates the rest.

Anchors & nudges

name-50, name-500, name-950 pin the outer anchors; name-300 (etc.) nudges one step.

Full ramp

Provide all eleven shades to reproduce an exact palette — like the pumpkin example below.

3. Example: pumpkin

A fully explicit eleven-shade ramp. Every swatch below is live — rendered from the generated light-side --color-light-pumpkin-{shade} tokens (the canonical 50→950 ramp) in this site's own theme.
Codecss
@natcore-palettes {
pumpkin-50: #fff5e9;
pumpkin-100: #ffe3c4;
pumpkin-200: #f9c489;
pumpkin-300: #f2a04a;
pumpkin-400: #eb7d1e;
pumpkin-500: #dd5f10;
pumpkin-600: #c24a0e;
pumpkin-700: #a23a12;
pumpkin-800: #7f2f14;
pumpkin-900: #6a2810;
pumpkin-950: #5a230a;
}
Generated ramp

--color-pumpkin-50 through --color-pumpkin-950, each paired with a readable --color-on-pumpkin-{shade} text color.

50100200300400500600700800900950

4. Use it like any palette

A custom palette unlocks the full token surface: native color utilities, the palette-{name} tone modifier, and component modifiers like btn-solid/pumpkin.
Codehtml
<!-- Everything a built-in palette can do, your palette can too -->
<div class="bg-pumpkin-500 text-on-pumpkin-500">
Pumpkin
</div>
<button class="btn-solid/pumpkin">Carve</button>
<section class="palette-pumpkin">
<span class="bg-500 text-on-500">
Current tone = pumpkin
</span>
</section>
Live pumpkin
card-soft/pumpkin

Component modifiers accept the custom palette name just like a built-in role.

palette-pumpkin
bg-100bg-500bg-800

Tree-shaking

Because the transform generates custom palettes through the same pipeline as the built-ins, Tailwind prunes what you don't use.
Per shade

Using bg-pumpkin-500 alone emits only that shade's tokens — not the whole ramp.

Per palette

palette-pumpkin pulls the shades it needs; a registered-but-unused palette emits nothing.

No manual CSS

You never hand-write shade variables — declare the anchors and the ramp, dark mode, and contrast are generated.

On this page