Skip to content

Nuxt + shadcn-vue prompt

Rules that make your coding agent write Nuxt 4 and Vue 3.5 the way they work today, with shadcn-vue components you own on Reka UI and Tailwind CSS v4 tokens.

What it makes your agent good at

Agents still write Nuxt 2 asyncData, the Options API, process.client checks and a tailwind.config.js, or call useFetch from a click handler and keep request data in module-level refs. This prompt gives them the Nuxt 4 layout under app/, Vue 3.5's defineModel and reactive props, keyed data fetching, validated server routes, and shadcn-vue components added with the CLI and styled with tokens.

  1. The Nuxt 4 layoutapp/ for pages, components and composables, server/ for API routes, shared/ for schemas both sides use, auto-imports left alone.
  2. Vue 3.5 script setupDestructured props with defaults, defineModel for v-model, useTemplateRef and useId, typed emits and slots.
  3. Data fetched onceuseFetch and useAsyncData in setup with unique keys, $fetch in handlers, useState instead of module-level refs.
  4. Server routes that check inputOne file per method, bodies parsed with readValidatedBody and Zod, createError for failures, secrets from useRuntimeConfig.
  5. shadcn-vue you ownComponents added with the CLI, variants in cva, cn() for classes, as-child and v-model on Reka UI parts.
  6. Tailwind v4 tokens and dark modeOKLCH variables mapped with @theme inline, a dark class from color-mode, no tailwind.config.js.

The Nuxt + shadcn-vue rules

135 lines, current to Nuxt 4, Vue 3.5, shadcn-vue on Reka UI and Tailwind CSS 4.3. Last reviewed October 23, 2026. Plain Markdown, no front matter: each agent's is below.

Download

nuxt-shadcn-vue.md

# Nuxt + shadcn-vue

You are working in a Nuxt 4 project with Vue 3.5, TypeScript, shadcn-vue components built on Reka UI, and Tailwind CSS v4. Write code for the versions in `package.json`, not the ones in your training data: Nuxt 4 moved the app into `app/`, Vue 3.5 changed how props and refs are written, and Tailwind v4 has no JavaScript config.

## Before you write code

- Read `package.json` for `nuxt`, `vue`, `reka-ui` (or `@ark-ui/vue`) and `tailwindcss`, and `nuxt.config.ts` for `modules` (`shadcn-nuxt`, `@nuxtjs/color-mode`), `runtimeConfig`, `css` and `vite.plugins`.
- Read `components.json`: its `aliases` say where UI components, `cn()` and composables live, and `tailwind.css` names the stylesheet that holds the tokens.
- Look in the UI folder before adding a component: it may exist, with local changes to keep.
- Auto-imports are generated into `.nuxt/`. After adding a composable, component or server route, run `npx nuxi prepare` (or keep `nuxi dev` running) so types resolve. Never edit `.nuxt/`.

## Project shape

- App code lives in `app/`: `app.vue`, `error.vue`, `pages/`, `layouts/`, `components/`, `composables/`, `middleware/`, `plugins/`, `utils/`, `assets/`. Server code lives in `server/` at the root, code used by both sides in `shared/` (`shared/utils` and `shared/types` are auto-imported on both), static files in `public/`.
- `~` and `@` resolve to `app/`, `~~` to the project root. Never import from `server/` in app code.
- Vue APIs, Nuxt composables and everything in `components/`, `composables/` and `utils/` are auto-imported; don't add imports for them. Import packages (`reka-ui`, `zod`) explicitly.
- Use `import.meta.client` and `import.meta.server` to branch by environment, `<ClientOnly>` or `onMounted` for browser-only APIs.

## Vue 3.5 components

- Write every component as `<script setup lang="ts">`. No Options API, no `this`, no `defineComponent({ props })` in single-file components.
- Destructure props with defaults; they stay reactive. Watch a destructured prop through a getter.

