The shadcn/ui CLI assumes Tailwind CSS. If your stack uses UnoCSS, the official npx shadcn@latest init will fail on “No Tailwind CSS configuration found” and “No import alias found.” You can still use shadcn components by wiring things manually and using the community preset unocss-preset-shadcn. Do not run init; follow the steps below instead.

This guide targets an Astro project with UnoCSS and React (for shadcn’s React components). The same preset works with Vue, Svelte, or Solid; the repo’s README has variants.

But, first: Why UnoCSS?

The site is Astro, content-first, mostly static HTML with a single React island for the shadcn components. UnoCSS trumps Tailwind in two places that matter.

On-demand engine: UnoCSS generates utilities through an atomic CSS engine with no separate file-scanning pass — sub-10ms HMR regardless of project size, smaller CSS payloads on content-heavy sites. Tailwind v4’s Oxide engine closes most of the speed gap, but the bundle-size edge persists.

Composable presets with granular opt-outs: My config stacks presetWind4, presetAnimations, and presetShadcn side by side. The shadcn preset runs with { color: false, globals: false } so its OKLCH variables don’t fight the HSL tokens I define in Base.astro. Tailwind’s plugin model doesn’t expose that kind of per-preset knob as cleanly.

PresetWind4 mirrors Tailwind v4’s class names and theme keys, so the markup still reads like Tailwind. Trade-offs: no Tailwind plugin ecosystem (I use none) and no npx astro add one-liner (manual setup, one-time).

Install packages

UnoCSS side:

npm i -D unocss-preset-animations unocss-preset-shadcn
npm i @unocss/reset

Shadcn’s React components need:

npm i lucide-react class-variance-authority clsx tailwind-merge

If you don’t already use React in Astro:

npm i @astrojs/react react react-dom

Add the React integration in astro.config.mjs (see Astro’s React integration docs).

Path alias

The shadcn CLI and your imports use @/components and @/lib/utils. In tsconfig.json, under compilerOptions, set:

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

Then @/components/ui resolves to ./src/components/ui, and so on.

cn( ) helper

Create src/lib/utils.ts:

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

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

Same helper as in the shadcn/ui installation docs. tailwind-merge works with the class names unocss-preset-shadcn emits.

components.json

In the project root, add components.json so the CLI knows where to put components and which style to use:

{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": false,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.js",
    "css": "src/styles/global.css",
    "baseColor": "neutral",
    "cssVariables": true,
    "prefix": ""
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui"
  }
}

No need to create src/styles/global.css for UnoCSS; the tailwind block only satisfies the CLI. baseColor is the default palette (e.g. neutral), style the component set (new-york or default).

Optional: empty Tailwind config

Some shadcn CLI commands look for a Tailwind config. To avoid errors, add an empty file at the project root:

tailwind.config.js:

export default {};

UnoCSS ignores this; only the CLI reads it.

Wire UnoCSS

In your UnoCSS config (e.g. uno.config.ts), add the presets and content so shadcn’s classes are generated.

If you use PresetWind4 (UnoCSS’s Tailwind-4–style preset, documented here):

import { defineConfig } from 'unocss'
import presetWind4 from 'unocss/preset-wind4'
import presetAnimations from 'unocss-preset-animations'
import { presetShadcn } from 'unocss-preset-shadcn'

export default defineConfig({
  presets: [
    presetWind4({ preflights: { reset: true } }),
    presetAnimations(),
    presetShadcn({ color: 'neutral' }),
  ],
  content: {
    pipeline: {
      include: [
        /\.(astro|svelte|[jt]sx|mdx?|html)($|\?)/,
        'src/**/*.{js,ts}',
      ],
    },
  },
  // theme, etc.
})

Wind4 ships its own reset; with preflights.reset: true you don’t need to import @unocss/reset/tailwind.css in your layout.

If you use PresetWind3, use the v3 build of the shadcn preset so theme keys match:

import { presetShadcnV3 } from 'unocss-preset-shadcn/v3'
// ...
presets: [
  presetWind3(),
  presetAnimations(),
  presetShadcnV3({ color: 'neutral' }),
],

Theme key names differ between Wind3 and Wind4 (e.g. Wind4 uses theme.font and theme.text instead of fontFamily and fontSize). The Wind4 preset docs list the mapping if you migrate.

TypeScript may complain about preset theme types when mixing presets. You can cast the animation and shadcn presets to the config’s preset type, or use a single as any on the presets array if you prefer to keep the config minimal.

Add components

From the project root:

npx shadcn@latest add button

Components are written under src/components/ui (or whatever you set in components.json). Use them in Astro via the React integration (e.g. client:load or client:visible on the wrapper that renders the component).

References

  • unocss-community/unocss-preset-shadcn: Preset that generates shadcn-compatible utilities for UnoCSS; README covers Wind3 vs Wind4 and optional components.json / utils.ts snippets.
  • UnoCSS Wind4 preset: Official docs for the Wind4 preset (reset, theme keys, preflights).
  • shadcn/ui: Component docs and installation guide (Tailwind-based; we only reuse the CLI and the cn helper pattern).