Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelog
  1. Home
  2. Blog
  3. Next.js and Tailwind CSS
  4. shadcn Vite Setup with Tailwind CSS v4 and React 19
Auf Deutsch lesen
  1. Blog
  2. Next.js and Tailwind CSS
  3. shadcn Vite Setup with Tailwind CSS v4 and React 19
Next.js and Tailwind CSS
Next.js and Tailwind CSS

shadcn Vite Setup with Tailwind CSS v4 and React 19

SR
Serban Rusu · Founder of Wingo UI
Oct 13, 2026 · 7 min read

On this page

0%
  1. What does a Vite project need for shadcn-style components?
  2. How do I do a shadcn Vite setup step by step?
    1. 1. Create the project and install Tailwind
    2. 2. Add the plugin and the Vite path alias
    3. 3. Set tsconfig.app.json paths, without baseUrl
    4. 4. Run init
    5. 5. Add components
  3. How do I get dark mode in Vite without Next.js?
  4. Can I use the shadcn CLI in Vite instead?
    1. The setup that worked: one folder per library
  5. What should I check when something breaks?
  6. Next step
  7. FAQ
    1. Does shadcn work with Vite?
    2. Why does tsc say it cannot find module @/components/ui/button in Vite?
    3. Should I add baseUrl to tsconfig.app.json?
    4. Can I use next-themes in a Vite app?
    5. Can I install Wingo UI components with the shadcn CLI in Vite?

TL;DR

Create the app with npm create vite@latest -- --template react-ts, add tailwindcss and @tailwindcss/vite, set the @ alias in vite.config.ts and as paths in both tsconfig.json and tsconfig.app.json (no baseUrl on TypeScript 6), then run npx wingo-ui@latest init and add components. Dark mode needs a .dark class on html: a short script in index.html plus the next-themes provider, which runs fine without Next.js. In a project already set up with the shadcn CLI, give the second library its own folder so the two button.tsx files never collide.

Published Oct 13, 2026

Most shadcn Vite tutorials were written for Vite 5 and TypeScript 5, and two of their steps now break. The current Vite template ships TypeScript 6, which rejects baseUrl, and it type checks your code through tsconfig.app.json, so an alias added only to tsconfig.json never reaches it. This guide sets up a Vite, React 19 and Tailwind CSS v4 project that takes shadcn-style components, then installs them with the Wingo UI CLI or the shadcn CLI, with the trade-offs of each.

Everything below was run on October 13, 2026 with create-vite 9.2.1 (Vite 8.3.4, React 19.3.0, TypeScript 6.0.3), Tailwind CSS 4.3.3, the wingo-ui CLI 1.0.3, the shadcn CLI 4.21.4 and next-themes 0.4.6. For the Next.js version of this setup, read the Next.js component library setup guide.

What does a Vite project need for shadcn-style components?

Source-owned components are plain .tsx files that import each other through an alias, style themselves with Tailwind utilities and read colors from CSS variables. A Vite Tailwind v4 React project needs five things for them:

PieceWhereWhy
React 19package.jsonref as a prop, no forwardRef
Tailwind CSS v4@tailwindcss/vite plugin, @import "tailwindcss";the utilities and @theme
@ alias for Vitevite.config.tsso the bundler resolves @/components/ui/button
@ alias for TypeScripttsconfig.json and tsconfig.app.jsonso the type checker and editor resolve it
Tokens and cnsrc/index.css, src/lib/utils.tscolors, radius, motion, class merging

How do I do a shadcn Vite setup step by step?

1. Create the project and install Tailwind

bash
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install tailwindcss @tailwindcss/vite
npm install -D @types/node

The template already lists @types/node as of create-vite 9.2.1. Installing it again is harmless, and older templates need it for path in the config.

Replace everything in src/index.css with one line, and delete src/App.css along with its import in src/App.tsx. The starter CSS styles #root, h1 and body and would fight the components.

css
@import "tailwindcss";

2. Add the plugin and the Vite path alias

ts
// vite.config.ts
import 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") } },
})

This Vite path alias is for the bundler only. TypeScript does not read vite.config.ts, so it needs its own copy.

3. Set tsconfig.app.json paths, without baseUrl

The template's tsconfig.json has "files": [] and two references. Your src is checked through tsconfig.app.json. Add the alias to both:

json
// tsconfig.json
{
"files": [],
"compilerOptions": { "paths": { "@/*": ["./src/*"] } },
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}
json
// tsconfig.app.json, inside compilerOptions
"types": ["vite/client", "node"],
"paths": { "@/*": ["./src/*"] },

Three details here, each from a failed run:

  • Paths only in tsconfig.json are not enough. We let wingo-ui init add the alias on its own: it wrote it to tsconfig.json, and tsc -b then failed with Cannot find module '@/components/ui/button', because tsconfig.app.json never saw it.
  • No baseUrl on TypeScript 6. With "baseUrl": "." in tsconfig.app.json, tsc -b stopped with error TS5101: the option is deprecated and stops working in TypeScript 7. paths resolves relative to the config file without it.
  • Add node to types. The template limits global types to vite/client. Some components read process.env.NODE_ENV to print development warnings (Theme Toggle does), and tsc reports Cannot find name 'process' without it. Vite replaces the expression when it builds: our production bundle had no process.env left in it.