```vue
<script setup lang="ts">
const { size = "md", disabled = false } = defineProps<{
  size?: "sm" | "md" | "lg";
  disabled?: boolean;
}>();
const emit = defineEmits<{ select: [id: string] }>();
const open = defineModel<boolean>("open", { default: false });
watch(() => size, (next) => console.log(next));
</script>
```

- Two-way binding is `defineModel`, never a hand-written prop plus `update:` emit pair. Type emits with named tuples as above.
- Template refs: `const input = useTemplateRef<HTMLInputElement>("input")` with `ref="input"`. Ids that must match between server and client (label `for`, `aria-describedby`): `useId()`, never `Math.random()`.
- Type slots with `defineSlots<{ default(): any; row(props: { item: Item }): any }>()`.
- Cancel stale async work in a watcher with `onWatcherCleanup(() => controller.abort())`.
- `computed` for derived values, never a watcher that writes another ref. `shallowRef` for large objects you replace whole.
- Defer hydration of heavy below-the-fold components: prefix the name with `Lazy` and add `hydrate-on-visible` or `hydrate-on-idle`.

## Data and state

- Fetch page data with `useFetch` or `useAsyncData` at the top level of `<script setup>` (or in a composable called there). It runs on the server, is serialized into the payload and is not fetched again on hydration.
- In event handlers (submit, click), call `$fetch`, then `refresh()` or `refreshNuxtData(key)`. Never call `useFetch` inside a handler, and never use bare `$fetch` in setup for SSR data: it runs twice.
- Give `useAsyncData` an explicit key unique to the data: `` useAsyncData(`post-${id}`, () => $fetch(`/api/posts/${id}`)) ``. Calls with the same key share `data`, `error` and `status` across components, so one key must always mean the same data and options.
- Pass refs or getters in `query` (or a computed URL) so `useFetch` refetches when they change.
- In Nuxt 4, `data` is a shallow ref that starts as `undefined`, and `error` too. Branch on `status` (`"idle" | "pending" | "success" | "error"`), replace `data.value` rather than mutating deep fields, and use `pick` or `transform` so the payload carries only what the page renders.
- During SSR, `useFetch` forwards the user's cookies to your API; any other server-side call that needs them goes through `useRequestFetch()`.
- Shared state is `useState("cart", () => [])`. Never declare a `ref` at module scope in a composable: on the server it is shared by every request. Use Pinia only if the project already does.

## Server routes

- One file per method: `server/api/posts.get.ts`, `server/api/posts.post.ts`, `server/api/posts/[id].patch.ts`. Helpers in `server/utils/` are auto-imported in server code.
- Validate every body, query and param with a schema; h3 turns a thrown parse error into a 400.

```ts
// server/api/posts.post.ts
import { z } from "zod";

const CreatePost = z.object({
  title: z.string().trim().min(1).max(200),
  body: z.string().max(20_000),
});

export default defineEventHandler(async (event) => {
  const user = await requireUser(event); // your helper in server/utils
  const input = await readValidatedBody(event, CreatePost.parse);
  return createPost(user.id, input);
});
```

- Use `getValidatedQuery(event, Schema.parse)` and `getValidatedRouterParams(event, Schema.parse)` for the rest. Put shared schemas in `shared/` so forms and handlers use the same one.
- Throw `createError({ statusCode: 404, statusMessage: "Post not found" })` for expected failures; never return a 200 with an error field.
- Every handler that reads or writes private data checks the session itself. `server/middleware/` can attach the user to `event.context` on every request; route middleware in `app/middleware/` only guards navigation.
- Secrets come from `useRuntimeConfig(event)`: declare `runtimeConfig: { stripeSecret: "", public: { siteUrl: "" } }` in `nuxt.config.ts` and set them at runtime with `NUXT_STRIPE_SECRET` and `NUXT_PUBLIC_SITE_URL`. Only `public` reaches the browser. Don't read `process.env` in app code.

## Routing, SEO and errors

