# shadcn Vite Setup mit Tailwind CSS v4 und React 19

> shadcn Vite Setup, getestet mit Vite 8 und TypeScript 6: Tailwind-v4-Plugin, @-Alias in tsconfig.app.json, Dark Mode ohne Next.js, dann Komponenten hinzufügen.

- Autor: [Serban Rusu](https://wingo-ui.com/blog/authors/serban), Founder of Wingo UI
- Veröffentlicht: 13. Okt. 2026
- Kategorie: [Next.js und Tailwind CSS](https://wingo-ui.com/de/blog/category/nextjs-tailwind)
- Lesezeit: 7 Min.
- Kanonische URL: https://wingo-ui.com/de/blog/shadcn-vite-setup
- Englische Fassung: https://wingo-ui.com/blog/shadcn-vite-setup

## Kurz gesagt

Leg die App mit npm create vite@latest -- --template react-ts an, installier tailwindcss und @tailwindcss/vite, setz den @-Alias in vite.config.ts und als paths in tsconfig.json und tsconfig.app.json (kein baseUrl unter TypeScript 6), dann führ npx wingo-ui@latest init aus und füg Komponenten hinzu. Dark Mode braucht eine .dark-Klasse am html-Element: ein kurzes Script in index.html plus den Provider von next-themes, der auch ohne Next.js läuft. In einem Projekt, das schon mit der shadcn CLI eingerichtet ist, bekommt die zweite Bibliothek einen eigenen Ordner, damit sich die beiden button.tsx-Dateien nie in die Quere kommen.

Die meisten Anleitungen zum shadcn Vite Setup wurden für Vite 5 und TypeScript 5 geschrieben, und zwei ihrer Schritte funktionieren heute nicht mehr. Das aktuelle Vite-Template bringt TypeScript 6 mit, das `baseUrl` ablehnt. Außerdem prüft es deinen Code über `tsconfig.app.json`, ein Alias nur in `tsconfig.json` kommt dort also nie an. Dieser Guide richtet ein Projekt mit Vite, React 19 und Tailwind CSS v4 ein, das Komponenten im shadcn-Stil aufnimmt. Danach installierst du sie mit der Wingo UI CLI oder der shadcn CLI, jeweils mit ihren Vor- und Nachteilen.

Alles hier haben wir am 13. Oktober 2026 ausgeführt, mit `create-vite` 9.2.1 (Vite 8.3.4, React 19.3.0, TypeScript 6.0.3), Tailwind CSS 4.3.3, der `wingo-ui` CLI 1.0.3, der `shadcn` CLI 4.21.4 und next-themes 0.4.6. Die Next.js-Variante dieses Setups findest du im [Guide zum Next.js Component Library Setup](https://wingo-ui.com/de/blog/next-js-component-library-setup).

## Was braucht ein Vite-Projekt für Komponenten im shadcn-Stil?

Komponenten, deren Quellcode dir gehört, sind einfache `.tsx`-Dateien. Sie importieren sich gegenseitig über einen Alias, stylen sich mit Tailwind-Utilities und lesen Farben aus CSS-Variablen. Ein Vite Tailwind v4 React Projekt braucht dafür fünf Dinge:

| Baustein | Wo | Wozu |
| --- | --- | --- |
| React 19 | `package.json` | `ref` als Prop, kein `forwardRef` |
| Tailwind CSS v4 | Plugin `@tailwindcss/vite`, `@import "tailwindcss";` | die Utilities und `@theme` |
| `@`-Alias für Vite | `vite.config.ts` | damit der Bundler `@/components/ui/button` auflöst |
| `@`-Alias für TypeScript | `tsconfig.json` und `tsconfig.app.json` | damit Type Checker und Editor ihn auflösen |
| Tokens und `cn` | `src/index.css`, `src/lib/utils.ts` | Farben, Radius, Motion, Klassen zusammenführen |

## Wie läuft ein shadcn Vite Setup Schritt für Schritt?

### 1. Projekt anlegen und Tailwind installieren

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

In create-vite 9.2.1 steht `@types/node` bereits im Template. Eine zweite Installation schadet nicht, und ältere Templates brauchen das Paket für `path` in der Config.

Ersetz den gesamten Inhalt von `src/index.css` durch eine Zeile und lösch `src/App.css` samt Import in `src/App.tsx`. Das Starter-CSS stylt `#root`, `h1` und `body` und würde mit den Komponenten kollidieren.

```css
@import "tailwindcss";
```

### 2. Plugin und Vite Path Alias ergänzen

```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") } },
})
```

Dieser Vite Path Alias gilt nur für den Bundler. TypeScript liest `vite.config.ts` nicht, es braucht also eine eigene Kopie.

### 3. tsconfig.app.json paths setzen, ohne baseUrl

Die `tsconfig.json` des Templates hat `"files": []` und zwei Referenzen. Dein `src` wird über `tsconfig.app.json` geprüft. Trag den Alias in beide Dateien ein:

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

```json
// tsconfig.app.json, innerhalb von compilerOptions
"types": ["vite/client", "node"],
"paths": { "@/*": ["./src/*"] },
```

Drei Details, jedes aus einem fehlgeschlagenen Durchlauf:

- **Paths nur in `tsconfig.json` reichen nicht.** Wir haben `wingo-ui init` den Alias selbst eintragen lassen. Es schrieb ihn in `tsconfig.json`, und `tsc -b` scheiterte danach mit `Cannot find module '@/components/ui/button'`, weil `tsconfig.app.json` ihn nie gesehen hat.
- **Kein `baseUrl` unter TypeScript 6.** Mit `"baseUrl": "."` in `tsconfig.app.json` brach `tsc -b` mit Fehler TS5101 ab: Die Option ist veraltet und funktioniert ab TypeScript 7 nicht mehr. `paths` löst ohne sie relativ zur Config-Datei auf.
- **`node` in `types` aufnehmen.** Das Template beschränkt die globalen Typen auf `vite/client`. Manche Komponenten lesen `process.env.NODE_ENV`, um Warnungen in der Entwicklung auszugeben ([Theme Toggle](https://wingo-ui.com/components/theme-toggle) macht das), und ohne den Eintrag meldet `tsc` den Fehler `Cannot find name 'process'`. Vite ersetzt den Ausdruck beim Build: In unserem Production-Bundle war kein `process.env` mehr übrig.

### 4. init ausführen

```bash
npx wingo-ui@latest init
```

init erkannte `vite`, fand den Alias in `tsconfig.app.json` (die CLI folgt der `references`-Liste) und meldete `alias @/* -> src/`. Danach legte es `src/lib/utils.ts` mit `cn` an, installierte `clsx` und `tailwind-merge`, führte die Tokens in `src/index.css` zusammen, mit einem `.dark`-Block und `@custom-variant dark (&:is(.dark *));`, und schrieb `wingo-ui.json`.

Steht `react-router` oder `@react-router/dev` in `package.json`, speichert init als Framework `react-router` statt `vite`, weil es zuerst nach React Router sucht. Dateipfade und Befehle bleiben gleich.

### 5. Komponenten hinzufügen

```bash
npx wingo-ui@latest add button card dialog
```

Die kostenlosen Items installierst du ohne Account. Die Dateien landen in `src/components/ui`, `src/hooks` und `src/lib`, zusammen mit den npm-Paketen, die sie importieren. Gib der Seite ihre Farben in `index.html`, denn das Starter-CSS, das `body` gestylt hat, ist weg:

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

Dann nutzt du sie:

```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-Tarif"
        description="Verlängert sich am 1. November."
        footer={
          <Dialog
            trigger={<Button variant="soft">Tarif kündigen</Button>}
            title="Tarif wirklich kündigen?"
            description="Pro bleibt dir bis zum Ende des Abrechnungszeitraums erhalten."
            footer={
              <>
                <DialogClose asChild>
                  <Button variant="soft">Tarif behalten</Button>
                </DialogClose>
                <Button tone="danger">Tarif kündigen</Button>
              </>
            }
          />
        }
      />
    </main>
  )
}
```

`npm run build` (das vorher `tsc -b` ausführt) lief mit dieser Datei durch. [Button](https://wingo-ui.com/components/button), [Card](https://wingo-ui.com/components/card) und [Dialog](https://wingo-ui.com/components/dialog) tragen `"use client"` ganz oben, für den Next.js App Router. Vite ignoriert die Direktive. Drück den Button unten, um seinen Ladezustand zu sehen.

> Live-Demo (Button): Klick auf den Button: Er zeigt kurz einen Spinner und behält seine Breite, nichts daneben verrutscht. Probier sie unter [Button](https://wingo-ui.com/components/button) aus und installier sie mit `npx wingo-ui@latest add button`.

## Wie bekomme ich Dark Mode in Vite ohne Next.js?

Die Komponenten folgen einer `.dark`-Klasse am `<html>`-Element. Irgendetwas muss sie vor dem ersten Paint setzen, und irgendetwas muss sie ändern, wenn der Nutzer ein Theme wählt. Unser [Guide zu Tailwind v4 Dark Mode](https://wingo-ui.com/de/blog/tailwind-v4-dark-mode) erklärt die `@custom-variant`-Zeile, auf die sich die Komponenten verlassen.

**Vor dem Paint:** Ein Script in `index.html`, im `<head>`, liest die gespeicherte Wahl und die Systemeinstellung:

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

**Beim Wechsel:** next-themes. Trotz des Namens hängt Version 0.4.6 nur von React ab, und sie speichert die Wahl unter demselben Schlüssel `theme`, den das Script liest:

```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>,
)
```

Wir haben den Production-Build mit Playwright in Chromium in beiden Farbschemata geladen. Bevorzugte das System Dark Mode, stand die Klasse `dark` schon am `<html>`, bevor React startete. Der Theme Toggle schaltete sie um, und `localStorage` behielt die Wahl. [`UIProvider`](https://wingo-ui.com/components/ui-config) ist optional: Er setzt app-weite Standardwerte für Komponenten und die Locale für Datums- und Zahlenformate.

Was in der Bibliothek nutzt next-themes? Stand 13. Oktober 2026 importiert nur `theme-toggle.tsx` das Paket, und die CLI installiert es mit diesem Item. Theme Toggle (Pro) akzeptiert auch `value` und `onValueChange`, falls du das Theme lieber in deinem eigenen State hältst. Dann setzt dein Code die Klasse. Drei Navigationskomponenten, Bounce Sidebar, Hook Sidebar und Gooey Nav, importieren `next/link` und brauchen deshalb Next.js. Alles andere läuft unverändert in Vite.

## Kann ich in Vite stattdessen die shadcn CLI nutzen?

Ja, mit Grenzen, die du vorher kennen solltest. Wir haben `npx shadcn@latest init -t vite -b radix -p nova` in einem frischen Projekt nach Schritt 3 ausgeführt. Wähl Radix: Die [Installations-Doku](https://wingo-ui.com/docs/installation) weist darauf hin, dass die Base-UI-Styles `asChild` umschreiben, und darauf verlassen sich diese Komponenten. Init 4.21.4 schrieb `components.json`, `src/lib/utils.ts` und eine eigene `src/components/ui/button.tsx`.

Kostenlose Wingo-UI-Items installierst du über ihre URL:

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

Die CLI fragte, ob sie `utils.ts` und danach `button.tsx` überschreiben soll. Bei `utils.ts` antwortest du mit Nein: Die shadcn-Version exportiert `cn` aus dem Paket `cn` weiter, einem direkten Ersatz für `clsx` plus `tailwind-merge`, und unsere Dateien brauchen nur einen `cn`-Export. Und hier liegt der Haken. Behältst du den Button von shadcn, besteht unser Dialog den Type Check nicht mehr, weil er Props wie `radius` übergibt, die der shadcn-Button nicht kennt. Überschreibst du ihn, brechen stattdessen shadcn-Komponenten, die dessen API brauchen: Der Calendar von shadcn importiert `buttonVariants`, und unser Button exportiert das nicht. Die shadcn CLI hat außerdem unsere Tokens nicht in `src/index.css` eingetragen, und sie speichert keine Versionen. `wingo-ui update` kann spätere Releases deshalb nicht in diese Dateien mergen.

Für Pro-Items trägst du laut Doku den Namespace `@wingo-ui` in `components.json` ein, mit deinem Key in einer Umgebungsvariable, und führst dann `npx shadcn@latest add @wingo-ui/calendar` aus. Diesen Weg haben wir für diesen Artikel nicht mit einem Key getestet.

### Das Setup, das funktioniert hat: ein Ordner pro Bibliothek

Im selben shadcn-Projekt haben wir `npx wingo-ui@latest init` ausgeführt. Es ergänzte nur die 43 Token-Namen, die shadcn nicht definiert hatte. Dann haben wir `"ui"` in `wingo-ui.json` auf `"@/components/wingo"` geändert und `npx wingo-ui@latest add dialog` ausgeführt. Unsere Dateien landeten in `src/components/wingo`, ihre Imports wurden passend umgeschrieben, die `button.tsx` von shadcn blieb unangetastet, und `tsc -b` lief durch. Die [CLI-Doku](https://wingo-ui.com/docs/cli) beschreibt die übrigen Aliase.

## Was prüfe ich, wenn etwas kaputtgeht?

| Fehler | Ursache | Lösung |
| --- | --- | --- |
| `Cannot find module '@/...'` von `tsc -b` | Alias fehlt in `tsconfig.app.json` | dort `paths` ergänzen (Schritt 3) |
| `TS5101: Option 'baseUrl' is deprecated` | `baseUrl` unter TypeScript 6 | entfernen, `paths` behalten |
| `Cannot find name 'process'` | `types` auf `vite/client` beschränkt | `"node"` zu `types` hinzufügen |
| Dark Mode blitzt beim Neuladen hell auf | kein Script in `index.html` | das Script vor dem Paint ergänzen |
| Komponenten ohne Styles | Tokens fehlen in `src/index.css` | `npx wingo-ui@latest init` erneut ausführen |

## Nächster Schritt

Führ `npx wingo-ui@latest add button card dialog` in deinem neuen Projekt aus und öffne die [Installations-Doku](https://wingo-ui.com/docs/installation) für den manuellen Weg. Das Token-System hinter all dem erklärt der Pillar-Artikel zu [Tailwind v4 Design Tokens](https://wingo-ui.com/de/blog/tailwind-v4-design-tokens). Weitere Setup-Guides findest du unter [Next.js und Tailwind CSS](https://wingo-ui.com/de/blog/category/nextjs-tailwind), darunter das [Next.js Component Library Setup](https://wingo-ui.com/de/blog/next-js-component-library-setup), falls du das Projekt auf den App Router umstellst.

## Komponenten in diesem Artikel

- [Button](https://wingo-ui.com/components/button) (Free): The React button component every screen starts with: five variants, six tones, all sizes, and a loading state that never jumps. Installation: `npx wingo-ui@latest add button`
- [Card](https://wingo-ui.com/components/card) (Free): A React card component for listings, stats, settings and plans: media, header, body and footer parts, and a lift-and-press mode. Installation: `npx wingo-ui@latest add card`
- [Dialog](https://wingo-ui.com/components/dialog) (Free): A React modal dialog for focused tasks: a spring-scaled card on desktop and a bottom sheet on phones, with nesting and scrolling. Installation: `npx wingo-ui@latest add dialog`
- [UIProvider](https://wingo-ui.com/components/ui-config) (Free): A React config provider for Wingo UI: app-wide default props for every component, the Intl locale and a reduced-motion policy. Installation: `npx wingo-ui@latest add ui-config`

## Häufige Fragen

### Funktioniert shadcn mit Vite?

Ja. shadcn-Komponenten sind Quelldateien aus React und Tailwind ohne Abhängigkeit zu Next.js. Vite braucht das Plugin @tailwindcss/vite, einen @-Alias in vite.config.ts und passende paths in tsconfig.app.json.

### Warum meldet tsc in Vite, dass es das Modul @/components/ui/button nicht findet?

Das Vite-Template prüft src über tsconfig.app.json, eine Referenz von tsconfig.json. Paths, die nur in tsconfig.json stehen, kommen dort nicht an. Trag denselben paths-Eintrag für den @-Alias mit Ziel ./src deshalb auch in tsconfig.app.json ein.

### Soll ich baseUrl in tsconfig.app.json eintragen?

Nicht unter TypeScript 6, das das Vite-Template Stand Oktober 2026 installiert. tsc -b bricht dann mit Fehler TS5101 ab, weil baseUrl veraltet ist, und paths funktioniert auch ohne.

### Kann ich next-themes in einer Vite-App nutzen?

Ja. next-themes 0.4.6 nennt nur react und react-dom als Peer Dependencies. Umschließ die App mit ThemeProvider und attribute="class" und ergänz ein kleines Script in index.html, damit die Klasse vor dem ersten Paint gesetzt ist.

### Kann ich Wingo-UI-Komponenten in Vite mit der shadcn CLI installieren?

Kostenlose Items installierst du über ihre URL, zum Beispiel npx shadcn@latest add https://wingo-ui.com/r/dialog.json. Die shadcn CLI ergänzt aber keine Wingo-UI-Tokens und speichert keine Versionen, und eine button.tsx von shadcn kollidiert mit unserer. Der bequemere Weg ist deshalb die Wingo UI CLI mit eigenem Ordner.

---

Quelle: https://wingo-ui.com/de/blog/shadcn-vite-setup
