Installation
Start with a Preact 11 project with Tailwind CSS v4 configured. Follow the Tailwind Vite guide if you need to add Tailwind.
1. Configure Preact compatibility
With Vite, use @preact/preset-vite. It aliases React imports to Preact for the upstream libraries. Keep the same aliases in your TypeScript configuration.
// vite.config.ts — keep your existing Tailwind plugin import preact from "@preact/preset-vite"; import { defineConfig } from "vite"; export default defineConfig({ plugins: [preact()], });// tsconfig.json — merge into compilerOptions { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "preact", "paths": { "react": ["./node_modules/preact/compat/"], "react-dom": ["./node_modules/preact/compat/"], "react/jsx-runtime": ["./node_modules/preact/jsx-runtime/"], "react/jsx-dev-runtime": ["./node_modules/preact/jsx-runtime/"] } } }2. Add shared dependencies
bun add --exact class-variance-authority@0.7.1 cn@0.4.0 lucide-preact@1.51.0 @floating-ui/react-dom@2.1.9 tw-animate-css@1.4.03. Choose your components
Choose a component from the catalog and follow its installation guide to copy that component and its local dependencies directly from the repository with degit.
Copy the entire catalog instead
bunx degit https://github.com/LiasCode/shadcn-preact/registry/ui#main ./src/components/uiThis copies the full catalog. Keep lib/ and primitives/ alongside the wrappers: their imports are relative. You can remove components you do not use, retaining the files they import.
4. Add the styles
Copy shadcn-tailwind.css into src/styles/. Add these imports at the top of your global stylesheet.
/* src/index.css */ @import "tailwindcss"; @import "tw-animate-css"; @import "./styles/shadcn-tailwind.css"; @custom-variant dark (&:is(.dark *));Add the theme tokens below after the imports. These match this port's nova styles. Import your global stylesheet from the app entry point.
Show theme CSS
@theme inline { --font-heading: var(--font-sans); --font-sans: "Geist Variable", sans-serif; --color-sidebar-ring: var(--sidebar-ring); --color-sidebar-border: var(--sidebar-border); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); --color-sidebar-accent: var(--sidebar-accent); --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); --color-sidebar-primary: var(--sidebar-primary); --color-sidebar-foreground: var(--sidebar-foreground); --color-sidebar: var(--sidebar); --color-chart-5: var(--chart-5); --color-chart-4: var(--chart-4); --color-chart-3: var(--chart-3); --color-chart-2: var(--chart-2); --color-chart-1: var(--chart-1); --color-ring: var(--ring); --color-input: var(--input); --color-border: var(--border); --color-destructive: var(--destructive); --color-accent-foreground: var(--accent-foreground); --color-accent: var(--accent); --color-muted-foreground: var(--muted-foreground); --color-muted: var(--muted); --color-secondary-foreground: var(--secondary-foreground); --color-secondary: var(--secondary); --color-primary-foreground: var(--primary-foreground); --color-primary: var(--primary); --color-popover-foreground: var(--popover-foreground); --color-popover: var(--popover); --color-card-foreground: var(--card-foreground); --color-card: var(--card); --color-foreground: var(--foreground); --color-background: var(--background); --radius-sm: calc(var(--radius) * 0.6); --radius-md: calc(var(--radius) * 0.8); --radius-lg: var(--radius); --radius-xl: calc(var(--radius) * 1.4); --radius-2xl: calc(var(--radius) * 1.8); --radius-3xl: calc(var(--radius) * 2.2); --radius-4xl: calc(var(--radius) * 2.6); } :root { --background: oklch(1 0 0); --foreground: oklch(0.145 0 0); --card: oklch(1 0 0); --card-foreground: oklch(0.145 0 0); --popover: oklch(1 0 0); --popover-foreground: oklch(0.145 0 0); --primary: oklch(0.205 0 0); --primary-foreground: oklch(0.985 0 0); --secondary: oklch(0.97 0 0); --secondary-foreground: oklch(0.205 0 0); --muted: oklch(0.97 0 0); --muted-foreground: oklch(0.556 0 0); --accent: oklch(0.97 0 0); --accent-foreground: oklch(0.205 0 0); --destructive: oklch(0.577 0.245 27.325); --border: oklch(0.922 0 0); --input: oklch(0.922 0 0); --ring: oklch(0.708 0 0); --chart-1: oklch(0.87 0 0); --chart-2: oklch(0.556 0 0); --chart-3: oklch(0.439 0 0); --chart-4: oklch(0.371 0 0); --chart-5: oklch(0.269 0 0); --radius: 0.625rem; --sidebar: oklch(0.985 0 0); --sidebar-foreground: oklch(0.145 0 0); --sidebar-primary: oklch(0.205 0 0); --sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-accent: oklch(0.97 0 0); --sidebar-accent-foreground: oklch(0.205 0 0); --sidebar-border: oklch(0.922 0 0); --sidebar-ring: oklch(0.708 0 0); } .dark { --background: oklch(0.145 0 0); --foreground: oklch(0.985 0 0); --card: oklch(0.205 0 0); --card-foreground: oklch(0.985 0 0); --popover: oklch(0.205 0 0); --popover-foreground: oklch(0.985 0 0); --primary: oklch(0.922 0 0); --primary-foreground: oklch(0.205 0 0); --secondary: oklch(0.269 0 0); --secondary-foreground: oklch(0.985 0 0); --muted: oklch(0.269 0 0); --muted-foreground: oklch(0.708 0 0); --accent: oklch(0.269 0 0); --accent-foreground: oklch(0.985 0 0); --destructive: oklch(0.704 0.191 22.216); --border: oklch(1 0 0 / 10%); --input: oklch(1 0 0 / 15%); --ring: oklch(0.556 0 0); --chart-1: oklch(0.87 0 0); --chart-2: oklch(0.556 0 0); --chart-3: oklch(0.439 0 0); --chart-4: oklch(0.371 0 0); --chart-5: oklch(0.269 0 0); --sidebar: oklch(0.205 0 0); --sidebar-foreground: oklch(0.985 0 0); --sidebar-primary: oklch(0.488 0.243 264.376); --sidebar-primary-foreground: oklch(0.985 0 0); --sidebar-accent: oklch(0.269 0 0); --sidebar-accent-foreground: oklch(0.985 0 0); --sidebar-border: oklch(1 0 0 / 10%); --sidebar-ring: oklch(0.556 0 0); } @layer base { * { @apply border-border outline-ring/50; } body { @apply bg-background text-foreground; } }
Usage
Import a copied component and use it in your app.
import { Button } from "./components/ui/button";
export function App() {
return <Button>Continue</Button>;
}For dark mode, wrap your app in ThemeProvider from ./components/ui/theme. Use useTheme to switch between light, dark, and system. The provider applies the dark class to the document element.
Additional dependencies
Install these only when you use the corresponding component.
| Component | Package |
|---|---|
| Calendar | react-day-picker, date-fns (date examples) |
| Chart | recharts |
| Command | cmdk |
| Carousel | embla-carousel-react, embla-carousel-autoplay (optional) |
| Input OTP | input-otp |
| Resizable | react-resizable-panels 4.x |
| Sonner | sonner |
Combobox, Toast, Message Scroller, and Questionnaire use local Preact primitives.
Upstream documentation
Use the official Base UI component documentation for composition and usage patterns. Adapt imports to your local Preact components. The official CLI installs upstream components; use the copy command above for this port.
For customization, see Theming. The older release is maintained on the v3 branch.