How To Build a Shadcn Astro Landing Page in Easy Steps

Written By Ajay Patel 21 mins

How To Build a Shadcn Astro Landing Page in Easy Steps

This guide will help you easily build a Shadcn Astro Landing Page. If you’ve been following modern web development trends, you’ve probably heard of shadcn/ui. Unlike traditional component libraries, shadcn/ui takes a different approach; instead of installing a bloated npm package, you copy beautifully designed, accessible components directly into your project. You own the code entirely, with no black-box dependencies to deal with.

Astro, on the other hand, is a web framework that’s laser-focused on performance. It ships zero JavaScript by default, rendering everything as static HTML. But here’s the clever part: it also supports “islands” of interactivity where you can drop in React, Vue, or Svelte components exactly where you need them.

Put these two together, and you get something powerful: Astro’s lightning-fast static rendering combined with shadcn/ui’s gorgeous, accessible React components. It’s the best of both worlds: performance when you need it, interactivity where it matters.

In this tutorial, we’ll walk you through the complete process of integrating shadcn/ui with Astro, using the latest official approach. We’ll cover not just the happy path, but also the real gotchas you’ll encounter, things like hydration quirks, path alias configuration, and why shadcn/ui blocks need special treatment in Astro.

What Makes This Combination Special?

Before we dive into the setup, let’s talk about why this combination makes sense.

Astro‘s architecture is built around a simple idea: render HTML at build time, and only ship JavaScript for the parts that truly need it. This is fundamentally different from frameworks like Next.js or Create React App, where everything is hydrated by default.

shadcn/ui provides beautifully designed React components built on top of Radix UI (for accessibility and behavior) and Tailwind CSS (for styling). These components are production-ready, accessible, and customizable.

Together, they create a workflow where:

  • Your layouts and content stay fast with Astro’s static rendering
  • Interactive UI elements get exactly the JavaScript they need
  • You maintain full control over your component code
  • Everything looks polished from day one

This separation of concerns is exactly what makes the combination powerful.

Prerequisites

Before we begin, make sure you have:

  • Node.js 18 or higherDownload here
  • pnpm – I’ll use pnpm in examples, but you can use npm or yarn if you prefer
  • Basic understanding of React – You should be comfortable with JSX and props
  • Familiarity with Tailwind CSS – Understanding utility classes helps
  • A code editor – VS Code is recommended for the best experience

You don’t need to be an expert in any of these, but having worked with them before will make the tutorial smoother.

Create Your Astro Project

The fastest way to get started is to use Astro’s official Tailwind template with React preconfigured. This saves you from having to manually wire up integrations later.

Open your terminal and run:

pnpm create astro@latest astro-shadcn-app --template with-tailwindcss --install --add react --git

Let me break down what each flag does:

  • -template with-tailwindcss – Uses the official Tailwind CSS starter template
  • -install – Automatically installs dependencies without prompting
  • -add react – Adds the @astrojs/react integration for React support
  • -git – Initializes a Git repository
shadcn astro landing page

Once installation completes, navigate into your project:

cd astro-shadcn-app

Note: This approach avoids one of the most common issues people encounter: a missing Tailwind configuration during shadcn initialization. Check the guide for reference.

Let’s verify everything is set up correctly. Open astro.config.mjs — it should look something like this:

import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import tailwindcss from '@tailwindcss/vite';

export default defineConfig({
  vite: {
    plugins: [tailwindcss()],
  },
  integrations: [react()],
});

Both React and Tailwind are configured and ready to go. If you created the project a different way and don’t see these integrations, you can add them manually with npx astro add react and npx astro add tailwind.

Configure TypeScript Path Aliases

This is a critical step that many people skip, leading to import errors later. shadcn/ui components use the @/ import alias everywhere:

import { Button } from "@/components/ui/button"

Without proper configuration, TypeScript won’t know how to resolve @/, and your imports will fail.

Open your tsconfig.json and add the path alias configuration inside compilerOptions:

