Skip to content

Getting Started

Installation

Four steps: install, import the tokens, map them into your Tailwind theme, and add the two providers. After that, every component works.

RequirementsPermalink to this section

The versions this system is built and tested against.

RequirementVersionNote
Node.js20.9 or laterRequired by Next.js 16
React19.2 or laterUses the current client component model
Next.js16.2 or laterApp Router only
Tailwind CSS4.0 or laterThe theme uses CSS-first @theme configuration
TypeScript5.0 or laterStrict mode, with target ES2022

Add components with the CLIPermalink to this section

The fastest way in: copy a component's source straight into your project with the shadcn CLI. No package to install — you own the code, and it stays editable.

Terminal
npx shadcn@latest add https://nexbooth.app/r/button.json

What gets installed

The first add also pulls the Borneo base item — lib/cn.ts and the token stylesheet at styles/borneo.css — because every component lists it as a registry dependency. Import that stylesheet once, after Tailwind, and set a colour mode on the document:

src/app/globals.css
@import "tailwindcss";
@import "./styles/borneo.css";
src/app/layout.tsx
<html lang="en" data-theme="light">

Prefer a package?

The steps below install Borneo as a single npm package instead. Use whichever model you prefer — the components are identical.

1 · Install the packagePermalink to this section

The package plus the peer dependencies it composes on.

Terminal
npm install @borneo/react lucide-react class-variance-authority clsx tailwind-merge motion

Why these dependencies

class-variance-authority declares component variants, tailwind-merge lets a caller override any variant class, and the Radix primitives supply focus management and ARIA semantics that are genuinely difficult to reimplement correctly. Everything else is built from React and CSS.

2 · Import the tokensPermalink to this section

Tokens ship as CSS custom properties. Import them once, before your Tailwind theme block.

src/app/globals.css
@import "tailwindcss";
@import "@borneo/react/tokens.css";

3 · Map the themePermalink to this section

`@theme inline` points Tailwind's colour namespace at the semantic tokens, so utilities resolve the variable at the point of use rather than freezing its value at build time.

src/app/globals.css
@theme inline {
  --color-canvas: var(--background);
  --color-ink: var(--foreground);
  --color-ink-muted: var(--muted-foreground);
  --color-ink-faint: var(--faint-foreground);

  --color-surface: var(--surface);
  --color-surface-hover: var(--surface-hover);
  --color-surface-raised: var(--surface-raised);

  --color-line-subtle: var(--border-subtle);
  --color-line: var(--border-default);
  --color-line-strong: var(--border-strong);

  --color-brand: var(--brand-primary);
  --color-brand-hover: var(--brand-primary-hover);
  --color-brand-soft: var(--brand-primary-soft);

  --spacing: var(--space-4);
  --font-sans: var(--font-family-sans);
  --font-mono: var(--font-family-mono);
}

@layer base {
  /* Tailwind v4 defaults borders to currentColor. */
  *,
  *::before,
  *::after {
    border-color: var(--border-default);
  }
}

4 · Add the providersPermalink to this section

Two providers at the root: one shares tooltip timing across the app, the other owns the command menu and its keyboard shortcut.

src/app/layout.tsx
import { Inter } from "next/font/google";
import { TooltipProvider } from "@borneo/react";

import "./globals.css";

// Self-hosted, preloaded, no layout shift. The variable is
// what --font-sans points at.
const inter = Inter({
  subsets: ["latin"],
  display: "swap",
  variable: "--font-inter",
});

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={inter.variable}>
      <body>
        {/* First tab stop on every page */}
        <a href="#main-content" className="sr-only focus:not-sr-only">
          Skip to content
        </a>

        <TooltipProvider delayDuration={320} skipDelayDuration={300}>
          {children}
        </TooltipProvider>
      </body>
    </html>
  );
}

VerifyPermalink to this section

Render a button and a form field. If both look right and the field's focus ring uses the brand colour, the tokens and the theme are wired correctly.

src/app/page.tsx
import { Button, FormField, Input } from "@borneo/react";

export default function Page() {
  return (
    <main id="main-content" className="mx-auto max-w-md p-8">
      <FormField
        id="project-name"
        label="Project name"
        description="Shown in the sidebar and in deployment logs."
        required
      >
        {(field) => <Input {...field} placeholder="Q3 marketing site" />}
      </FormField>

      <Button className="mt-4">Create project</Button>
    </main>
  );
}

TroubleshootingPermalink to this section

The three things that go wrong, and what each one means.

SymptomCauseFix
Components render unstyledThe token stylesheet is not imported, or is imported after the theme blockImport the tokens immediately after Tailwind, before @theme
Borders are blackTailwind v4 defaults border-color to currentColorAdd the base layer rule from step 3
Fonts fall back to system sans--font-inter is not defined on the html elementApply the next/font variable className to <html>
“Functions cannot be passed to Client Components”A render prop is being passed from a server componentMark the file "use client", or move the interactive part into its own client module
⌘K does nothingThe command menu provider is missingWrap the app in CommandMenuProvider at the root