shadcn.io is an independent project built on shadcn/ui
In Astro, the components are React islands: they render to HTML at build time, and the ones people interact with hydrate in the browser when you give them a client: directive. Astro needs its React integration and Tailwind CSS v4.
Create a new site
Run init with the Astro template
The CLI asks for a project name and creates the site from shadcn's Astro starter (Astro 7, the React integration, React 19, Tailwind CSS v4 through @tailwindcss/vite, the @/ alias), then adds our theme to src/styles/global.css, which src/layouts/main.astro imports. Add --monorepo for a workspace with the site in apps/web and the components in packages/ui.
Add a component
The starter's home page already renders a Button: answer yes when the CLI asks to overwrite button.tsx.
Use it in a page
---
import Layout from "@/layouts/main.astro";
import { Button } from "@/components/ui/button";
---
<Layout>
<div class="grid min-h-svh place-items-center">
<Button client:load>Get started</Button>
</div>
</Layout>Add it to an existing site
Create the site, if you need one
bun create astro@latest astro-app -- --template with-tailwindcss --install --add react --gitThis starts from Astro's Tailwind CSS template and adds the React integration. On a site you have, run npx astro add react and npx astro add tailwind for whichever is missing.
Add the alias
Astro's tsconfig.json extends one of its presets. Add paths to it:
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"],
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react",
"paths": {
"@/*": ["./src/*"]
}
}
}Astro reads paths for its own build, so there's nothing to add to astro.config.mjs.
Run init
It adds the theme to the stylesheet that imports Tailwind CSS (src/styles/global.css in Astro's template). Make sure every page loads that stylesheet, usually through a shared layout.
Add a component and use it
Import it in the frontmatter of any .astro page and render it.
Islands: what to hydrate
A component without a directive is rendered to HTML and ships no JavaScript. That's right for anything that only displays: a Card, a Badge, a Table, a Separator. Anything with state or events needs a directive:
| Directive | Hydrates | Use it for |
|---|---|---|
client:load | As soon as the page loads | Controls above the fold: a menu, a search box |
client:visible | When it scrolls into view | Anything further down the page |
client:idle | Once the browser is idle | Things that can wait a moment |
client:only="react" | In the browser only, never on the server | Components that read window while rendering |
A component made of parts, like a Dialog, a Dropdown Menu or Tabs, keeps its state in React context that the parts share. Astro hydrates each component you put in a .astro file as its own island, so the parts must live in one React component. Write it in a .tsx file and hydrate that:
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
export function FeedbackDialog() {
return (
<Dialog>
<DialogTrigger render={<Button variant="outline" />}>
Send feedback
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Send feedback</DialogTitle>
<DialogDescription>Tell us what to improve.</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
);
}---
import { FeedbackDialog } from "@/components/feedback-dialog";
---
<FeedbackDialog client:load />FAQ
My Button renders but onClick does nothing. Why?
It has no client: directive, so Astro sent it as plain HTML. Add client:load (or client:visible), or move the button into a React component you hydrate. Event handlers can't be passed from .astro frontmatter anyway: put the handler in a .tsx file.
Why doesn't my Dialog open when I write its parts in an .astro file?
Each component in an .astro file is its own island, and the parts of a Dialog share state through React context, which doesn't cross islands. Write the whole Dialog in one .tsx component and hydrate that one, as the islands section shows.
The components have no styles. What's missing?
The page doesn't load the stylesheet with Tailwind CSS and the theme. Import src/styles/global.css in the layout every page uses, and check that it starts with @import "tailwindcss";.
Do I write class or className on the components?
className: they're React components, even inside an .astro file. Plain HTML elements in .astro files keep class.
Can I use the Vue or Svelte components in Astro instead?
Astro supports those frameworks too, but this guide installs the React components with the shadcn CLI. The ports install with their own CLIs; see Installation.
How do I add dark mode in Astro?
Toggle a .dark class on <html>. Set it in an inline script in your layout's <head>, from localStorage or the system setting, so the page never flashes light before it switches.