Using shadcn/ui
Vui Starter is built to pair with shadcn/ui. Lean on shadcn's large, accessible component set for everyday UI (forms, dialogs, tabs, and the rest), and keep Vui's RecordView for datatables. Because they share the same tokens, shadcn components adopt the Vui look with nothing extra to configure.
Why this works
shadcn/ui components are styled entirely with CSS-variable tokens (--background, --primary, --border, …). Vui's theme.css defines exactly those tokens, so the moment you import the Vui theme, every shadcn component renders in the Vui style with no restyling and no overrides.
Division of labor
RecordViewon the datatable, with its editable cells, import/export, and add/edit form. That's the one piece shadcn doesn't provide.See it live
/components and a fully validated shadcn Form (every field type, backed by Zod) at /forms, all themed by theme.css with zero overrides.Setup
1. Import the Vui theme
This provides every token shadcn expects, along with Vui's baseline.
@import "tailwindcss";
@import "@viliha/vui-ui/theme.css";2. Initialize shadcn/ui
npx shadcn@latest initMatch these choices so the setup lines up with Vui:
A matching components.json:
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "app/globals.css",
"baseColor": "neutral",
"cssVariables": true
},
"iconLibrary": "lucide",
"aliases": { "components": "@/components", "utils": "@/lib/utils" }
}3. Let Vui own the tokens
shadcn init writes a :root / .darktoken block into your CSS. Since Vui's theme.css already defines those, delete the block shadcn added, or keep the @import after it so Vui wins. One source of truth for tokens means one consistent look.
4. Add components
npx shadcn@latest add button input label form dialog dropdown-menu tabsThey render in the Vui style right away, with the same radius, colors, borders, and focus rings.
Forms
shadcn's form (React Hook Form with Zod) is the recommended way to build forms on top of Vui:
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Form, FormField, FormItem, FormLabel, FormControl } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
const schema = z.object({ email: z.string().email() });
export function SignupForm() {
const form = useForm({ resolver: zodResolver(schema) });
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(console.log)} className="space-y-4">
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl><Input placeholder="you@example.com" {...field} /></FormControl>
</FormItem>
)}
/>
<Button type="submit">Save</Button>
</form>
</Form>
);
}