Installation#
Beaket UI adds components to an existing React and Tailwind project. Point the
@ alias at your source directory, run init to write the tokens, then copy in
the parts you need.
Requirements#
- React >=19.0.0 (
tabsrequires React >=19.2.0;data-tablerequires React >=19.2.0) - Tailwind CSS 4+
- TypeScript
Beaket UI adds components to an existing React and Tailwind project. If you don’t have one yet, create a Vite app first:
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm install tailwindcss @tailwindcss/vite
Setup#
Vite#
Add Tailwind and the @ alias to vite.config.ts. The TypeScript path below
only helps TypeScript; Vite needs its own alias too.
import { fileURLToPath, URL } from "node:url";
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});
Then add Tailwind to src/index.css:
@import "tailwindcss";
Finally, configure the matching TypeScript alias:
// tsconfig.app.json → compilerOptions
"paths": {
"@/*": ["./src/*"]
}
Next.js#
Keep the default @/* alias, Tailwind CSS 4, and the global CSS import in your
App Router layout. Hook-based components already declare "use client"; your
page can stay a Server Component.
In a server page, import compound parts by name. Attached properties such
as Tabs.List do not cross a client-module boundary:
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
export default function Page() {
return (
<Tabs defaultValue="account">
<TabsList>
<TabsTrigger value="account">Account</TabsTrigger>
</TabsList>
<TabsContent value="account">Account settings</TabsContent>
</Tabs>
);
}
Run npx @beaket/ui add tabs for this example. Tabs.List and the other
compound forms still work inside Client Components. Event handlers and
function props (for example, DataTable cell renderers or Pagination’s
buildPageUrl) belong in a Client Component too. For Dialog and Sheet in a
server page, use trigger={<Button>Open</Button>} with named content parts.
PostCSS alternative#
Tailwind’s PostCSS plugin is supported too. On Vite, use it instead of
@tailwindcss/vite; keep the React plugin and both alias configurations above.
Next.js projects created with Tailwind already include this PostCSS setup.
npm install tailwindcss @tailwindcss/postcss postcss
// postcss.config.mjs
export default {
plugins: { "@tailwindcss/postcss": {} },
};
Keep @import "tailwindcss"; in your global CSS and import that stylesheet
from your app entry or layout. See the Tailwind PostCSS setup.
Initialize#
npx @beaket/ui init
init writes the complete Beaket foundation into that CSS file: type scale,
font stacks and weights, line heights, spacing, borders, radii, semantic
tokens, and the selected color and shadow palette. The generated block is
marked so later add and theme commands can update it without touching the
rest of your stylesheet.
Or with a theme:
npx @beaket/ui init --theme tobacco
Add Components#
npx @beaket/ui add button
With the Vite setup above, the default destination is src/components/ui/, so
you can import a component like this. This import requires the matching
TypeScript and bundler aliases from Vite setup, or Next.js’s default alias:
import { Button } from "@/components/ui/button";
If you chose a different component directory during init, import from that
directory instead.
See CLI for all options.
Icons#
Beaket UI does not ship an icon set of its own. Components that draw a fixed
glyph — the check in Checkbox, the chevrons in Select and Pagination, the close
button in Dialog and Sheet — import it from
Lucide, and add installs lucide-react for exactly
those components. Nothing else requires it.
Where you supply the icon — an Input prefix or suffix, the children of a
menu item or button — you pass a React element, so any icon library works. Lucide is the
default only because the shipped components already use it.
An icon-only control still needs an accessible name, and the icon inside it is decoration, hidden from assistive technology:
import { Plus } from "lucide-react";
import { Button } from "@/components/ui/button";
export function AddRowButton() {
return (
<Button size="icon" aria-label="Add row">
<Plus aria-hidden="true" />
</Button>
);
}