- Pages map to files: `app/pages/posts/[id].vue` reads `useRoute().params.id`. Set page options with `definePageMeta({ layout: "dashboard", middleware: ["auth"] })`; it is a compiler macro, so values must be static.
- Route middleware (`app/middleware/auth.ts`, `*.global.ts` for every route) is `defineNuxtRouteMiddleware((to) => { … })` that returns `navigateTo("/login")` or `abortNavigation()`. Never call `router.push` inside it.
- Link with `<NuxtLink to="/posts">`; navigate in code with `await navigateTo(…)`.
- Set titles and social tags with `useSeoMeta({ title, description, ogTitle, ogImage })`, links and scripts with `useHead`.
- Throw `createError({ statusCode: 404, statusMessage: "Not found", fatal: true })` from a page to show `app/error.vue` (on the client, only `fatal` errors take over the page). `error.vue` receives an `error` prop of type `NuxtError` and leaves with `clearError({ redirect: "/" })`. Wrap a risky widget in `<NuxtErrorBoundary>` instead of failing the page.

## shadcn-vue components

- Add components with the CLI, `npx shadcn-vue@latest add dialog`, never from memory. It writes source files into the UI folder (`ui/dialog/` with an `index.ts` and one `.vue` per part) that you own and edit.
- With the `shadcn-nuxt` module the UI components are auto-imported under their names (`<Button>`, `<DialogContent>`), with the prefix set in its `shadcn.prefix` option. Without it, import from the folder: `import { Button } from "@/components/ui/button"`.
- Variants live in the folder's `index.ts` as `cva` definitions (`buttonVariants`). Add a variant there instead of repeating long class lists. Merge classes with `cn()` from the utils alias (clsx plus tailwind-merge), passing the caller's `class` last.
- Reka UI renders the parts. `as-child` makes a part render its single child element instead of its own:

```vue
<Dialog v-model:open="open">
  <DialogTrigger as-child>
    <Button variant="outline">Edit profile</Button>
  </DialogTrigger>
  <DialogContent>
    <DialogHeader>
      <DialogTitle>Edit profile</DialogTitle>
      <DialogDescription>Changes save when you press Save.</DialogDescription>
    </DialogHeader>
  </DialogContent>
</Dialog>
```

- Control state with `v-model` on the root (`v-model:open`, `v-model` on Select). Every dialog has a `DialogTitle`, every input a label wired with `useId()`.
- When you wrap a Reka part yourself, copy the pattern of the generated files: forward props and emits with `useForwardPropsEmits`, and drop `class` before forwarding so `cn()` can merge it.
- Style open, checked and disabled states with the data attributes the parts set (`data-[state=open]:`, `data-disabled:`), not with extra refs.

## Tailwind CSS v4 and theming

- Tailwind runs through `@tailwindcss/vite`: `vite: { plugins: [tailwindcss()] }` in `nuxt.config.ts`, and the stylesheet (`@import "tailwindcss";`) listed in `css: ["~/assets/css/main.css"]`. No `tailwind.config.js`, no `@tailwind` directives, no `@nuxtjs/tailwindcss` module next to it.
- Colors are CSS variables in OKLCH: `:root { --background: oklch(1 0 0); }`, overridden in `.dark { … }`, mapped to utilities in `@theme inline { --color-background: var(--background); }`. Components use `bg-background`, `text-muted-foreground`, `border-input`; never raw palette colors or `hsl(var(--x))`.
- Dark mode is a class: `@custom-variant dark (&:is(.dark *));` in the stylesheet, and `@nuxtjs/color-mode` with `colorMode: { classSuffix: "" }` so it sets `.dark`. Switch with `useColorMode().preference = "dark"`. The resolved mode is unknown on the server, so render mode-dependent UI inside `<ClientOnly>`.
- An SFC `<style>` that uses `@apply` or theme variables needs `@reference` with the relative path to the main stylesheet at its top (`@reference "../assets/css/main.css";`). Prefer utilities in the template.

## If the project uses Ark UI

