# Frontend Standards (Vue.js / Nuxt 3) > **MAINTENANCE:** This file is indexed in `/skills/shared-patterns/standards-coverage-table.md`. > When adding/removing `## ` sections, follow FOUR-FILE UPDATE RULE in CLAUDE.md: (1) edit standards file, (2) update TOC, (3) update standards-coverage-table.md, (4) update agent file. This file defines the specific standards for frontend development using Vue 3 and Nuxt 3. > **Reference**: Always consult `docs/PROJECT_RULES.md` for common project standards. --- ## Table of Contents | # | Section | Description | |---|---------|-------------| | 1 | [Framework](#framework) | Vue 3, Nuxt 3 (version policy) | | 2 | [Libraries & Tools](#libraries--tools) | Core, state, forms, UI, styling, testing | | 3 | [State Management Patterns](#state-management-patterns) | Pinia stores, `useAsyncData`, `useFetch` | | 4 | [Form Patterns](#form-patterns) | VeeValidate + Zod | | 5 | [Styling Standards](#styling-standards) | TailwindCSS, CSS variables | | 6 | [Typography Standards](#typography-standards) | Font selection and pairing | | 7 | [Animation Standards](#animation-standards) | CSS transitions, ``, GSAP | | 8 | [Component Patterns](#component-patterns) | Composables, `` | | 9 | [File Organization](#file-organization-mandatory) | File-level single responsibility | | 10 | [Accessibility](#accessibility) | WCAG 2.1 AA compliance | | 11 | [Performance](#performance) | Nuxt lazy loading, image optimization | | 12 | [Directory Structure](#directory-structure) | Nuxt 3 file-based routing layout | | 13 | [Forbidden Patterns](#forbidden-patterns) | Anti-patterns to avoid | | 14 | [Standards Compliance Categories](#standards-compliance-categories) | Categories for bee:dev-refactor | | 15 | [Form Field Abstraction Layer](#form-field-abstraction-layer) | **HARD GATE:** Field wrappers, dual-mode (sindarian-vue vs vanilla) | | 16 | [Plugin Composition Pattern](#plugin-composition-pattern) | Nuxt plugins, app-level provide/inject | | 17 | [Composable Patterns](#composable-patterns) | **HARD GATE:** usePagination, useCursorPagination, useSheet, useStepper, useDebounce | | 18 | [Fetcher Utilities Pattern](#fetcher-utilities-pattern) | $fetch wrappers, useFetch, useAsyncData | | 19 | [Client-Side Error Handling](#client-side-error-handling) | **HARD GATE:** NuxtErrorBoundary, error.vue, API error helpers, toast integration | | 20 | [Data Table Pattern](#data-table-pattern) | TanStack Table (Vue adapter), server-side pagination, column definitions | **Meta-sections (not checked by agents):** - [Checklist](#checklist) - Self-verification before submitting code --- ## Framework - Vue 3 with Composition API (` ``` ### Error Boundaries with NuxtErrorBoundary ```html ``` ### Global error.vue (Nuxt route-level errors) ```html ``` --- ## File Organization (MANDATORY) **Single Responsibility per File:** Each component file MUST represent ONE UI concern. ### Rules | Rule | Description | |------|-------------| | **One component per file** | A file exports ONE primary component (`.vue` file) | | **Max 200 lines per component file** | If longer, extract sub-components or composables | | **Co-locate related files** | Component, composable, types, test in same feature folder | | **Composables in separate files** | Custom composables that exceed 20 lines get their own file | | **Separate data from presentation** | Container (data-fetching) and presentational components split | ### Examples ```html ``` ```html ``` ```typescript // composables/useUsers.ts (~60 lines) — Data fetching composable export function useUsers(filters?: Ref) { const { data, pending, error } = useAsyncData( () => `users-${JSON.stringify(filters?.value ?? {})}`, () => $fetch<{ data: User[]; total: number }>('/api/users', { params: filters?.value, }), { watch: filters ? [filters] : undefined } ) return { users: data, pending, error } } // composables/useUserFilters.ts (~40 lines) — Filter state composable export function useUserFilters() { const filters = ref({}) function updateFilter(key: string, value: unknown) { filters.value = { ...filters.value, [key]: value } } function resetFilters() { filters.value = {} } return { filters, updateFilter, resetFilters } } ``` ### Signs a File Needs Splitting | Sign | Action | |------|--------| | Component file exceeds 200 lines | Extract sub-components or composables | | More than 3 `ref`/`watch` at top-level | Extract to composable | | Template exceeds 100 lines | Extract child components | | File mixes data fetching and presentation | Split container and presentational components | | Multiple `useAsyncData` calls in one file | Extract to dedicated composable files | | Component accepts more than 5 props | Consider composition or slot pattern | --- ## Accessibility ### Required Practices ```html
``` ### Focus Management ```html ``` --- ## Performance ### Lazy Loading with defineAsyncComponent ```typescript // Lazy load heavy components const Dashboard = defineAsyncComponent(() => import('~/components/Dashboard.vue')) const Analytics = defineAsyncComponent(() => import('~/components/Analytics.vue')) ``` ```html ``` ### Image Optimization with NuxtImg ```html ``` ### Memoization with computed ```typescript // computed() is automatically cached and only re-evaluates when dependencies change const sortedItems = computed(() => [...items.value].sort((a, b) => b.score - a.score) ) // For expensive renders, use v-memo directive //
  • // Lazy async data const { data } = useLazyAsyncData('heavy-data', () => $fetch('/api/heavy')) ``` ### useLazyFetch for Non-Critical Data ```typescript // Defers loading until after hydration — does not block navigation const { data, pending } = useLazyFetch('/api/recommendations') ``` --- ## Directory Structure ```text / /pages # Nuxt file-based routing index.vue # / /dashboard index.vue # /dashboard /users index.vue # /users [id].vue # /users/:id /layouts # Nuxt layouts default.vue auth.vue /components /ui # Primitive UI components (shadcn-vue / Radix Vue) Button.vue Input.vue Card.vue /features # Feature-specific components /user UserProfile.vue UserList.vue /order OrderForm.vue /composables # Auto-imported composables useUsers.ts useDebounce.ts /stores # Pinia stores (auto-imported) users.ts ui.ts /server # Nuxt server routes /api /users index.get.ts # GET /api/users index.post.ts # POST /api/users [id].get.ts # GET /api/users/:id /plugins # Nuxt plugins toast.client.ts /middleware # Route middleware auth.ts /utils # Utility functions cn.ts api-error.ts /types # TypeScript types user.ts api.ts /public # Static assets /assets # Processed assets (Vite) ``` --- ## Forbidden Patterns **The following patterns are never allowed. Agents MUST refuse to implement these:** ### TypeScript Anti-Patterns | Pattern | Why Forbidden | Correct Alternative | |---------|---------------|---------------------| | `any` type | Defeats TypeScript purpose | Use proper types, `unknown`, or generics | | Type assertions without validation | Runtime errors | Use type guards or Zod parsing | | `// @ts-ignore` or `// @ts-expect-error` | Hides real errors | Fix the type issue properly | | Non-strict mode | Allows unsafe code | Enable `"strict": true` in tsconfig | ### Accessibility Anti-Patterns | Pattern | Why Forbidden | Correct Alternative | |---------|---------------|---------------------| | `
    ` for buttons | Not keyboard accessible | Use `