Skip to content

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

Astro

Set up shadcn.io in Astro with the React integration: create a site with our preset or run init in yours, then hydrate components as islands.

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

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 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

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 home page already renders a Button: answer yes when the CLI asks to overwrite button.tsx.

Use it in a page

src/pages/index.astro
---
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 --git

This 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:

tsconfig.json
{
  "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

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 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

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 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:

DirectiveHydratesUse it for
client:loadAs soon as the page loadsControls above the fold: a menu, a search box
client:visibleWhen it scrolls into viewAnything further down the page
client:idleOnce the browser is idleThings that can wait a moment
client:only="react"In the browser only, never on the serverComponents 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:

src/components/feedback-dialog.tsx
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>
  );
}
src/pages/index.astro
---
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.

Does it work with Astro's server rendering?

Yes. With an adapter and output: "server", components render on each request instead of at build time; directives work the same way.

Can I use the components in Markdown or MDX pages?

In .mdx, yes: import them at the top and render them, with a client: directive for interactive ones. Plain .md files can't render components.