When `package.json` has `@ark-ui/vue` instead of `reka-ui`, the components wrap Ark parts: import them per component (`import { Dialog } from "@ark-ui/vue/dialog"`), compose `Dialog.Root`, `Dialog.Trigger`, `Dialog.Content`, use `as-child` the same way, bind `v-model:open` or `v-model` on the root, and read change details from the event payload (`@value-change="(details) => …"`). Never mix Reka and Ark parts in one component.

## Do not

- Do not write Nuxt 2 or early Nuxt 3 code: `asyncData` or `fetch()` hooks, `@nuxtjs/axios`, `process.client`, a root-level `pages/` folder in a Nuxt 4 layout.
- Do not import from `radix-vue` (renamed to `reka-ui`) or wrap destructured props in `withDefaults`.
- Do not keep request data in module-level refs, call `useFetch` from handlers, or trust route middleware to protect an API route.
- Do not add `tailwind.config.js`, `hsl(var(--x))` tokens or hard-coded colors, or install a component package instead of running the CLI.

## Before you finish

- Run `npx nuxi typecheck` (or the project's typecheck script) and the linter, and fix what they report.
- Load every page you touched with a hard refresh: data is in the server HTML, and the console shows no hydration mismatch.
- Call each new server route without a session and with bad input: expect 401 or 400, never a 500 or private data.
- Check the UI in light and dark mode, and that dialogs and menus trap focus and close with Escape.

11,428 characters, 1,638 words. Under Windsurf's 12,000-character limit for a rule, as every prompt here is.

Use it in your agent

The same rules, where your agent reads them. Each file has the front matter its agent needs, set to apply on every request.

Claude Code

A rule it loads every session

  1. Save it as .claude/rules/nuxt-shadcn-vue.md in your project, front matter included, or run the install command.
  2. Claude Code loads every file in .claude/rules with your CLAUDE.md when a session starts.
  3. Prefer one file? Paste it into CLAUDE.md under its own heading instead.

.claude/rules/nuxt-shadcn-vue.md

# Nuxt + shadcn-vueYou are working in a Nuxt 4 project with Vue 3.5, TypeScript, shadcn-vue components built on Reka UI, and Tailwind CSS v4. Write code for the versions in `package.json`, not the ones in your training data: Nuxt 4 moved the app into `app/`, Vue 3.5 changed how props and refs are written, and Tailwind v4 has no JavaScript config.## Before you write code- Read `package.json` for `nuxt`, `vue`, `reka-ui` (or `@ark-ui/vue`) and `tailwindcss`, and `nuxt.config.ts` for `modules` (`shadcn-nuxt`, `@nuxtjs/color-mode`), `runtimeConfig`, `css` and `vite.plugins`.- Read `components.json`: its `aliases` say where UI components, `cn()` and composables live, and `tailwind.css` names the stylesheet that holds the tokens.- Look in the UI folder before adding a component: it may exist, with local changes to keep.- Auto-imports are generated into `.nuxt/`. After adding a composable, component or server route, run `npx nuxi prepare` (or keep `nuxi dev` running) so types resolve. Never edit `.nuxt/`.## Project shape…

Or let the shadcn CLI write this file: sign in (free) for your command.

Cursor

A project rule with front matter

  1. Save it as .cursor/rules/nuxt-shadcn-vue.mdc, front matter included, or run the install command.
  2. alwaysApply: true adds it to every Agent request in this project.
  3. Check it under Cursor Settings, Rules, then commit the folder for your team.

.cursor/rules/nuxt-shadcn-vue.mdc

---description: "Nuxt 4 and Vue 3.5 conventions with shadcn-vue and Tailwind CSS v4: the app/ layout, data fetching, server routes, components and theming."alwaysApply: true---# Nuxt + shadcn-vueYou are working in a Nuxt 4 project with Vue 3.5, TypeScript, shadcn-vue components built on Reka UI, and Tailwind CSS v4. Write code for the versions in `package.json`, not the ones in your training data: Nuxt 4 moved the app into `app/`, Vue 3.5 changed how props and refs are written, and Tailwind v4 has no JavaScript config.## Before you write code- Read `package.json` for `nuxt`, `vue`, `reka-ui` (or `@ark-ui/vue`) and `tailwindcss`, and `nuxt.config.ts` for `modules` (`shadcn-nuxt`, `@nuxtjs/color-mode`), `runtimeConfig`, `css` and `vite.plugins`.- Read `components.json`: its `aliases` say where UI components, `cn()` and composables live, and `tailwind.css` names the stylesheet that holds the tokens.- Look in the UI folder before adding a component: it may exist, with local changes to keep.- Auto-imports are generated into `.nuxt/`. After adding a composable, component or server route, run `npx nuxi prepare` (or keep `nuxi dev` running) so types resolve. Never edit `.nuxt/`.## Project shape…

Or let the shadcn CLI write this file: sign in (free) for your command.

Windsurf

A workspace rule for Cascade

  1. Save it as .windsurf/rules/nuxt-shadcn-vue.md, or run the install command.
  2. trigger: always_on applies it to every Cascade request in this workspace.
  3. Windsurf reads up to 12,000 characters per rule; this one is 11,428.

.windsurf/rules/nuxt-shadcn-vue.md

---trigger: always_on---# Nuxt + shadcn-vueYou are working in a Nuxt 4 project with Vue 3.5, TypeScript, shadcn-vue components built on Reka UI, and Tailwind CSS v4. Write code for the versions in `package.json`, not the ones in your training data: Nuxt 4 moved the app into `app/`, Vue 3.5 changed how props and refs are written, and Tailwind v4 has no JavaScript config.## Before you write code- Read `package.json` for `nuxt`, `vue`, `reka-ui` (or `@ark-ui/vue`) and `tailwindcss`, and `nuxt.config.ts` for `modules` (`shadcn-nuxt`, `@nuxtjs/color-mode`), `runtimeConfig`, `css` and `vite.plugins`.- Read `components.json`: its `aliases` say where UI components, `cn()` and composables live, and `tailwind.css` names the stylesheet that holds the tokens.- Look in the UI folder before adding a component: it may exist, with local changes to keep.- Auto-imports are generated into `.nuxt/`. After adding a composable, component or server route, run `npx nuxi prepare` (or keep `nuxi dev` running) so types resolve. Never edit `.nuxt/`.## Project shape…

Or let the shadcn CLI write this file: sign in (free) for your command.

GitHub Copilot

Instructions for the repository

  1. Save it as .github/instructions/nuxt-shadcn-vue.instructions.md, or run the install command.
  2. applyTo: "**" applies it to every request in this repository.
  3. Or paste it into .github/copilot-instructions.md, which Copilot reads for every request.

.github/instructions/nuxt-shadcn-vue.instructions.md

---description: "Nuxt 4 and Vue 3.5 conventions with shadcn-vue and Tailwind CSS v4: the app/ layout, data fetching, server routes, components and theming."applyTo: "**"---# Nuxt + shadcn-vueYou are working in a Nuxt 4 project with Vue 3.5, TypeScript, shadcn-vue components built on Reka UI, and Tailwind CSS v4. Write code for the versions in `package.json`, not the ones in your training data: Nuxt 4 moved the app into `app/`, Vue 3.5 changed how props and refs are written, and Tailwind v4 has no JavaScript config.## Before you write code- Read `package.json` for `nuxt`, `vue`, `reka-ui` (or `@ark-ui/vue`) and `tailwindcss`, and `nuxt.config.ts` for `modules` (`shadcn-nuxt`, `@nuxtjs/color-mode`), `runtimeConfig`, `css` and `vite.plugins`.- Read `components.json`: its `aliases` say where UI components, `cn()` and composables live, and `tailwind.css` names the stylesheet that holds the tokens.- Look in the UI folder before adding a component: it may exist, with local changes to keep.- Auto-imports are generated into `.nuxt/`. After adding a composable, component or server route, run `npx nuxi prepare` (or keep `nuxi dev` running) so types resolve. Never edit `.nuxt/`.## Project shape…

Or let the shadcn CLI write this file: sign in (free) for your command.

Codex & AGENTS.md

The file most agents read

  1. Paste it into AGENTS.md at your project's root, under its own heading.
  2. Codex reads AGENTS.md before it starts work, and so do Cursor, Zed, Jules, Amp and Copilot's coding agent.
  3. A folder can have its own AGENTS.md: the one closest to the file being edited wins.

AGENTS.md

## Nuxt + shadcn-vueYou are working in a Nuxt 4 project with Vue 3.5, TypeScript, shadcn-vue components built on Reka UI, and Tailwind CSS v4. Write code for the versions in `package.json`, not the ones in your training data: Nuxt 4 moved the app into `app/`, Vue 3.5 changed how props and refs are written, and Tailwind v4 has no JavaScript config.### Before you write code- Read `package.json` for `nuxt`, `vue`, `reka-ui` (or `@ark-ui/vue`) and `tailwindcss`, and `nuxt.config.ts` for `modules` (`shadcn-nuxt`, `@nuxtjs/color-mode`), `runtimeConfig`, `css` and `vite.plugins`.- Read `components.json`: its `aliases` say where UI components, `cn()` and composables live, and `tailwind.css` names the stylesheet that holds the tokens.- Look in the UI folder before adding a component: it may exist, with local changes to keep.- Auto-imports are generated into `.nuxt/`. After adding a composable, component or server route, run `npx nuxi prepare` (or keep `nuxi dev` running) so types resolve. Never edit `.nuxt/`.### Project shape…

AGENTS.md is shared with your own instructions, so paste the rules in: the copy sets their headings a level down.

Rules teach it. The MCP equips it. With Pro, your agent installs every component, block and icon it needs, from a prompt.

Get Pro

Pricing

Give your agent the parts, with Pro

The prompts are free. Every Pro plan adds the MCP server, so your agent installs the components, blocks and icons it writes code around.

4.9/5 out of 5 · 150+ reviewsfrom 150+ founders and developers.

  • Secure checkout via Stripe
  • Code lives in your repo
  • Keep what you've shipped if you cancel
  • License terms

Nuxt + shadcn-vue prompt FAQ

What the Nuxt + shadcn-vue prompt covers and how to use it in your agent.

Does the shadcn-vue prompt use Reka UI or Radix Vue?

Reka UI, the current name of Radix Vue, which shadcn-vue is built on. The prompt keeps the agent from importing radix-vue, shows as-child and v-model:open on the parts, and has it wrap parts the way the generated components do.

Can I use the Nuxt prompt with Ark UI for Vue instead of Reka UI?

Yes. When package.json has @ark-ui/vue, the prompt has the agent import parts per component from @ark-ui/vue, compose Dialog.Root and its parts, and use the same as-child and v-model patterns. It also tells the agent never to mix Ark and Reka parts in one component.

How does the agent add a shadcn-vue component in Nuxt?

It runs npx shadcn-vue@latest add with the component's name, which writes the source into your UI folder, then edits those files. It reads components.json for the aliases first and looks for an existing copy before adding one, so your local changes survive.

How is dark mode set up with shadcn-vue in Nuxt?

With @nuxtjs/color-mode set to an empty classSuffix so it toggles a dark class on the html element, and a dark custom variant in the Tailwind stylesheet. Tokens are OKLCH variables overridden under .dark, and mode-dependent UI renders inside ClientOnly to avoid hydration mismatches.

4.9 out of 5 from 150+ developers

Rules teach it. The MCP equips it.

Every component, block and icon, installed by your AI agent on your stack. The prompts stay free.

Get Pro
  • Cancel anytime
  • Keep what you've shipped