Theming

The theme is the heart of the “looks like Untitled UI, branded Nexus” requirement. It ships as one CSS file with two layers, and every component is restyled through it before any per-component class delta.

The two-layer theme

@nx-ui/theme ships a single self-sufficient CSS file, styles/nx-ui-theme.css, with two layers stacked so that raw shadcn base-vega components look right with zero per-component work:

  1. Layer 1 — the full Untitled UI token system, Nexus-branded. Colour ramps 25–950 for brand (Nexus navy scaled around #121541), accent (Aureolin yellow around #F7EC33), the UUI cool-gray, and error/warning/success; the semantic UUI tokens that flip in dark mode (bg-*, text-*, fg-*, border-*, utility-*); the UUI type scale; the gray-900-tinted shadow scale; the radius scale; and the 4px brand-tinted focus ring.
  2. Layer 2 — the shadcn semantic variables mapped onto layer 1 (--background, --primary, --border, --ring, the --sidebar-* family…), so raw shadcn components work unmodified.

The class-vocabulary rules

Two namespaces coexist with no collisions. Getting this right is the single most important thing — getting it wrong silently breaks the brand.

shadcn semantics are canonical

For the common vocabulary, reach for the short shadcn utilities first: bg-primary (the brand fill), text-muted-foreground, bg-secondary, border-border, ring-ring, bg-card.

UUI full-token utilities

When you need a specific Untitled UI role that shadcn's shorter vocabulary doesn't name, use the prefixed full-token forms: bg-bg-primary / bg-bg-tertiary (surfaces), text-fg-quaternary / text-fg-brand-primary (foreground roles), border-border-secondary, bg-utility-brand-100.

Never use stripped-prefix utilities

text-tertiary, border-secondary, or bg-primary meaning the UUI surface don't exist. The theme deliberately omits the stripped-prefix re-export blocks (--background-color-*, …) — if they were re-added they'd make bg-primary resolve to a white surface and collide with the shadcn name. Use the shadcn form (text-muted-foreground) or the full UUI token (text-fg-quaternary).

Brand variants

The base @nx-ui/theme is the default Nexus brand (navy + Aureolin yellow). Drop-in variations layer on top of it and re-tint the whole surface — buttons, rings, sidebar, dark mode — by overriding only the brand ramp, accent ramp and focus ring. The 9 brands in the showcase:

BrandItemCharacter
Nexus (default)@nx-ui/themeNavy + Aureolin yellow. Sets no data-brand.
Ultraviolet@nx-ui/theme-ultravioletThe Untitled UI signature purple.
Evergreen@nx-ui/theme-evergreenA deep regenerative forest green.
Ocean@nx-ui/theme-oceanA clear, confident blue.
Ember@nx-ui/theme-emberA warm, energetic orange.
Graphite@nx-ui/theme-graphiteA restrained near-monochrome graphite.
Nexus Slate@nx-ui/theme-nexus-slateKeeps the Nexus colours; cool blue-slate surfaces and a deep blue-slate dark UI.
Nexus Sand@nx-ui/theme-nexus-sandKeeps the Nexus colours; warm paper-like surfaces and a warm charcoal dark UI.
Nexus Midnight@nx-ui/theme-nexus-midnightKeeps the Nexus colours; faintly navy light surfaces and an immersive brand-navy dark UI.

Install a variant after the base theme, import its CSS after nx-ui-theme.css, and switch it on with data-brand on <html>:

shell
bunx shadcn@latest add @nx-ui/theme-ultraviolet
globals.css
@import "../styles/nx-ui-theme.css";
@import "../styles/themes/ultraviolet.css";   /* path-relative to where it landed */
html
<html data-brand="ultraviolet"> … </html>

Each variant is scoped to html[data-brand="<name>"], so it stays inert until you set the attribute; remove it (or set none) to fall back to the default Nexus brand. To let users switch at runtime, set document.documentElement.dataset.brand, persist to localStorage, and restore it in an inline <head> script before paint to avoid a flash.

The showcase's BrandPicker (components/showcase/brand-picker.tsx) plus lib/brand-themes.ts are the reference implementation — try the picker in the sidebar to see every variant live.

Dark-mode behaviour

Dark mode is a class strategy: light-mode / dark-mode on <html> via next-themes, with the theme's own @custom-variant dark (&:where(.dark-mode, .dark-mode *)). Every semantic UUI token flips, and the shadcn layer flips with it.

primary flips navy → yellow in dark mode

By design — matching the nx app, --primaryis brand navy on light and the accent yellow on dark. Don't fight it with dark: overrides; let the token do it. Yellow (accent) is used sparingly — one highlight per view, and never yellow text on a white background.

Density mode

Density is orthogonal to colour. Comfortable is the default and sets no attribute; compact sets html[data-density="compact"], which a block in nx-ui-theme.css keys off to tighten control sizing and spacing across the surface. Toggle it the same way as the brand — set document.documentElement.dataset.density, persist to localStorage, and restore it in an inline <head> script before paint to avoid a flash.

The showcase's density toggle (components/showcase/density-toggle.tsx) plus lib/density.ts are the reference implementation — the same pre-paint, no-flash pattern as the brand picker.

The shadcn-compat shim

nx-ui-theme.css pulls in ./shadcn-compat.css (shipped by the same registry item): the custom variants (data-horizontal:, data-open:, …), no-scrollbar, scroll-fade-* and shimmer utilities that upstream base-vega components rely on — normally provided by @import "shadcn/tailwind.css", which nx-ui consumers do not have. It rides along with the theme import; you don't reference it directly.

Next: Components & blocks for the item types and install paths, or Foundations → Colours to see the ramps and semantic tokens rendered live.