Skip to content
AstroCraft Docs
On this theme

Colors & Theming

Indexa’s colours are CSS-first. There is no JavaScript theme object and no tailwind.config.js colour map to keep in sync — everything is custom properties across two files, in three layers.

The three layers

Layer 1 — the palette, in src/styles/tailwind-theme.css under @theme. Two ramps of eleven steps: --color-primary-* is the brand, --color-base-* is neutral grey. These are raw values, and they are the only raw values in the project.

Layer 2 — the semantic vars, in src/styles/global.css under :root. --background, --foreground, --primary, --muted, --accent, --border and the rest, each mapped onto a palette step. This is the retheming surface: which grey is muted text, which blue is a border.

Layer 3 — the bridge, back in tailwind-theme.css under @theme inline. It ties each semantic var to a utility name, so bg-background and text-foreground resolve to the runtime variables rather than to literal values. The inline keyword is load-bearing — without it Tailwind would inline the value at build time and the indirection would be lost.

/* layer 1 */
--color-primary-900: #002a54;

/* layer 2 */
--foreground: var(--color-primary-900);

/* layer 3 */
--color-foreground: var(--foreground);

In markup you only ever touch layer 3: bg-primary, text-foreground, border-border, text-base-700. Never bg-sky-700 or text-zinc-300 — a raw Tailwind colour bypasses the whole system and survives a rebrand unchanged, which is exactly the bug you would not see.

The Indexa palette

Six steps of the primary ramp are exact values from the design; the in-between steps are interpolations for hover states.

Token Value What uses it
--color-primary-50 #f2f7fa The page background — an ice blue, not white
--color-primary-100 #d4eef5 Hairline borders, tints, icon tiles, tag pills
--color-primary-300 #9dd8e8 Tinted section bands
--color-primary-500 #3bbcd9 Brand cyan — the index mark, the active tab rule, focus rings
--color-primary-700 #1b7a94 Link teal
--color-primary-900 #002a54 Navy — body text, buttons, dark bands, the footer

The base ramp is standard neutral grey, anchored on the design’s muted text #575757 at step 600.

Then one colour comes from outside both ramps: --accent: #ff960a, the orange of the count and price pills. It is deliberately not a palette step, because it is the one hue in the system that is not the brand — its job is to be the only warm thing on a cool page. --warning is mapped to the same value rather than to a second orange, so there is exactly one.

Rebranding

Edit the six --color-primary-* values in tailwind-theme.css. That is the whole change for a hue swap: every border, band, button, link and focus ring follows, because none of them name a colour directly.

If your brand’s relationships differ from Indexa’s — say your darkest step is not what body text should be — remap in layer 2 instead. --foreground pointing at --color-base-900 rather than --color-primary-900 is a one-line change that takes the navy out of the type without touching the palette.

The radius scale works the same way. --radius: 0.5rem in :root is the base, and the --radius-xs through --radius-3xl steps in the bridge are all computed from it, so one value moves every corner in the system.

There is no dark mode

The token system is light-only, and that is a decision rather than an omission. There is no .dark block, no theme bootstrap script, no ThemeToggle primitive — the dark-mode machinery was removed, and html carries scheme-light so form controls and scrollbars agree.

If you need dark mode back, the shape is: a second :root block under [data-theme="dark"] or @media (prefers-color-scheme: dark) remapping the layer 2 vars only, plus a toggle that sets the attribute before first paint. Layers 1 and 3 do not change — that is the return on the indirection. Nothing in the markup changes either, because nothing in the markup names a colour.

Contrast

The palette is built so the pairings the design actually uses clear WCAG AA. Navy on the ice-blue page is 13.3:1, white on a navy button 14.4:1, muted #575757 on the page 6.7:1, navy on the cyan band 9.2:1.

Two pairings are worth knowing before you rebrand. The link teal on the page background is 4.57:1 — a pass for body text, with almost nothing to spare, so darkening --color-primary-700 is the safer direction if you shift the ramp. And the accent is a one-way street: navy on #ff960a is 6.6:1, white on it is 2.2:1. That is why --accent-foreground is the navy, and why swapping it for white would fail every price pill on the site at once.

NEXT STEPTypography