Tokens, helpers, and the theme composable — installed with one npx. Files copy into your repo. You own them.
Bootstrap
shadcn-vue's preflight refuses to write components when it can't find a Tailwind setup (No Tailwind CSS configuration found), so wire Tailwind v4 in first. If you're not on Nuxt, follow tailwindcss.com/docs/installation then jump to step 02.
1. Install Tailwind v4 + the Vite plugin
$ npm install -D tailwindcss @tailwindcss/vite typescript2. Create a placeholder CSS entry
shadcn-vue probes for a Tailwind-importing CSS file before it does anything. Step 03 overwrites this file with the full OKLCH token set, so the placeholder is throwaway. Create app/assets/css/tailwind.css in your editor and paste in:
@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=Anybody:wght@600;700;800&display=swap');
@import 'tailwindcss';
@import 'tw-animate-css';
@custom-variant dark (&:is(.dark *));
3. Wire the Vite plugin + CSS in nuxt.config.ts
import tailwindcss from '@tailwindcss/vite'
export default defineNuxtConfig({
css: ['~/assets/css/tailwind.css'],
components: [{ path: '~/components', pathPrefix: false }],
vite: { plugins: [tailwindcss()] }
})components.json You can let npx shadcn-vue add scaffold this for you, but the CLI fires interactive prompts (icon library / font / base color) before it reads -y, and it won't pre-register the @uipkge named registry. Writing it yourself skips both. Our registry items target ~/app/... directly (Nuxt 4 srcDir), so files land in the right place regardless of how the CLI resolves the @/ aliases below.
Create components.json at the project root and paste this in:
{
"$schema": "https://shadcn-vue.com/schema.json",
"style": "new-york",
"typescript": true,
"tailwind": {
"config": "",
"css": "app/assets/css/tailwind.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"composables": "@/composables",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib"
},
"registries": {
"@uipkge": "https://uipkge.dev/r/nuxt/{name}.json"
}
}$ npx shadcn-vue@latest add https://uipkge.dev/r/nuxt/init.json -y With components.json already in place from step 02, the CLI resolves init.json's three transitive deps (tailwind tokens, cn(), useTheme) and writes four files. You'll see a single prompt mid-install: "The file ../../assets/css/tailwind.css already exists. Would you like to overwrite? (y/N)" — press Enter (default N). The token merge has already run; the prompt is only asking whether to overwrite your file with a smaller bootstrap copy, which would lose the merged tokens. The "Circular dependency detected" notice you may see before the prompt is a benign upstream resolver quirk.
Drops these into your repo — yours to edit, no upgrades.
One-shot bootstrap. Pulls tailwind tokens, the cn() helper, and the useTheme composable in a single command. Run this first when starting a new app.
Tailwind v4 design tokens (light + dark), motion tokens, base layer styling, and shadow utilities. Ships the full canonical tailwind.css as the `files` content so a re-pull with `--overwrite` lands a working setup. The cssVars block stays as a safety net for first-install scenarios where the CLI merges tokens into a consumer's pre-existing tailwind.css. The `css` (@layer base) field is intentionally omitted to avoid duplicating the @layer base block already present in the shipped file when the CLI merges.
Tailwind class merge helper: cn(). Combines clsx and tailwind-merge.
useTheme composable + a Nuxt boot plugin that inlines the dark-class assignment in <head>, so initial paint matches the saved theme (no white flash on reload).
$ npx shadcn-vue@latest add https://uipkge.dev/r/nuxt/button.json -yPaste the URL of any item from /components or /blocks. Transitive registry deps come along.
import { Button } from '@/components/ui/button'
// then in your template:
// <Button>Hello uipkge</Button>Cosmetic noise you'll see during install. None of these break anything; they're listed so you can ignore them without wondering.
init.json. False positive from shadcn-vue's resolver — the init → {tailwind, utils, use-theme} graph is acyclic. Install completes cleanly. Button.vue and index.ts as resolving to UiButton. Harmless — your explicit import { Button } from '@/components/ui/button' wins. Will quiet once shadcn-vue's generated index files learn to opt out of auto-import. tailwind.css and not the Nuxt-default main.css. The tailwind registry item ships two independent install actions: a file-write (target hard-coded in the manifest: ~/app/assets/css/tailwind.css) and a token-merge (target read from components.json's tailwind.css field). If those two paths differ, you get two CSS files — one with the real tokens, one with a 10-line bootstrap entry that's never loaded. Naming both tailwind.css (the docs default above) makes them converge on a single file. Use main.css if you prefer the Nuxt convention — just delete the orphan after install. The shadcn-vue CLI rejects files: [] on registry items, otherwise we'd ship cssVars-only.