You picked Vue for its gentle learning curve and now you need to ship a complex SPA with TypeScript, Pinia stores, and composables that your team can actually maintain. These recipes give you production-ready prompts that produce idiomatic Vue 3 code — not Options API leftovers dressed up as Composition API.
Prompts for generating Vue 3 components with <script setup> and TypeScript
Pinia store patterns with proper typing and persistence
Composable recipes for reusable logic extraction
Testing prompts for Vue Test Utils and Vitest
Scenario: You need a new feature component that follows your team’s conventions — <script setup>, typed props with defaults, typed emits, and scoped styles.
Tip
Create a Vue 3 component at src/components/UserCard.vue using <script setup lang="ts">. Define props with defineProps<{ user: User; showActions?: boolean; compact?: boolean }>() using withDefaults to set showActions to true and compact to false. Define emits with defineEmits<{ edit: [userId: string]; delete: [userId: string]; select: [user: User] }>(). Include a User interface import from @/types. Use Tailwind CSS in a template that shows the user avatar, name, email, and role badge. When showActions is true, show edit and delete icon buttons. When compact is true, hide the email and role. Add a <style scoped> block only for animations. Write a companion unit test using Vue Test Utils and Vitest that mounts the component with mock data and verifies each prop behavior and emit.
Expected output: A .vue SFC with typed <script setup>, a responsive template, and a .test.ts file.
Scenario: You need a cart store that persists across page refreshes, supports undo, and integrates with Vue DevTools.
Tip
Create a Pinia store in src/stores/cart.ts using the setup syntax (composable-style) for better TypeScript inference. State: items array (CartItem[]), couponCode string or null, isCheckingOut boolean. Getters: totalItems (sum of quantities), subtotal (sum of price times quantity), discount (computed from coupon), total (subtotal minus discount). Actions: addItem (increment quantity if exists, otherwise push), removeItem, updateQuantity (with min 1 validation), applyCoupon (validate against an API endpoint), clearCart, checkout (async, sets isCheckingOut, calls API, clears cart on success). Use pinia-plugin-persistedstate to sync to localStorage. Implement undo for the last cart modification using a history stack. Export typed composable. Write tests for every action including the undo behavior.
Expected output: Store file, CartItem type definition, and test file covering all actions and undo.
Scenario: Multiple components duplicate the same fetch-with-loading-state pattern, debounced search, and permission checks. Extract them into composables.
Tip
Create these composables in src/composables/, each in its own file with full TypeScript generics:
useFetch<T>(url: MaybeRef<string>, options?) — reactive URL watching, auto-refetch on URL change, returns { data: Ref<T | null>, error: Ref<Error | null>, isLoading: Ref<boolean>, execute, abort }. Handles AbortController cleanup on component unmount.
useSearch<T>(searchFn: (query: string) => Promise<T[]>, delay?: number) — debounced search with 300ms default delay, returns { query, results, isSearching }. Cancels pending searches when query changes.
usePermission(permission: string) — checks against the auth store, returns { hasPermission: ComputedRef<boolean>, isLoading }. Caches results per permission string.
usePagination<T>(fetchFn: (page, pageSize) => Promise<PaginatedResponse<T>>) — returns { items, currentPage, totalPages, pageSize, isLoading, goToPage, nextPage, prevPage }.
Export all from src/composables/index.ts. Write tests for each using Vue Test Utils mount with a test component that consumes the composable.
Expected output: Four composable files, four test files, type definitions, and a barrel export.
Scenario: You need a checkout form with 4 steps where each step validates before advancing and the user can navigate back without losing data.
Tip
Build a multi-step form system in src/components/checkout/. Create: (1) MultiStepForm.vue — manages current step index, validates the current step before advancing, exposes navigation via provide/inject so child steps can call next/prev/goTo. (2) FormStep.vue — a generic step wrapper that receives a Zod schema and validates its fields on “Next”. (3) Individual step components: ShippingStep.vue, BillingStep.vue, PaymentStep.vue, ReviewStep.vue. (4) Use VeeValidate with Zod adapter for per-step validation. (5) A progress indicator showing completed, current, and upcoming steps. (6) A useCheckoutForm() composable that aggregates all step data into a single typed object and submits to the API. Persist partially completed form data to sessionStorage so the user does not lose progress on accidental page refresh. Test step navigation, validation blocking, and data persistence.
Expected output: Six component files, one composable, Zod schemas for each step, and integration tests.
Scenario: Your admin panel needs to display 50,000 log entries without freezing the browser.
Tip
Create a VirtualDataTable.vue component that handles large datasets using vue-virtual-scroller. Features: (1) Generic typed columns via a columns prop with ColumnDef<T> type. (2) Sortable headers — click to cycle through asc/desc/none. (3) Column filters rendered below each header. (4) Fixed header that stays visible while scrolling. (5) Row selection with Shift+click for range selection. (6) Resizable columns with drag handles. (7) Export selected rows to CSV. The component should accept a data prop (reactive array) and a loading prop. When loading, show skeleton rows. Internally, compute filtered and sorted data as computed properties so they update reactively. Keep the virtual scroller rendering only visible rows. Test with a 50,000 row dataset to verify no frame drops.
Expected output: VirtualDataTable component, column type definitions, CSV export utility, and a performance test.
Scenario: You need a theme system that lets any component access and toggle between light, dark, and system themes without prop drilling.
Tip
Create a theme system using Vue’s provide/inject with TypeScript safety. (1) Define ThemeContext interface with theme (light/dark/system), resolvedTheme (light/dark, computed from system preference when theme is ‘system’), toggleTheme(), setTheme(theme). (2) Create ThemeProvider.vue that provides the context, reads initial theme from localStorage, watches system preference via matchMedia('(prefers-color-scheme: dark)'), applies the dark class to document.documentElement. (3) Create useTheme() composable that injects the context with a runtime error if used outside the provider. (4) Create ThemeToggle.vue component with three states (sun icon, moon icon, monitor icon). (5) Ensure SSR compatibility — do not access window or document during server rendering. Use onMounted for browser-only code. Test theme switching, system preference detection, and persistence.
Expected output: Provider component, composable, toggle component, type definitions, and tests.
Scenario: Your Vue Router setup loads all page components eagerly, bloating the initial bundle.
Tip
Refactor src/router/index.ts to use lazy-loaded routes. For each route: (1) Replace static imports with () => import('../views/PageName.vue'). (2) Add a per-route meta.skeleton property that references a lightweight skeleton component matching that page’s layout. (3) Create a RouterViewWithSuspense.vue component that wraps <RouterView> in <Suspense>, renders the route’s skeleton as the fallback, and catches chunk-load errors with a retry UI. (4) Add route prefetching: create a usePrefetch() composable that preloads route chunks on mouseenter of navigation links after a 150ms hover delay. (5) Group related routes into named chunks. Verify each route loads as a separate chunk in the production build.
Expected output: Updated router config, RouterViewWithSuspense component, usePrefetch composable, and skeleton components.
Scenario: Your app needs a global toast notification system that any component can trigger without prop drilling.
Tip
Create a toast notification system in src/components/toast/. (1) ToastProvider.vue — renders a fixed container in the bottom-right corner, manages a reactive queue of toast objects, provides useToast() context. (2) Toast.vue — individual toast with icon (success/error/warning/info), title, message, optional action button, auto-dismiss timer (default 5 seconds, configurable), dismiss on click, and slide-in/slide-out transitions using <Transition>. (3) useToast() composable — returns { success(msg, options?), error(msg, options?), warning(msg, options?), info(msg, options?), dismiss(id), clearAll() }. Options include duration, action label, action callback, and persistent (no auto-dismiss). (4) Stack toasts vertically with a max of 5 visible. When exceeding 5, collapse older toasts into a “+N more” indicator. Test toast creation, auto-dismiss timing, manual dismiss, and the action callback.
Expected output: ToastProvider, Toast component, useToast composable, and tests.
Scenario: Your SPA needs to support English, Spanish, and Japanese with lazy-loaded locale files and pluralization.
Tip
Set up Vue I18n v9 with lazy-loaded locales. (1) Create src/i18n/index.ts that initializes vue-i18n with en as default, legacy mode disabled. (2) Create locale files at src/i18n/locales/en.json, es.json, ja.json with at least 30 keys covering common UI patterns: navigation, forms, notifications, dates, and pluralization examples. (3) Implement lazy loading: create loadLocale(locale) that dynamically imports the JSON file and calls i18n.global.setLocaleMessage(). (4) Create a LocaleSwitcher.vue component showing a dropdown that switches locale on selection, persisting to localStorage. (5) Create a useI18nRoute() composable that prefixes all routes with the current locale and redirects when locale changes. (6) Handle date and number formatting using vue-i18n’s $d() and $n(). Test that switching locales updates all visible text without a page reload.
Expected output: I18n config, three locale files, locale switcher, routing composable, and integration tests.
Scenario: Your app feels static. You need reusable transition components for page changes, list animations, and micro-interactions.
Tip
Create an animation library in src/components/transitions/. (1) FadeTransition.vue — wraps <Transition> with CSS fade in/out, configurable duration prop. (2) SlideTransition.vue — slide from left, right, top, or bottom via a direction prop. (3) ScaleTransition.vue — scale from 0.95 to 1 with opacity. (4) ListTransition.vue — wraps <TransitionGroup> for staggered list animations where each item enters 50ms after the previous one. (5) PageTransition.vue — for route transitions, detects navigation direction (forward/back) and slides accordingly. (6) CollapseTransition.vue — animates height from 0 to auto for accordion/expandable sections. Each component should use CSS transitions for performance, accept duration and easing props, and work with v-if and v-show. Create a demo page showing all transitions.
Expected output: Six transition components, a demo page, and type definitions for all props.
Scenario: Your dashboard needs real-time updates from a WebSocket that handles disconnections gracefully.
Tip
Create a useWebSocket(url, options?) composable in src/composables/useWebSocket.ts. Features: (1) Reactive connection state: status ref as ‘connecting’ | ‘open’ | ‘closed’ | ‘error’. (2) Auto-reconnect with exponential backoff (1s, 2s, 4s, 8s, max 30s), configurable max retries. (3) Heartbeat ping/pong every 30 seconds to detect dead connections. (4) Message queuing: messages sent while disconnected are queued and flushed on reconnect. (5) Typed message handling: accept a generic onMessage<T>(type, handler) that filters messages by a type field. (6) Authentication: send an auth token on connect and reconnect. (7) Cleanup on component unmount. (8) send(type, data) method that JSON-serializes and sends. Create a useRealtimeTodos() composable that wraps useWebSocket for todo updates, syncing with the Pinia store. Test reconnection behavior and message queuing.
Expected output: useWebSocket composable, useRealtimeTodos composable, and tests for reconnection logic.
Scenario: Your component library lacks documentation. New team members cannot discover or understand existing components.
Tip
Set up Storybook 8 for this Vue 3 project with Vite builder. (1) Install and configure @storybook/vue3-vite with autodocs. (2) Create stories for 5 existing components: Button, Input, Modal, Card, and DataTable. Each story file should include: a default export with argTypes matching the component props, a Primary story showing default usage, a gallery story showing all variants in a grid, an interactive story where every prop is controllable via Storybook controls, and a documentation page using MDX. (3) Configure Storybook to resolve the same path aliases as the main Vite config. (4) Add @storybook/addon-a11y and show accessibility audit results for each story. (5) Add @storybook/test for interaction testing within stories. Write an interaction test for the Modal story that clicks open, verifies focus trap, and closes with Escape.
Expected output: Storybook config, 5 story files with variants and docs, accessibility addon, and one interaction test.