{
  "extends": "astro/tsconfigs/strict",
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "react",
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

This tells TypeScript that any path starting with @/ should resolve to the src/ directory. Make sure this is configured before running shadcn init in the next step; the CLI validates path aliases as part of its preflight checks.

Initialize shadcn/ui

Here’s where things get interesting, and where a lot of developers hit their first roadblock.

The old package name shadcn-ui is deprecated and no longer maintained. If you try to use it, you’ll see a deprecation warning. The correct package to use now is simply shadcn.

Run this command to initialize shadcn/ui in your project:

pnpm dlx shadcn@latest init

Or if you prefer npm:

npx shadcn@latest init

The CLI will prompt you with several questions:

  • Which style would you like to use? – Choose “Default” or “New York” (both look great)
  • Which color would you like to use as the base color? – Choose from several options like Slate, Gray, Zinc, etc.
  • Would you like to use CSS variables for colors? – Choose “yes” (recommended for theming)

For your first project, the defaults work perfectly fine; just press Enter at each prompt. Check the CLI documentation for further info.

Once it finishes, shadcn/ui will have:

  • Created components.json – the config file that tells the CLI where everything lives
  • Generated src/lib/utils.ts with the cn() helper function
  • Updated your global CSS file with theme tokens and CSS variables
  • Installed core dependencies like clsx, tailwind-merge, and class-variance-authority

The cn() utility that gets generated is used throughout the shadcn/ui components. It looks like this:

import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

This function safely merges Tailwind class names, handling conflicts intelligently. You’ll see it used in every shadcn/ui component.

Add Your First Component

Let’s add the Button component to see the system in action:

pnpm dlx shadcn@latest add button

The CLI downloads the component and installs any required dependencies. When it finishes, you’ll find a new file at src/components/ui/button.tsx.

Here’s what makes shadcn/ui special: this is your code now. Open that file and look through it. You’ll see it’s a TypeScript React component using Tailwind CSS and class-variance-authority for variants. You can modify it however you want – change the styles, add new variants, adjust the API. There’s no abstraction layer hiding the implementation.

Want to add more components? Same pattern every time:

pnpm dlx shadcn@latest add card
pnpm dlx shadcn@latest add input
pnpm dlx shadcn@latest add dialog

Or install multiple at once:

pnpm dlx shadcn@latest add button card input dialog

Build a Practical Example

Let’s build something real to see how everything fits together. We’ll create a welcome card that showcases both the Button and Card components.

First, make sure you have both components installed:

pnpm dlx shadcn@latest add button card

Now, create a new React component at src/components/WelcomeCard.tsx:

import { Button } from '@/components/ui/button';
import {
  Card,
  CardContent,
  CardDescription,
  CardFooter,
  CardHeader,
  CardTitle,
} from '@/components/ui/card';

export default function WelcomeCard() {
  return (
    <Card className="w-full max-w-md">
      <CardHeader>
        <CardTitle>Welcome to Astro + shadcn/ui</CardTitle>
        <CardDescription>
          A powerful combination of performance and beautiful design.
        </CardDescription>
      </CardHeader>
      <CardContent>
        <p className="text-sm text-muted-foreground">
          You're looking at a React component with shadcn/ui styling,
          rendered inside an Astro page. The best of both worlds —
          Astro's performance with React's interactivity exactly where
          you need it.
        </p>
      </CardContent>
      <CardFooter className="flex gap-2">
        <Button>Get Started</Button>
        <Button variant="outline">Learn More</Button>
      </CardFooter>
    </Card>
  );
}

Now let’s use this component in an Astro page. Open src/pages/index.astro and update it:

---
import WelcomeCard from '../components/WelcomeCard';
---

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Astro + shadcn/ui</title>
  </head>
  <body>
    <main class="flex min-h-screen items-center justify-center p-8 bg-background">
      <WelcomeCard client:load />
    </main>
  </body>
</html>

Start the development server:

pnpm dev

Open http://localhost:4321 in your browser. You should see a beautifully styled card with two buttons. Everything works, looks polished, and is fully accessible.

Notice the client:load directive on the component? That’s crucial, and it’s worth understanding deeply.

Static vs Interactive Components

This is the most important concept when using shadcn/ui in Astro. Astro renders React components as static HTML by default with zero JavaScript. This makes pages fast, but interactive components won’t work without hydration.

Components that work as static HTML:

  • Badge, Card, Separator, Skeleton, Avatar, Table

Components that need JavaScript (use Radix UI):

  • Dialog, DropdownMenu, Accordion, Tabs, Switch, Select, Popover, Sheet, Tooltip

To make interactive components work, add a client:* directive:

---
import MyDialog from '../components/MyDialog';
---

<!-- ❌ Won't work - no JavaScript -->
<MyDialog />

<!-- ✅ Works - hydrated with JavaScript -->
<MyDialog client:load />

Hydration directives:

  • client:load — Load immediately (for critical UI)
  • client:idle — Load when the browser is idle
  • client:visible — Load when scrolled into view

Rule of thumb: If a component uses Radix UI, it needs a client:* directive.

Working with shadcn/ui Blocks

shadcn/ui provides pre-built Block sections. You can also find more shadcn blocks in shadcn/studio layouts, such as dashboards, authentication forms, and Hero Sections.

Shadcn Studio:

This isn’t a traditional component library or a replacement for Shadcn. Instead, it’s a unique collection that offers customizable variants of components, blocks, and templates. Preview, customize, and copy-paste them into your apps with ease.

Building on the solid foundation of the Shadcn components & blocks, we’ve enhanced it with custom-designed components & blocks to give you a head start. This allows you to craft, customize, and ship your projects faster and more efficiently.

Features:

  • Open-source: Dive into a growing, community-driven collection of copy-and-paste shadcn UI components, Shadcn blocks, and templates.
  • Component & Blocks variants: Access a diverse collection of customizable shadcn UI blocks and component variants to quickly build and style your UI with ease.
  • Animated variants with Motion: Add smooth, modern animations to your components, enhancing user experiences with minimal effort.
  • Landing pages & Dashboards: Explore 20+ premium & free Shadcn templates for dashboards, landing pages & more. Fully customizable & easy to use.
  • shadcn/ui for Figma: Speed up your workflow with Shadcn Figma UI Kit, which consists of components, blocks & templates – a full design library inspired by shadcn/ui.
  • Powerful theme generator: Customize your UI instantly with Shadcn Theme Generator. Preview changes in real time and create consistent, on-brand designs faster.
  • shadcn/studio MCP: Integrate shadcn MCP Server directly into your favorite IDE and craft stunning shadcn/ui Components, Blocks, and Pages inspired by shadcn/studio.
  • Shadcn Figma To Code Plugin: Convert your Figma designs into production-ready code instantly with the Shadcn Figma Plugin.

Installing Blocks in Astro

You can install blocks from both shadcn/ui and shadcn/studio using the CLI:

From shadcn/ui:

pnpm dlx shadcn@latest add dashboard-01

From shadcn/studio (using URL):

pnpm dlx shadcn@latest add "<https://shadcnstudio.com/r/blocks/hero-section-41.json>"

Important: Understanding What Gets Installed

Here’s where things get tricky. shadcn/ui blocks are primarily structured for Next.js, which uses an app/ directory for routing. Astro doesn’t use this routing structure, so you need to understand which components get installed and which don’t.

When you install a block, the CLI will:

Install all component files into src/components/ui/ or src/components/shadcn-studio/blocks/ — These work fine

Skip page files intended for Next.js routing (inside app/) — these are not installed into Astro

For example, if you install the Dashboard block:

pnpm dlx shadcn@latest add dashboard-01

You’ll get all the building blocks:

  • src/components/ui/sidebar.tsx
  • src/components/app-sidebar.tsx
  • src/components/nav-main.tsx
  • src/components/site-header.tsx
  • And other sub-components

But you won’t get the main page file:

  • app/dashboard/page.tsx ❌ (not created)

This page file actually assembles all those components into a working dashboard. Without it, you have all the pieces but no complete picture of how to use them together.

The Solution: Manual Adaptation

Since Astro doesn’t use Next.js’s app/ directory structure, you need to manually adapt the page files. Here’s the step-by-step process:

Step 1: Install the block via CLI:

pnpm dlx shadcn@latest add dashboard-01

All the component pieces are now in src/components/ and src/components/ui/.

Step 2: Visit the shadcn/ui blocks page, find your block, and click the code button (</>) to view the source.

Step 3: In the code viewer, look for the page file that assembles the components. It’ll be on a path like app/dashboard/page.tsx. This is the file that wasn’t installed because Astro doesn’t use the app/ directory structure. Copy its contents.

Step 4: Create a folder for assembled page sections in your Astro project:

mkdir -p src/components/sections

Step 5: Create a new file for this block and paste the code:

touch src/components/sections/dashboard.tsx

The code will look something like this:

import { AppSidebar } from "@/components/app-sidebar"
import { SiteHeader } from "@/components/site-header"
import {
  SidebarInset,
  SidebarProvider,
} from "@/components/ui/sidebar"

export default function DashboardPage() {
  return (
    <SidebarProvider>
      <AppSidebar />
      <SidebarInset>
        <SiteHeader />
        <div className="flex flex-1 flex-col gap-4 p-4">
          <h1 className="text-2xl font-bold">Dashboard</h1>
          {/* Your page content */}
        </div>
      </SidebarInset>
    </SidebarProvider>
  )
}

Step 6: Use it in any Astro page:

---
import DashboardPage from "@/components/sections/dashboard";
---

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>Dashboard</title>
  </head>
  <body>
    <DashboardPage client:load />
  </body>
</html>

Since dashboard sidebars are interactive (collapsible, keyboard navigable), client:load is the right choice here.

This pattern of keeping assembled layouts in src/components/sections/ and reusable components in src/components/ui/ scales nicely as your project grows.

Practical Example: Hero Section from shadcn/studio

Let’s walk through a real-world example using a block from shadcn/studio. We’ll install the Hero Section 41 — a beautiful, production-ready landing page hero.

Step 1: Install the hero section block from shadcn/studio:

pnpm dlx shadcn@latest add "<https://shadcnstudio.com/r/blocks/hero-section-41.json>"

This will install all the component files into src/components/shadcn-studio/blocks/hero-section-41/ along with any required UI components.

Step 2: The page file (app/page.tsx) is not installed automatically. You need to manually create it. Visit the Shadcn Hero Section blocks.

Find Hero Section 41 and copy the page implementation code to create your src/components/sections/hero.tsx file.

Step 3: Use the hero component in your Astro page with the client:load directive for interactivity.

See the complete implementation in the repository:

This example demonstrates how powerful shadcn/studio is — you get a production-ready, animated hero section in minutes, not hours. The block comes with all the components you need, properly structured and styled. All you have to do is install the block, copy the page assembly code from the shadcn/studio website, and adapt it for Astro by adding client:load directives.

Key takeaway: shadcn/studio dramatically accelerates your development workflow. Instead of building complex UI patterns from scratch, you can leverage battle-tested, professional designs that you fully own and can customize. Explore more marketing UI blocks for landing pages, pricing sections, testimonials, and more.

Building a Complete Shadcn Astro Landing Page

Now that you understand how to work with individual blocks, let’s talk about building a complete Shadcn Astro landing page. A professional landing page typically includes several key sections: hero, features, pricing, testimonials, FAQ, and call-to-action.

The Step-by-Step Approach

You can build your Shadcn Astro landing page by installing blocks one at a time from shadcn/studio:

Step 1: Install blocks one at a time:

pnpm dlx shadcn@latest add "<https://shadcnstudio.com/r/blocks/hero-section-41.json>"

Step 2: Copy the page assembly code from shadcn/studio and create section components in src/components/sections/.

Step 3: Assemble everything in an Astro page:

---
import Hero from '../components/sections/hero';
import Features from '../components/sections/features';
import Pricing from '../components/sections/pricing';
import Testimonials from '../components/sections/testimonials';
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>My Landing Page</title>
  </head>
  <body>
    <Hero client:load />
    <Features client:load />
    <Pricing client:load />
    <Testimonials client:load />
  </body>
</html>

This approach gives you maximum learning and flexibility — you understand exactly how each section works and can customize everything to your needs.

Need a Quick Start?

If you want to launch quickly without building from scratch, we’ve created a production-ready Shadcn Astro landing page template: Bistro Landing Page Template

The template includes:

  • ✨ Hero, About, Offers, Testimonials, and Contact sections
  • 🧭 Responsive navigation with mobile menu
  • 🌓 Light/Dark mode with theme toggle
  • ⚡ Fully optimized and production-ready

Browse more templates at Shadcn Templates.

Common Issues and How to Fix Them

Even with a clean setup, certain issues keep recurring. Here are the most common problems and their solutions.

Issue 1: Using the Deprecated Package

Symptom: You see this warning:

The 'shadcn-ui' package is deprecated. Please use the 'shadcn' package instead.

Solution: Always use shadcn (without -ui):

# ❌ Old and deprecated
npx shadcn-ui@latest init

# ✅ Correct
pnpm dlx shadcn@latest init
# or
npx shadcn@latest init

The old package is frozen at v0.9.5 and won’t receive updates.

Issue 2: Path Alias Not Resolving

Symptom: TypeScript errors like Cannot find module '@/components/ui/button' or Module not found.

Solution: Make sure tsconfig.json includes the path alias configuration before running shadcn init:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

After adding this, restart your dev server — Astro picks up tsconfig.json changes on restart, not hot reload.

Issue 3: Components Render Without Styles

Symptom: shadcn/ui components appear as unstyled HTML. Buttons look like browser defaults, spacing is wrong, and colors are missing.

Solution: Check two things:

First, make sure your global CSS is imported. In your layout or page, you should have:

---
import '../styles/global.css';
---

Second, verify that your Tailwind config scans .tsx files. Open tailwind.config.mjs (or whatever config file you have) and check the content array:

export default {
  content: ['./src/**/*.{astro,html,js,jsx,md,mdx,ts,tsx}'],
  // ...
}

If .tsx is missing, Tailwind won’t scan your React component files, and those classes get purged from the build.

Issue 4: Components Look Right, But Don’t Work

Symptom: A Dialog button doesn’t open the modal, a DropdownMenu doesn’t expand, a Switch doesn’t toggle.

Solution: The component needs hydration. Add a client:* directive:

<!-- ❌ Static HTML — no JavaScript -->
<MyDialog />

<!-- ✅ Hydrated — fully interactive -->
<MyDialog client:load />

Remember: any component using Radix UI primitives needs JavaScript to function.

Issue 5: Using className in .astro Files

Symptom: You write <div className="..."> in an .astro file, and it doesn’t work.

Solution: Use class instead of className in .astro files:

<!-- ❌ Wrong in .astro files -->
<div className="flex items-center">...</div>

<!-- ✅ Correct in .astro files -->
<div class="flex items-center">...</div>

className is a React convention. In .astro files, use standard HTML attribute names.

Issue 6: Dark Mode Not Working

Symptom: You toggle a .dark class, but the shadcn/ui components don’t respond.

Solution: Ensure your Tailwind config uses class-based dark mode:

export default {
  darkMode: ['class'],
  // ...
};

Check that your global CSS includes the dark mode variables (the CLI adds this automatically):

@layer base {
  :root {
    --background: 0 0% 100%;
    /* ...other light mode variables */
  }

  .dark {
    --background: 222.2 84% 4.9%;
    /* ...other dark mode variables */
  }
}

To toggle dark mode at runtime, create a component that manipulates the class:

function DarkModeToggle() {
  return (
    <button
      onClick={() => document.documentElement.classList.toggle('dark')}
      className="px-4 py-2 rounded-md border"
    >
      Toggle Dark Mode
    </button>
  );
}

Use this component with client:load in your Astro pages.

Issue 7: Block Page Files Missing

Symptom: You install a shadcn/ui block, but the main page that assembles everything is missing.

Solution: This is expected behavior. shadcn/ui blocks use Next.js’s app/ directory structure, which Astro doesn’t have.

Manually copy the page file from the block’s source code on ui.shadcn.com/blocks, paste it into src/components/sections/, and import it into your .astro page with client:load. See the Working with shadcn/ui Blocks section for complete step-by-step instructions.

Production Considerations

When you’re ready to deploy, keep these performance tips in mind:

Minimize JavaScript Hydration

Don’t hydrate entire pages. Keep layouts and content static in Astro, and only hydrate specific interactive components:

---
import Header from '../components/Header.astro';  // Static
import Footer from '../components/Footer.astro';  // Static
import ContactForm from '../components/ContactForm'; // Interactive
---

<Header />
<main>
  <h1>Contact Us</h1>
  <ContactForm client:load />
</main>
<Footer />

Use Lazy Hydration

For below-the-fold interactive components, use client:visible:

<!-- Only loads JavaScript when scrolled into view -->
<Newsletter client:visible />
<CommentsSection client:visible />

Prefer client:idle for Non-Critical UI

For interactive elements that aren’t immediately needed:

<ChatWidget client:idle />
<FeedbackButton client:idle />

Build and Test

Always build for production and test the output:

pnpm build
pnpm preview

Check your bundle sizes and ensure only necessary JavaScript ships to the browser.

Conclusion

Integrating shadcn/ui with Astro gives you a powerful setup: Astro’s performance-first architecture combined with shadcn/ui’s beautiful, accessible component library.

Key takeaways:

  • Always use shadcn@latest (not the deprecated shadcn-ui package)
  • Configure path aliases in tsconfig.json before running shadcn init
  • Interactive components (using Radix UI) need client:* directives
  • When using blocks, manually copy page files into src/components/sections/
  • Use hydration strategically: client:load, client:idle, or client:visible

Resources

Tutorial Code: All examples from this article are available at github.com/smitbhalodiya/astro-shadcn-app

git clone <https://github.com/smitbhalodiya/astro-shadcn-app.git>
cd astro-shadcn-app
pnpm install
pnpm dev

Ready-to-Use Template: Need a landing page quickly? Check out our free Astro + shadcn/ui template at shadcnstudio.com/templates

Next Steps

The combination of Astro and shadcn/ui gives you performance by default with a beautiful design. Whether you’re building a marketing site, SaaS application, or documentation portal, this stack provides the tools for fast, accessible, and visually stunning experiences.

Happy building!

Ajay Patel

CEO & Co-founder

I’ve spent 15+ years in tech as an entrepreneur, programmer, and builder. As co-founder of Clevision, I’ve worked on products like ThemeSelection, PixInvent, FlyonUI, and ShadcnStudio.

Get notified about upcoming Premium & Free themes, Unique promo codes and Sales 🎉 !

[sibwp_form id=1]
] Announcements
Announcements Banner