shadcn.io is an independent project built on shadcn/ui
Vite is the setup for a single-page app with no server: React, TypeScript and Tailwind CSS v4 through its plugin. The only part that takes care is the @/ alias, which Vite and TypeScript each need to know about.
Create a new app
Run init with the Vite template
The CLI asks for a project name and creates the app from shadcn's Vite starter (Vite 8, React 19, TypeScript, Tailwind CSS v4 and the @/ alias already wired), then applies our preset to src/index.css. Add --monorepo for a workspace with the app in apps/web and the components in packages/ui.
Add a component
The starter's App.tsx already renders a Button: answer yes when the CLI asks to overwrite button.tsx, and it renders ours.
Use it
import { Button } from "@/components/ui/button";
export function App() {
return (
<div className="flex min-h-svh flex-col items-center justify-center">
<Button>Click me</Button>
</div>
);
}
export default App;Add it to an existing app
Create the app, if you need one
bun create vite@latest my-app -- --template react-tsOr run npm create vite@latest and pick React, then TypeScript.
Add Tailwind CSS
Skip this if Tailwind CSS v4 already builds your styles.
bun add tailwindcss @tailwindcss/viteReplace everything in src/index.css with:
@import "tailwindcss";Add the alias to TypeScript
Vite's React template splits its TypeScript config in three files. Add paths to compilerOptions in tsconfig.json, which the CLI reads:
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
],
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}And the same paths in tsconfig.app.json, which your editor and tsc -b use for src/:
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}Leave out baseUrl: paths resolve from the file they're in, and TypeScript 6 deprecates baseUrl.
Add the alias and Tailwind to Vite
bun add -D @types/nodeimport path from "node:path";
import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});On Vite 8, resolve: { tsconfigPaths: true } reads the alias from tsconfig.json instead, without path or @types/node.
Run init
It adds the theme to src/index.css, writes src/lib/utils.ts and components.json, and installs the packages.
Add a component and use it
Import it in src/App.tsx as above.
Dark mode
The dark theme applies under a .dark class on <html>. A single-page app toggles it itself: read the saved choice before React renders, in index.html, so the page never flashes light first.
<script>
const theme = localStorage.getItem("theme");
const dark =
theme === "dark" ||
(!theme && matchMedia("(prefers-color-scheme: dark)").matches);
document.documentElement.classList.toggle("dark", dark);
</script>Then a toggle sets the class and saves the choice:
import { MoonIcon, SunIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
export function ModeToggle() {
const toggle = () => {
const dark = document.documentElement.classList.toggle("dark");
localStorage.setItem("theme", dark ? "dark" : "light");
};
return (
<Button aria-label="Toggle dark mode" onClick={toggle} size="icon" variant="outline">
<SunIcon className="dark:hidden" />
<MoonIcon className="hidden dark:block" />
</Button>
);
}FAQ
Vite can't resolve '@/components/ui/button'. Why?
TypeScript knows the alias but Vite doesn't. Add resolve.alias (or resolve.tsconfigPaths: true on Vite 8) to vite.config.ts and restart the dev server. An editor that still complains needs the same paths in tsconfig.app.json.
Why add paths to two tsconfig files?
The CLI reads tsconfig.json to find where components go. tsconfig.app.json is the project that checks src/, so your editor and tsc -b resolve @/ from it. Without the second, the app builds with Vite but your editor shows errors.
Do I need baseUrl in tsconfig?
No. Since TypeScript 4.1, paths resolve relative to the config file, and TypeScript 6 deprecates baseUrl. shadcn's own Vite starter has no baseUrl.
Can I use Vite's JavaScript template?
The components are TypeScript files (.tsx), so start from react-ts. A JavaScript app can still use them if it runs TypeScript through Vite, which it does by default, but you'll want tsconfig.json for the alias anyway.
Does it work with @vitejs/plugin-react-swc?
Yes. The components need React and Tailwind CSS, not a particular React plugin. Keep tailwindcss() in plugins beside whichever one you use.
I want file-based routing on Vite. What should I use?
TanStack Router adds file routes to a Vite single-page app, and React Router in framework mode adds loaders and server rendering. Both use the same components.