4. Run init

bash
npx wingo-ui@latest init

It detected vite, found the alias in tsconfig.app.json (the CLI follows the references list) and reported alias @/* -> src/. Then it created src/lib/utils.ts with cn, installed clsx and tailwind-merge, merged the tokens into src/index.css with a .dark block and @custom-variant dark (&:is(.dark *));, and wrote wingo-ui.json.

If package.json lists react-router or @react-router/dev, init records the framework as react-router instead of vite, because it checks for React Router first. File locations and commands stay the same.

5. Add components

bash
npx wingo-ui@latest add button card dialog

The free items install without an account. Files land in src/components/ui, src/hooks and src/lib, with the npm packages they import. Give the page its colors in index.html, since the starter CSS that styled body is gone:

html
<body class="bg-background text-foreground antialiased">

Then use them:

tsx
// src/App.tsx
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { Dialog, DialogClose } from "@/components/ui/dialog"
export default function App() {
return (
<main className="mx-auto flex max-w-md flex-col gap-6 p-6">
<Card
title="Pro plan"
description="Renews on November 1."
footer={
<Dialog
trigger={<Button variant="soft">Cancel plan</Button>}
title="Cancel your plan?"
description="You keep Pro until the end of the billing period."
footer={
<>
<DialogClose asChild>
<Button variant="soft">Keep plan</Button>
</DialogClose>
<Button tone="danger">Cancel plan</Button>
</>
}
/>
}
/>
</main>
)
}

npm run build (which runs tsc -b first) passed with this file. Button, Card and Dialog carry "use client" at the top for the Next.js App Router; Vite ignores the directive. Press the button below to see its loading state.

Click the button: it shows a spinner for a moment and keeps its width, so nothing next to it moves
$ npx wingo-ui@latest add button
FreeButton docs

How do I get dark mode in Vite without Next.js?

The components follow a .dark class on <html>. Something has to set it before the first paint, and something has to change it when the user picks a theme. Our Tailwind v4 dark mode guide covers the @custom-variant line those components rely on.

Before paint: a script in index.html, in <head>, reads the saved choice and the system setting:

html
<script>
try {
const theme = localStorage.getItem("theme")
const dark = theme === "dark" || (theme !== "light" && matchMedia("(prefers-color-scheme: dark)").matches)
document.documentElement.classList.toggle("dark", dark)
} catch {}
</script>

On change: next-themes. Despite the name, version 0.4.6 depends on nothing but React, and it stores the choice under the same theme key the script reads:

tsx
// src/main.tsx
import { StrictMode } from "react"
import { createRoot } from "react-dom/client"
import { ThemeProvider } from "next-themes"
import { UIProvider } from "@/lib/ui-config"
import "./index.css"
import App from "./App"
createRoot(document.getElementById("root")!).render(
<StrictMode>
<ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
<UIProvider locale="en-US">
<App />
</UIProvider>
</ThemeProvider>
</StrictMode>,
)

We loaded the production build in Chromium with Playwright in both color schemes: the dark class was on <html> before React started when the system preferred dark, the theme toggle flipped it, and localStorage kept the choice. UIProvider is optional: it sets app-wide component defaults and the locale for dates and numbers.

What uses next-themes in the library? As of October 13, 2026, only theme-toggle.tsx imports it, and the CLI installs it with that item. Theme Toggle (Pro) also takes value and onValueChange if you would rather keep the theme in your own state; then your code sets the class. Three navigation components, Bounce Sidebar, Hook Sidebar and Gooey Nav, import next/link, so they need Next.js. Everything else runs in Vite unchanged.

Can I use the shadcn CLI in Vite instead?

Yes, with limits worth knowing first. We ran npx shadcn@latest init -t vite -b radix -p nova on a fresh project from step 3. Pick Radix: the installation docs note that Base UI styles rewrite asChild, which these components rely on. Init 4.21.4 wrote components.json, src/lib/utils.ts and its own src/components/ui/button.tsx.

Free Wingo UI items install from their URL:

bash
npx shadcn@latest add https://wingo-ui.com/r/dialog.json

The CLI asked to overwrite utils.ts and then button.tsx. Answer no to utils.ts: shadcn's version re-exports cn from the cn package, a drop-in for clsx plus tailwind-merge, and our files only need a cn export. Here is the catch. Keep shadcn's button and our dialog no longer type checks, because it passes props such as radius that shadcn's button does not have. Overwrite it and shadcn components that need its API break instead: shadcn's calendar imports buttonVariants, which our button does not export. The shadcn CLI also did not add our tokens to src/index.css, and it does not record versions, so wingo-ui update cannot merge later releases into those files.

For Pro items the docs add the @wingo-ui namespace to components.json with your key in an environment variable, then npx shadcn@latest add @wingo-ui/calendar. We did not test that path with a key for this post.

The setup that worked: one folder per library

In the same shadcn project we ran npx wingo-ui@latest init, which added only the 43 token names shadcn had not defined, changed "ui" in wingo-ui.json to "@/components/wingo", and ran npx wingo-ui@latest add dialog. Our files went to src/components/wingo, their imports were rewritten to match, shadcn's button.tsx stayed untouched, and tsc -b passed. The CLI docs cover the other aliases.

What should I check when something breaks?

ErrorCauseFix
Cannot find module '@/...' from tsc -balias missing in tsconfig.app.jsonadd paths there (step 3)
TS5101: Option 'baseUrl' is deprecatedbaseUrl on TypeScript 6remove it, keep paths
Cannot find name 'process'types limited to vite/clientadd "node" to types
Dark mode flashes light on reloadno script in index.htmladd the pre-paint script
Components unstyledtokens missing from src/index.cssrun npx wingo-ui@latest init again

Next step

Run npx wingo-ui@latest add button card dialog in your new project and open the installation docs for the manual path. The token system behind all of this is explained in the pillar on Tailwind v4 design tokens, and more setup guides live under Next.js and Tailwind CSS, including the Next.js component library setup if you move the project to the App Router.

Components in this post

  • Button

    The React button component every screen starts with: five variants, six tones, all sizes, and a loading state that never jumps.

    Free
  • Card

    A React card component for listings, stats, settings and plans: media, header, body and footer parts, and a lift-and-press mode.

    Free
  • Dialog

    A React modal dialog for focused tasks: a spring-scaled card on desktop and a bottom sheet on phones, with nesting and scrolling.

    Free
  • UIProvider

    A React config provider for Wingo UI: app-wide default props for every component, the Intl locale and a reduced-motion policy.

    Free

FAQ

Does shadcn work with Vite?

Yes. shadcn components are React and Tailwind source files with no Next.js dependency. Vite needs the @tailwindcss/vite plugin, an @ alias in vite.config.ts and matching paths in tsconfig.app.json.

Why does tsc say it cannot find module @/components/ui/button in Vite?

The Vite template type checks src through tsconfig.app.json, which is a reference of tsconfig.json. Paths only in tsconfig.json do not reach it, so add the same paths entry for the @ alias, pointing at ./src, to tsconfig.app.json as well.

Should I add baseUrl to tsconfig.app.json?

Not on TypeScript 6, which the Vite template installs as of October 2026. tsc -b fails with error TS5101 because baseUrl is deprecated, and paths work without it.

Can I use next-themes in a Vite app?

Yes. next-themes 0.4.6 lists only react and react-dom as peer dependencies. Wrap the app in ThemeProvider with attribute="class" and add a small script to index.html so the class is set before the first paint.

Can I install Wingo UI components with the shadcn CLI in Vite?

Free items install from their URL, for example npx shadcn@latest add https://wingo-ui.com/r/dialog.json (opens in a new tab). The shadcn CLI does not add the Wingo UI tokens or record versions, and a shadcn-made button.tsx clashes with ours, so the Wingo UI CLI with its own folder is the smoother path.

  • Vite
  • shadcn/ui
  • Setup
  • Installation
  • Tailwind CSS v4

Share

SR

About the author

Serban Rusu

Founder of Wingo UI

Serban Rusu founded Wingo UI. He builds its component library, CLI and MCP server, and writes about React interfaces for phones and AI coding agents.

More from Serban
Keep reading

Related posts

All posts
Next.js and Tailwind CSS

Next.js Component Library Setup for the App Router

Next.js component library setup for the App Router, tested on Next.js 16.4: use client boundaries, tokens in globals.css, dark mode without a flash, updates.

SRSerban Rusu·Oct 13, 2026·8 min read
Next.js and Tailwind CSS

Tailwind v4 Design Tokens: Colors, Dark Mode and Contrast

Tailwind v4 design tokens from a production React library: @theme inline, semantic surfaces, status colors, dark mode and a contrast check you can run in CI.

SRSerban Rusu·Oct 9, 2026·20 min read
Building UI with AI agents

AGENTS.md vs CLAUDE.md vs Cursor Rules for React

AGENTS.md vs CLAUDE.md vs Cursor rules for React and Tailwind: which agent reads which file in October 2026, and a rules file that keeps agents on your tokens.

SRSerban Rusu·Oct 9, 2026·14 min read
Newsletter

Get new posts by email

New guides, tutorials and comparisons from the Wingo UI blog, sent when they are published.

No spam. Unsubscribe at any time.

WingoUI

Animated, configurable, mobile-first React components and blocks. Copy the source, make it yours, and let your coding agent build with it.

Product

  • Components
  • Templates
  • Pricing
  • AI agents
  • Theme generator

Resources

  • Docs
  • Blog
  • Changelog
  • Changelog RSS
  • llms.txt

Legal

  • Terms
  • Privacy
  • Refunds
  • License
  • Imprint

Component categories

  • Buttons & Actions
  • Inputs
  • Forms
  • Navigation
  • Overlays
  • Feedback
  • Data Display
  • Tables & Lists
  • Charts & Stats
  • Layout
  • Media
  • AI Kit
  • Text & Effects
  • Mobile
  • Commerce
  • Marketing Sections
  • Blocks
  • Hooks & Utilities

© 2026 Wingo UI

Wingo UI