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:
- 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. - 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:
| Brand | Item | Character |
|---|---|---|
| Nexus (default) | @nx-ui/theme | Navy + Aureolin yellow. Sets no data-brand. |
| Ultraviolet | @nx-ui/theme-ultraviolet | The Untitled UI signature purple. |
| Evergreen | @nx-ui/theme-evergreen | A deep regenerative forest green. |
| Ocean | @nx-ui/theme-ocean | A clear, confident blue. |
| Ember | @nx-ui/theme-ember | A warm, energetic orange. |
| Graphite | @nx-ui/theme-graphite | A restrained near-monochrome graphite. |
| Nexus Slate | @nx-ui/theme-nexus-slate | Keeps the Nexus colours; cool blue-slate surfaces and a deep blue-slate dark UI. |
| Nexus Sand | @nx-ui/theme-nexus-sand | Keeps the Nexus colours; warm paper-like surfaces and a warm charcoal dark UI. |
| Nexus Midnight | @nx-ui/theme-nexus-midnight | Keeps 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>:
bunx shadcn@latest add @nx-ui/theme-ultraviolet@import "../styles/nx-ui-theme.css";
@import "../styles/themes/ultraviolet.css"; /* path-relative to where it landed */<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.
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
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.
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.