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.
- The Nuxt 4 layoutapp/ for pages, components and composables, server/ for API routes, shared/ for schemas both sides use, auto-imports left alone.
- Vue 3.5 script setupDestructured props with defaults, defineModel for v-model, useTemplateRef and useId, typed emits and slots.
- Data fetched onceuseFetch and useAsyncData in setup with unique keys, $fetch in handlers, useState instead of module-level refs.
- Server routes that check inputOne file per method, bodies parsed with readValidatedBody and Zod, createError for failures, secrets from useRuntimeConfig.
- shadcn-vue you ownComponents added with the CLI, variants in cva, cn() for classes, as-child and v-model on Reka UI parts.
- 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.
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.
<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.
// 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:
<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
- Save it as
.claude/rules/nuxt-shadcn-vue.mdin your project, front matter included, or run the install command. - Claude Code loads every file in
.claude/ruleswith yourCLAUDE.mdwhen a session starts. - Prefer one file? Paste it into
CLAUDE.mdunder 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
- Save it as
.cursor/rules/nuxt-shadcn-vue.mdc, front matter included, or run the install command. alwaysApply: trueadds it to every Agent request in this project.- 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
- Save it as
.windsurf/rules/nuxt-shadcn-vue.md, or run the install command. trigger: always_onapplies it to every Cascade request in this workspace.- 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
- Save it as
.github/instructions/nuxt-shadcn-vue.instructions.md, or run the install command. applyTo: "**"applies it to every request in this repository.- 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
- Paste it into
AGENTS.mdat your project's root, under its own heading. - Codex reads
AGENTS.mdbefore it starts work, and so do Cursor, Zed, Jules, Amp and Copilot's coding agent. - 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 ProMore prompts
Prompts that pair with this one. Each is short enough to sit beside the others.
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?
Can I use the Nuxt prompt with Ark UI for Vue instead of Reka UI?
How does the agent add a shadcn-vue component in Nuxt?
How is dark mode set up with shadcn-vue in Nuxt?
Does the Nuxt + shadcn-vue prompt work on a Nuxt 3 project?
Why does the Nuxt prompt keep useFetch out of click handlers?
How does the Nuxt + shadcn-vue prompt share state between components?
Where does the agent put secrets in a Nuxt 4 app?
What does the Nuxt + shadcn-vue prompt cover?
Does the Nuxt + shadcn-vue prompt set up Tailwind CSS v4?
How do I add the Nuxt + shadcn-vue prompt to Claude Code?
How do I use the Nuxt + shadcn-vue prompt as a Cursor rule?
Does the Nuxt + shadcn-vue prompt fit in a Windsurf rule?
Can GitHub Copilot and Codex use the Nuxt + shadcn-vue prompt?
Can I edit the Nuxt + shadcn-vue prompt for my project?
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.
- Cancel anytime
- Keep what you've shipped