Getting started
Wire a consumer repo to the nx-ui registry, install the theme first, and add your first components. nx-ui is a shadcn registry, not an npm package — you vendor components into your own repo and nothing imports nx-ui at runtime.
Prerequisites
nx-ui components assume the nx-era stack. If your repo is not on all of these, fix that first — the components take them as given:
- Tailwind CSS v4, CSS-first (no
tailwind.config.js). - React 19 and the Next.js App Router.
- Bun as the package manager and runtime.
Fresh app? Initialise shadcn first
If the repo has never been shadcn init-ed (no components.json), initialise it so the plumbing exists:
bunx shadcn@latest init -d-d is non-interactive and selects the base-nova preset (Base UI) — the correct base for nx-ui, which is built on @base-ui/react.
Don't use init -b base -y
init -b base -y path still opens an interactive preset picker and -y does not skip it, so the command hangs. Use init -d.init writes components.json with "iconLibrary": "lucide" — leave it. nx-ui files carry their own @untitledui/icons imports and never consult the configured icon library, so it is harmless. The one thing init gets wrong for us is the globals.css it scaffolds — you strip that below. If components.json already exists, skip this step.
Wire the registry into components.json
Add an @nx-ui entry to the registries map in components.json. The same registry JSON is served from two hosts — pick one.
Option 1 — raw GitHub (the default)
The registry is served as static JSON from public/r/ in the private nexus-regen/nx-ui repo via raw.githubusercontent.com, authenticated with a GitHub token. This path is unaffected by the state of the Vercel deployment, so it is the reliable default (and agents usually already carry a gh token):
{
"registries": {
"@nx-ui": {
"url": "https://raw.githubusercontent.com/nexus-regen/nx-ui/main/public/r/{name}.json",
"headers": {
"Authorization": "token ${NX_UI_GITHUB_TOKEN}"
}
}
}
}Then set the token as an environment variable — never commit it to components.json or any tracked file:
# .env.local (or your CI secret store)
NX_UI_GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxThe token needs repo scope (classic PAT) or Contents: Read-only on nexus-regen/nx-ui (fine-grained PAT). Prefer a machine/service account, scope it tightly, and rotate it periodically. Keep the variable name exactly NX_UI_GITHUB_TOKEN — it is the name every consumer uses.
Option 2 — the Vercel preview (no token)
The showcase is deployed to Vercel and serves the same registry at https://nx-ui.preview.nexusregen.com/r/{name}.json, currently without authentication — the lighter path if you would rather not manage a token:
{
"registries": {
"@nx-ui": "https://nx-ui.preview.nexusregen.com/r/{name}.json"
}
}Install the theme first
The theme item ships the full design-token CSS file and is not an implicit dependency of every component. Install it once, explicitly, before anything else:
bunx shadcn@latest add @nx-ui/themeThis drops styles/nx-ui-theme.css into your project. Because the item carries only that CSS file, re-running add @nx-ui/theme later (e.g. to pick up a token change) never clobbers your components.
Wire globals.css — and strip the init scaffolding
nx-ui-theme.css is self-sufficient: it declares its own @theme block, its own :root / .dark-mode shadcn token blocks, its own @theme inline mapping, and its own @custom-variant dark (bound to .dark-mode). So the token scaffolding shadcn init writes is not just redundant — it actively breaks the theme, because its unlayered :root / .dark blocks sit after the theme import and win the cascade.
Two moves: add the theme import immediately after Tailwind, and remove everything shadcn init scaffolded that the theme already provides. A typical init globals.css — delete the highlighted scaffolding:
@import "tailwindcss";
@import "shadcn/tailwind.css"; /* ← remove */
@import "tw-animate-css";
@custom-variant dark (&:is(.dark *)); /* ← remove */
@theme inline { /* ← remove whole block */
--color-background: var(--background);
--color-foreground: var(--foreground);
/* …many more… */
}
:root { /* ← remove whole block */
--background: oklch(1 0 0);
--primary: oklch(0.205 0 0);
/* …full shadcn token set… */
}
.dark { /* ← remove whole block */
--background: oklch(0.145 0 0);
/* …full shadcn dark token set… */
}The complete finalfile — everything nx-ui needs, nothing it doesn't:
@import "tailwindcss";
@import "tw-animate-css"; /* keep if init added it; optional otherwise */
@import "../styles/nx-ui-theme.css"; /* path-relative to where the theme landed */
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground font-sans antialiased;
}
}Order matters
@import "tailwindcss";, and no :root / .dark token blocks may follow it. If styles still look default-shadcn (grey primary, wrong ring), you almost certainly left the init scaffolding in place.Configure next-themes
nx-ui's dark mode uses the class names light-mode / dark-mode on <html> (matching the nx app), not the shadcn default light / dark. Configure next-themes with the value map so it emits the right class:
// app/layout.tsx
import type { Metadata } from "next"
import { Inter } from "next/font/google"
import { ThemeProvider } from "next-themes"
import "./globals.css"
const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
})
export const metadata: Metadata = { title: "…" }
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={inter.variable} suppressHydrationWarning>
<body>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
value={{ light: "light-mode", dark: "dark-mode" }}
>
{children}
</ThemeProvider>
</body>
</html>
)
}suppressHydrationWarningon<html>is required —next-themessets the class before React hydrates, so the server/client class differs by design.Intervianext/fontwired to--font-inter— the theme's font family reads that variable.
The .dark vs .dark-mode gotcha
value map, next-themes writes dark and no dark: styles apply — the theme's variant is bound to .dark-mode. Always pass value={{ light: "light-mode", dark: "dark-mode" }}.Install your first components
Install by namespaced name, same as any shadcn registry item. Dependencies resolve automatically — npm packages are added at the versions nx-ui built against, and transitive nx-ui items (utils, shared hooks, blocks-on-components) install for you:
bunx shadcn@latest add @nx-ui/button @nx-ui/card @nx-ui/dialogStarter kit for a new app
A sensible first install for a fresh nx-era app — the theme, a core set of controls, and the app-shell family:
bunx shadcn@latest add @nx-ui/theme
bunx shadcn@latest add \
@nx-ui/button @nx-ui/input @nx-ui/field @nx-ui/dialog @nx-ui/dropdown-menu \
@nx-ui/avatar @nx-ui/badge @nx-ui/tooltip @nx-ui/sonner
# the app shell family:
bunx shadcn@latest add \
@nx-ui/app-shell @nx-ui/app-sidebar @nx-ui/sidebar-nav \
@nx-ui/org-switcher @nx-ui/user-menu @nx-ui/page-header