shadcn-ui Setup & Components
Whity Core uses shadcn-ui with a custom preset for the web dashboard, providing a collection of accessible, unstyled component primitives built on Radix UI.
What is shadcn-ui?
Section titled “What is shadcn-ui?”shadcn-ui is not a component library you install — it’s a collection of copy-paste components built on:
- Radix UI — Unstyled, accessible primitives
- Tailwind CSS — Utility-first styling
- React Hook Form — Form state management
Components are copied into your project source code, giving you full control over styling and behavior.
Project Setup
Section titled “Project Setup”Our web application is set up with shadcn-ui at /web with a custom preset:
# The preset includes:# - RTL (Right-to-Left) language support# - Pointer event enhancements# - Custom theme tokensnpx shadcn@latest init --preset b1D0eTWj --template next --rtl --pointerWhat Was Configured
Section titled “What Was Configured”components.json— shadcn config with import pathslib/utils.ts— Tailwind merge utilitiesapp/globals.css— Design tokens and theme system- Base components installed: Button, UI utilities
Adding Components
Section titled “Adding Components”Install from Registry
Section titled “Install from Registry”Add any component from the shadcn registry:
cd web
# Add a single componentnpx shadcn-ui@latest add button
# Add multiple componentsnpx shadcn-ui@latest add input textarea select
# Add with aliasesnpx shadcn-ui@latest add dialog --alias "use-dialog"Available Components
Section titled “Available Components”Popular components ready to install:
Forms & Input
input— Text, email, password inputstextarea— Multi-line textcheckbox— Checkbox inputsradio-group— Radio buttonsselect— Dropdown selectswitch— Toggle switchesslider— Range sliderform— Complete form system with validation
Display
button— Primary, secondary, outline variantsbadge— Label badgescard— Card containerstable— Data tablesavatar— User avatarsimage— Optimized images
Navigation
sidebar— Collapsible navigationbreadcrumb— Breadcrumb navigationnavigation-menu— Dropdown menustabs— Tab navigationpagination— Page navigation
Feedback
alert— Alert messagesalert-dialog— Confirmation dialogsdialog— Modal dialogstoast— Notification toastsprogress— Progress barsskeleton— Loading skeletons
Disclosure
accordion— Collapsible sectionspopover— Floating popoversdropdown-menu— Context menussheet— Side panelstooltip— Hover tooltips
See the full registry: https://ui.shadcn.com/docs/components/
Using Components
Section titled “Using Components”Import and Use
Section titled “Import and Use”Components are imported from @/components/ui:
import { Button } from "@/components/ui/button";import { Input } from "@/components/ui/input";import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export default function Dashboard() { return ( <Card> <CardHeader> <CardTitle>Welcome</CardTitle> </CardHeader> <CardContent> <Input placeholder="Enter your name" /> <Button>Submit</Button> </CardContent> </Card> );}Component Props
Section titled “Component Props”Each component accepts standard HTML props:
// Button variants<Button>Default</Button><Button variant="secondary">Secondary</Button><Button variant="outline">Outline</Button><Button variant="ghost">Ghost</Button><Button variant="destructive">Delete</Button><Button disabled>Disabled</Button>
// Sizes<Button size="sm">Small</Button><Button size="default">Default</Button><Button size="lg">Large</Button>
// States<Button onClick={handleClick}>Click me</Button><Button loading>Loading...</Button><Button aria-label="Close">×</Button>Customizing Components
Section titled “Customizing Components”Modifying Style
Section titled “Modifying Style”Components use Tailwind CSS classes. Edit them in /web/components/ui/:
const buttonVariants = cva( "inline-flex items-center justify-center rounded-md text-sm font-medium", { variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", // ... more variants }, }, });Extending Components
Section titled “Extending Components”Create wrapper components for project-specific behavior:
import { Button } from "@/components/ui/button";
export function DashboardButton(props) { return ( <Button size="lg" className="w-full" {...props} /> );}Theme Integration
Section titled “Theme Integration”All components automatically use the design tokens from globals.css:
/* In globals.css */:root { --primary: oklch(0.205 0 0); --primary-foreground: oklch(0.985 0 0); --background: oklch(1 0 0); /* ... more tokens ... */}
/* Components use these via Tailwind */<Button className="bg-primary text-primary-foreground">Change theme tokens and all components update automatically.
Best Practices
Section titled “Best Practices”1. Copy, Don’t Import
Section titled “1. Copy, Don’t Import”shadcn components are copied into your project. Feel free to modify them:
# This copies the component to your projectnpx shadcn-ui@latest add button
# Edit it freely — it's now your code# components/ui/button.tsx2. Use Semantic Variants
Section titled “2. Use Semantic Variants”Prefer semantic names over generic colors:
// ✅ Good<Button variant="destructive">Delete</Button>
// ❌ Avoid<Button className="bg-red-500">Delete</Button>3. Compose Components
Section titled “3. Compose Components”Build complex UIs from simple components:
<Card> <CardHeader> <CardTitle>Settings</CardTitle> </CardHeader> <CardContent> <form> <div className="space-y-4"> <Input placeholder="Email" /> <Select> <SelectItem value="admin">Admin</SelectItem> </Select> <Button>Save</Button> </div> </form> </CardContent></Card>4. Accessible by Default
Section titled “4. Accessible by Default”All components follow WAI-ARIA standards. Use semantic HTML:
// Components handle accessibility<Button aria-label="Close menu">×</Button><Dialog open={open} onOpenChange={setOpen}>5. Dark Mode
Section titled “5. Dark Mode”Components automatically support dark mode via .dark class:
// In layout.tsxexport default function RootLayout({ children }) { const [isDark, setIsDark] = useState(false);
return ( <html className={isDark ? "dark" : ""}> <body>{children}</body> </html> );}Preset Details
Section titled “Preset Details”Our custom preset (b1D0eTWj) includes:
- ✅ RTL support for international applications
- ✅ Pointer event enhancements for better mobile UX
- ✅ Optimized component defaults
- ✅ Integrated theme token system
To regenerate the preset or use a different one:
npx shadcn-ui@latest init --preset <preset-id>Browse presets: https://ui.shadcn.com/create
Common Patterns
Section titled “Common Patterns”Form with Validation
Section titled “Form with Validation”import { useForm } from "react-hook-form";import { Button } from "@/components/ui/button";import { Input } from "@/components/ui/input";
export function LoginForm() { const { register, handleSubmit } = useForm();
return ( <form onSubmit={handleSubmit(onSubmit)}> <Input {...register("email")} type="email" /> <Input {...register("password")} type="password" /> <Button type="submit">Login</Button> </form> );}Modal Dialog
Section titled “Modal Dialog”import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";import { Button } from "@/components/ui/button";
export function ConfirmDialog({ open, onConfirm, onCancel }) { return ( <Dialog open={open} onOpenChange={onCancel}> <DialogContent> <DialogHeader> <DialogTitle>Confirm Action</DialogTitle> </DialogHeader> <div className="flex gap-2 justify-end"> <Button variant="outline" onClick={onCancel}>Cancel</Button> <Button onClick={onConfirm}>Confirm</Button> </div> </DialogContent> </Dialog> );}Resources
Section titled “Resources”- Component Registry: https://ui.shadcn.com/docs/components/
- Preset Creator: https://ui.shadcn.com/create
- Installation Guide: https://ui.shadcn.com/docs/installation/next
- Accessibility: https://www.radix-ui.com/docs/primitives/overview/accessibility
- Tailwind CSS: https://tailwindcss.com/docs
Sharing Components via the Whity Registry (WC-168)
Section titled “Sharing Components via the Whity Registry (WC-168)”web/registry.json declares every components/ui/* and components/admin/*
component as a shadcn registry item (with its npm and intra-registry
dependencies). The registry build output is generated, never committed:
-
npm run registry:build(also runs automatically asprebuildbeforenext build) writes the distributable item JSONs toweb/public/r/, so any built deployment serves its registry athttps://<host>/r/{name}.json. -
A consuming app adds to its own
components.json:"registries": { "@whity": "https://<whity-host>/r/{name}.json" }and pulls components by copy-in:
npx shadcn add @whity/data-table. The CLI resolves intra-registry dependencies (e.g.data-tablebrings@whity/skeleton) and installs the npm packages the item declares.
This is deliberate copy-in distribution — no published npm package — per the Option C decision (#168): no publish/version burden until a real second consumer demands it.
Notes:
- The
@whitymapping committed in whity’s owncomponents.jsonpoints athttp://localhost:3000as a local-dev default: pulling from it requiresnpm run registry:buildfirst AND the dev server running (/public/r/is gitignored;prebuildonly fires onnext build). Deployed hosts serve it out of the box. - Always consume via the
@whitynamespace mapping, not raw item URLs — the items’registryDependencies(e.g.@whity/skeleton) only resolve through the configured namespace.