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.
| Requirement | Version | Note |
|---|---|---|
| Node.js | 20.9 or later | Required by Next.js 16 |
| React | 19.2 or later | Uses the current client component model |
| Next.js | 16.2 or later | App Router only |
| Tailwind CSS | 4.0 or later | The theme uses CSS-first @theme configuration |
| TypeScript | 5.0 or later | Strict 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.
npx shadcn@latest add https://nexbooth.app/r/button.jsonWhat 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:
@import "tailwindcss";
@import "./styles/borneo.css";<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.
npm install @borneo/react lucide-react class-variance-authority clsx tailwind-merge motionWhy 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.
@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.
@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.
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.
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.
| Symptom | Cause | Fix |
|---|---|---|
| Components render unstyled | The token stylesheet is not imported, or is imported after the theme block | Import the tokens immediately after Tailwind, before @theme |
| Borders are black | Tailwind v4 defaults border-color to currentColor | Add the base layer rule from step 3 |
| Fonts fall back to system sans | --font-inter is not defined on the html element | Apply the next/font variable className to <html> |
| “Functions cannot be passed to Client Components” | A render prop is being passed from a server component | Mark the file "use client", or move the interactive part into its own client module |
| ⌘K does nothing | The command menu provider is missing | Wrap the app in CommandMenuProvider at the root |