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:

shell
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

The 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):

components.json
{
  "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
# .env.local (or your CI secret store)
NX_UI_GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

The 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:

components.json
{
  "registries": {
    "@nx-ui": "https://nx-ui.preview.nexusregen.com/r/{name}.json"
  }
}
The preview may later be locked down (Vercel Deployment Protection). The raw-GitHub path keeps working regardless, which is why it stays the default.

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:

shell
bunx shadcn@latest add @nx-ui/theme

This 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:

globals.css (before)
@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:

globals.css (final)
@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

The theme import must come after @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
// 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>
  )
}
  • suppressHydrationWarning on <html> is required — next-themes sets the class before React hydrates, so the server/client class differs by design.
  • Inter via next/font wired to --font-inter— the theme's font family reads that variable.

The .dark vs .dark-mode gotcha

If you omit the 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:

shell
bunx shadcn@latest add @nx-ui/button @nx-ui/card @nx-ui/dialog

Starter 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:

starter kit
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
Next, read Theming for the class-vocabulary rules and brand variants, and Components & blocks for the item types and where each block installs.