Theming

nsdev/ui is themed with design tokens declared once and used everywhere. Components never use raw values, so changing a token restyles every component that uses it.

Tokens

The tokens live in the @theme block of src/app/globals.css. Tailwind CSS v4 is configured in CSS, so there is no tailwind.config.js: each token in that block becomes a CSS variable and a set of utilities.

A color token like --color-surface gives you bg-surface, text-surface, border-surface and the rest, plus var(--color-surface) for plain CSS. Components are built from those utilities:

<div className="rounded-lg border border-border bg-surface text-text" />

Text on fills

Many themes pair every surface with a matching foreground token. nsdev/ui keeps the palette small and uses one rule instead: the text or icon on top of a solid accent, success, warning or danger fill is always bg, never text.

<div className="bg-warning text-bg" />

Light text on these fills fails contrast badly, as low as 1.08:1. Dark text on them sits between 5.5:1 and 14.6:1 across all four.

Color tokens

TokenValueWhat it is for
--color-bg#0A0A0AThe page background.
--color-surface#171717Cards and panels.
--color-border#262626The default hairline border.
--color-border-strong#404040Emphasized borders, and the off-state track of a toggle.
--color-muted#525252Structural only: dividers and disabled fills. Never text.
--color-text-secondary#737373Labels, captions and larger text only.
--color-text#E5E5E5Primary text.
--color-accent#00FF66The default button, and active and on states inside components.
--color-success#00FF66Success states. The same green as accent.
--color-warning#FFB020Warning states.
--color-danger#FF3355Destructive actions and error states.

accent and success share one value on purpose. A single green covers both roles, so don't split them.

Usage rules

  • muted is structural only. Use it for dividers and disabled fills, never for text. It measures 2.53:1 on bg, which fails AA.
  • text-secondary is for labels, captions and larger text. At 4.18:1 on bg it is just under AA for small body copy, so keep it out of paragraphs.
  • Green stays inside components. The site around them is neutral. Green shows a component's active, on or success state.
  • Soft alerts use a wash. For inline alerts and banners, use about 9% opacity of the semantic color as the background and about 30% as the border. Keep the full color for the indicator dot, the icon or the button.

The contrast ratios above are measured against the default bg.

Radius

Corners scale with the size of the component, and nothing rounds past 8px. These tokens override Tailwind's built-in rounded-sm, rounded-md and rounded-lg, so those are the utility names to use.

TokenValueWhat it is for
--radius-sm4pxSmall controls: checkboxes, badges, tags.
--radius-md6pxButtons, inputs, small cards.
--radius-lg8pxCards, panels, dialogs, dropdowns. The ceiling.

Typography

Two faces cover everything: Host Grotesk for interface text and headings, Geist Mono for code. They are exposed as --font-sans and --font-mono, which give you font-sans and font-mono. The scale and usage are on the Typeset page.

Light mode

nsdev/ui ships a single dark theme for now. A light theme is planned for the polish phase. It will be defined by overriding these same tokens, so components won't need to change.

Adding a token

Declare it in the @theme block and Tailwind generates the utilities for you.

@theme {
  --color-info: #4DA3FF;
}

You can now use bg-info, with text-bg on top by the rule above. Check that bg stays readable on your new color.

Theme source

This is the live theme, read from src/app/globals.css when the site is built, so it always matches what the components use.

@theme {
  --color-bg: #0A0A0A;
  --color-surface: #171717;
  --color-border: #262626;
  --color-border-strong: #404040;
  --color-muted: #525252;
  --color-text-secondary: #737373;
  --color-text: #E5E5E5;
  --color-accent: #00FF66;
  --color-success: #00FF66;
  --color-warning: #FFB020;
  --color-danger: #FF3355;

  --font-sans: var(--font-host-grotesk), system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: var(--font-geist-mono), 'SF Mono', Consolas, monospace;

  --radius-sm: 4px;
  --radius-md: 6px;
  --radius-lg: 8px;
}