Skip to content

shadcn.io is an independent project built on shadcn/ui

TanStack Start

Set up shadcn.io in TanStack Start: create an app with our preset, or run init in yours, then use the components in file routes and server functions.

TanStack Start is full-stack React on TanStack Router: file routes, server functions and server rendering, built on Vite. Its starter has Tailwind CSS v4 and the @/ alias, so the components drop in with init.

Create a new app

Run init with the Start template

shadcn CLILocked
Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.

The CLI asks for a project name and creates the app from shadcn's TanStack Start starter (React 19, Vite 8, Tailwind CSS v4, the @/ alias), then adds our theme to src/styles.css. Add --monorepo for a workspace with the app in apps/web and the components in packages/ui.

Add a component

shadcn CLILocked
Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.

The starter's index route already renders a Button: answer yes when the CLI asks to overwrite button.tsx.

Use it in a route

src/routes/index.tsx
import { createFileRoute } from "@tanstack/react-router";
import { Button } from "@/components/ui/button";

export const Route = createFileRoute("/")({
  component: App,
});

function App() {
  return (
    <div className="flex min-h-svh items-center justify-center p-6">
      <Button>Click me</Button>
    </div>
  );
}

Add it to an existing app

Create the app, if you need one

bunx --bun @tanstack/cli@latest create

Choose TanStack Start with React and keep Tailwind CSS on. Don't pick the shadcn add-on: it sets up shadcn/ui's own registry, and init below sets up ours.

Check Tailwind CSS and the alias

The TanStack CLI configures both: Tailwind CSS v4 through @tailwindcss/vite, imported in src/styles.css, and "@/*": ["./src/*"] in tsconfig.json, which Vite resolves through resolve.tsconfigPaths or the vite-tsconfig-paths plugin. An older app needs both before you go on.

Run init

shadcn CLILocked
Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.

It detects TanStack Start from @tanstack/react-start, adds the theme to your stylesheet, and writes components.json and src/lib/utils.ts.

Add a component and use it

shadcn CLILocked
Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.

Import it from @/components/ui/card in any route.

Data from a server function

Components render on the server and hydrate in the browser, so a loader's data goes straight into them. A server function reads the data, the route's loader calls it:

src/routes/projects.tsx
import { createFileRoute } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
import {
  Card,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";

const getProjects = createServerFn({ method: "GET" }).handler(async () => [
  { id: "1", name: "Website", owner: "Ada" },
  { id: "2", name: "Mobile app", owner: "Grace" },
]);

export const Route = createFileRoute("/projects")({
  loader: () => getProjects(),
  component: Projects,
});

function Projects() {
  const projects = Route.useLoaderData();
  return (
    <div className="grid gap-4 p-6 sm:grid-cols-2">
      {projects.map((project) => (
        <Card key={project.id}>
          <CardHeader>
            <CardTitle>{project.name}</CardTitle>
            <CardDescription>Owned by {project.owner}</CardDescription>
          </CardHeader>
        </Card>
      ))}
    </div>
  );
}

FAQ

Should I pick the shadcn add-on when creating the app?

No. The add-on configures shadcn/ui's registry and theme. Leave it off and run our init, which writes our theme and components.json. If you already picked it, run our init anyway: it asks before overwriting components.json, and you can keep the components you have.

I don't need a server. Which guide do I follow?

TanStack Router: the same file routes in a single-page app on Vite, with no server functions.

Where does the theme go?

Into src/styles.css, which the root route links in its head (import appCss from "../styles.css?url"). init finds it and records it in components.json.

Do the components work with server rendering?

Yes. They render on the server and hydrate in the browser; the ones that need the browser (overlays, menus, anything with state) do their work after hydration. Nothing needs to be marked client-only.

How do I add dark mode?

The dark theme applies under a .dark class on <html>. Set it before the page paints, from a cookie read on the server or from a small inline script in the root route's head, so the first paint is already dark.

Imports of @/components/ui fail at runtime. What's missing?

Vite doesn't know the alias. Add resolve: { tsconfigPaths: true } to vite.config.ts (Vite 8) or the vite-tsconfig-paths plugin, so Vite reads "@/*": ["./src/*"] from tsconfig.json.

How do I post a form to a server function?

Build the form with Field and your form library, and call the server function from the submit handler with the values. The Form guide covers React Hook Form and TanStack Form, which pairs naturally with Start.

Can I keep the components in a shared package?

Yes: init -t start --monorepo creates a workspace with the app in apps/web and the components in packages/ui. Add components from apps/web, or with -c apps/web from the root.