vue-best-practices

Guide des bonnes pratiques Vue.js 3 couvrant la Composition API, la conception de composants, les patrons de réactivité, le styling utility-first avec Tailwind CSS, l'intégration native de la bibliothèque de composants PrimeVue et l'organisation du code. À utiliser lors de l'écriture, la revue ou le refactoring de code Vue.js pour garantir des patrons idiomatiques et un code maintenable.

dedalus-erp-pas/foundation-skills253 installsMITSynced Aug 26

Works with

Claude CodeCursorCodex CLIGitHub CopilotGemini CLI
---
name: vue-best-practices
description: Guide des bonnes pratiques Vue.js 3 couvrant la Composition API, la conception de composants, les patrons de réactivité, le styling utility-first avec Tailwind CSS, l'intégration native de la bibliothèque de composants PrimeVue et l'organisation du code. À utiliser lors de l'écriture, la revue ou le refactoring de code Vue.js pour garantir des patrons idiomatiques et un code maintenable.
license: MIT
---

# Vue.js Best Practices

Comprehensive best practices guide for Vue.js 3 applications. Contains guidelines across multiple categories to ensure idiomatic, maintainable, and scalable Vue.js code, including Tailwind CSS integration patterns for utility-first styling and PrimeVue component library best practices.

## When to Apply

Reference these guidelines when:
- Writing new Vue components or composables
- Implementing features with Composition API
- Reviewing code for Vue.js patterns compliance
- Refactoring existing Vue.js code
- Setting up component architecture
- Working with Nuxt.js applications
- Styling Vue components with Tailwind CSS utility classes
- Creating design systems with Tailwind and Vue
- Using PrimeVue component library natively
- Customizing PrimeVue theme through design tokens and definePreset()

## Rule Categories

| Category | Focus | Prefix |
|----------|-------|--------|
| Composition API | Proper use of Composition API patterns | `composition-` |
| Component Design | Component structure and organization | `component-` |
| Reactivity | Reactive state management patterns | `reactive-` |
| Props & Events | Component communication patterns | `props-` |
| Template Patterns | Template syntax best practices | `template-` |
| Code Organization | Project and code structure | `organization-` |
| TypeScript | Type-safe Vue.js patterns | `typescript-` |
| Error Handling | Error boundaries and handling | `error-` |
| Tailwind CSS | Utility-first styling patterns | `tailwind-` |
| PrimeVue | Component library integration patterns | `primevue-` |

## Quick Reference

### 1. Composition API Best Practices

- `composition-script-setup` - Always use `<script setup>` for single-file components
- `composition-ref-vs-reactive` - Use `ref()` for primitives, `reactive()` for objects
- `composition-computed-derived` - Use `computed()` for all derived state
- `composition-watch-side-effects` - Use `watch()`/`watchEffect()` only for side effects
- `composition-composables` - Extract reusable logic into composables
- `composition-lifecycle-order` - Place lifecycle hooks after reactive state declarations
- `composition-avoid-this` - Never use `this` in Composition API

### 2. Component Design

- `component-single-responsibility` - One component, one purpose
- `component-naming-convention` - Use PascalCase for components, kebab-case in templates
- `component-small-focused` - Keep components under 200 lines
- `component-presentational-container` - Separate logic from presentation when beneficial
- `component-slots-flexibility` - Use slots for flexible component composition
- `component-expose-minimal` - Only expose what's necessary via `defineExpose()`

### 3. Reactivity Patterns

- `reactive-const-refs` - Always declare refs with `const`
- `reactive-unwrap-template` - Let Vue unwrap refs in templates (no `.value`)
- `reactive-shallow-large-data` - Use `shallowRef()`/`shallowReactive()` for large non-reactive data
- `reactive-readonly-props` - Use `readonly()` to prevent mutations
- `reactive-toRefs-destructure` - Use `toRefs()` when destructuring reactive objects
- `reactive-avoid-mutation` - Prefer immutable updates for complex state

### 4. Props & Events

- `props-define-types` - Always define prop types with `defineProps<T>()`
- `props-required-explicit` - Be explicit about required vs optional props
- `props-default-values` - Provide sensible defaults with `withDefaults()`
- `props-immutable` - Never mutate props directly
- `props-validation` - Use validator functions for complex prop validation
- `events-define-emits` - Always define emits with `defineEmits<T>()`
- `events-naming` - Use kebab-case for event names in templates
- `events-payload-objects` - Pass objects for events with multiple values

### 5. Template Patterns

- `template-v-if-v-show` - Use `v-if` for conditional rendering, `v-show` for toggling
- `template-v-for-key` - Always use unique, stable `:key` with `v-for`
- `template-v-if-v-for` - Never use `v-if` and `v-for` on the same element
- `template-computed-expressions` - Move complex expressions to computed properties
- `template-event-modifiers` - Use event modifiers (`.prevent`, `.stop`) appropriately
- `template-v-bind-shorthand` - Use shorthand syntax (`:` for `v-bind`, `@` for `v-on`)
- `template-v-model-modifiers` - Use v-model modifiers (`.trim`, `.number`, `.lazy`)

### 6. Code Organization

- `organization-feature-folders` - Organize by feature, not by type
- `organization-composables-folder` - Keep composables in dedicated `composables/` folder
- `organization-barrel-exports` - Use index files for clean imports
- `organization-consistent-naming` - Follow consistent naming conventions
- `organization-colocation` - Colocate related files (component, tests, styles)

### 7. TypeScript Integration

- `typescript-generic-components` - Use generics for reusable typed components
- `typescript-prop-types` - Use TypeScript interfaces for prop definitions
- `typescript-emit-types` - Type emit payloads explicitly
- `typescript-ref-typing` - Specify types for refs when not inferred
- `typescript-template-refs` - Type template refs with `ref<InstanceType<typeof Component> | null>(null)`

### 8. Error Handling

- `error-boundaries` - Use `onErrorCaptured()` for component error boundaries
- `error-async-handling` - Handle errors in async operations explicitly
- `error-provide-fallbacks` - Provide fallback UI for error states
- `error-logging` - Log errors appropriately for debugging

### 9. Tailwind CSS

- `tailwind-utility-first` - Apply utility classes directly in templates, avoid custom CSS
- `tailwind-class-order` - Use consistent class ordering (layout → spacing → typography → visual)
- `tailwind-responsive-mobile-first` - Use mobile-first responsive design (`sm:`, `md:`, `lg:`)
- `tailwind-component-extraction` - Extract repeated utility patterns into Vue components
- `tailwind-dynamic-classes` - Use computed properties or helper functions for dynamic classes
- `tailwind-complete-class-strings` - Always use complete class strings, never concatenate
- `tailwind-state-variants` - Use state variants (`hover:`, `focus:`, `active:`) for interactions
- `tailwind-dark-mode` - Use `dark:` prefix for dark mode support
- `tailwind-design-tokens` - Configure design tokens in Tailwind config for consistency
- `tailwind-avoid-apply-overuse` - Limit `@apply` usage; prefer Vue components for abstraction

### 10. PrimeVue

- `primevue-use-natively` - Use PrimeVue components as-is with their documented props and API
- `primevue-design-tokens` - Customize appearance exclusively through design tokens and `definePreset()`
- `primevue-no-pt-overrides` - NEVER use PassThrough (pt) API to restyle components
- `primevue-no-unstyled-mode` - NEVER use unstyled mode to strip and rebuild component styles
- `primevue-no-wrapper-components` - NEVER wrap PrimeVue components just to override their styling
- `primevue-props-api` - Use built-in props (severity, size, outlined, rounded, raised, text) for variants
- `primevue-css-layers` - Configure CSS layer ordering for clean Tailwind coexistence
- `primevue-typed-components` - Leverage PrimeVue's TypeScript support for type safety
- `primevue-accessibility` - Maintain WCAG compliance with proper aria attributes
- `primevue-lazy-loading` - Use async components for large PrimeVue imports

## Key Principles

### Composition API Best Practices

The Composition API is the recommended approach for Vue.js 3. Follow these patterns:

- **Always use `<script setup>`**: More concise, better TypeScript inference, and improved performance
- **Organize code by logical concern**: Group related state, computed properties, and functions together
- **Extract reusable logic to composables**: Follow the `use` prefix convention (e.g., `useAuth`, `useFetch`)
- **Keep setup code readable**: Order: props/emits, reactive state, computed, watchers, methods, lifecycle hooks

### Component Design Principles

Well-designed components are the foundation of maintainable Vue applications:

- **Single Responsibility**: Each component should do one thing well
- **Props Down, Events Up**: Follow unidirectional data flow
- **Prefer Composition over Inheritance**: Use composables and slots for code reuse
- **Keep Components Small**: If a component exceeds 200 lines, consider splitting it

### Reactivity Guidelines

Understanding Vue's reactivity system is crucial:

- **ref vs reactive**: Use `ref()` for primitives and values you'll reassign; use `reactive()` for objects you'll mutate
- **Computed for derived state**: Never store derived state in refs; use `computed()` instead
- **Watch for side effects**: Only use `watch()` for side effects like API calls or localStorage
- **Be mindful of reactivity loss**: Don't destructure reactive objects without `toRefs()`

### Props & Events Patterns

Proper component communication ensures maintainable code:

- **Type your props**: Use TypeScript interfaces with `defineProps<T>()`
- **Validate complex props**: Use validator functions for business logic validation
- **Emit typed events**: Use `defineEmits<T>()` for type-safe event handling
- **Use v-model for two-way binding**: Implement `modelValue` prop and `update:modelValue` emit

## Common Patterns

### Script Setup Structure

**Recommended structure for `<script setup>`:**
```vue
<script setup lang="ts">
// 1. Imports
import { ref, computed, watch, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import type { User } from '@/types'

// 2. Props and Emits
const props = defineProps<{
  userId: string
  initialData?: User
}>()

const emit = defineEmits<{
  submit: [user: User]
  cancel: []
}>()

// 3. Composables
const router = useRouter()
const { user, loading, error } = useUser(props.userId)

// 4. Reactive State
const formData = ref({ name: '', email: '' })
const isEditing = ref(false)

// 5. Computed Properties
const isValid = computed(() => {
  return formData.value.name.length > 0 && formData.value.email.includes('@')
})

// 6. Watchers (for side effects only)
watch(() => props.userId, (newId) => {
  fetchUserData(newId)
})

// 7. Methods
function handleSubmit() {
  if (isValid.value) {
    emit('submit', formData.value)
  }
}

// 8. Lifecycle Hooks
onMounted(() => {
  if (props.initialData) {
    formData.value = { ...props.initialData }
  }
})
</script>
```

### Composable Pattern

**Correct: Well-structured composable**
```typescript
// composables/useUser.ts
import { ref, computed, watch } from 'vue'
import type { Ref } from 'vue'
import type { User } from '@/types'

export function useUser(userId: Ref<string> | string) {
  // State
  const user = ref<User | null>(null)
  const loading = ref(false)
  const error = ref<Error | null>(null)

  // Computed
  const fullName = computed(() => {
    if (!user.value) return ''
    return `${user.value.firstName} ${user.value.lastName}`
  })

  // Methods
  async function fetchUser(id: string) {
    loading.value = true
    error.value = null
    try {
      const response = await api.getUser(id)
      user.value = response.data
    } catch (e) {
      error.value = e as Error
    } finally {
      loading.value = false
    }
  }

  // Auto-fetch when userId changes (if reactive)
  if (isRef(userId)) {
    watch(userId, (newId) => fetchUser(newId), { immediate: true })
  } else {
    fetchUser(userId)
  }

  // Return
  return {
    user: readonly(user),
    fullName,
    loading: readonly(loading),
    error: readonly(error),
    refresh: () => fetchUser(unref(userId))
  }
}
```

### Props with Defaults

**Correct: Typed props with defaults**
```vue
<script setup lang="ts">
interface Props {
  title: string
  size?: 'sm' | 'md' | 'lg'
  disabled?: boolean
  items?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  size: 'md',
  disabled: false,
  items: () => []  // Use factory function for arrays/objects
})
</script>
```

### Event Handling

**Correct: Typed emits with payloads**
```vue
<script setup lang="ts">
interface FormData {
  name: string
  email: string
}

const emit = defineEmits<{
  submit: [data: FormData]
  cancel: []
  'update:modelValue': [value: string]
}>()

function handleSubmit(data: FormData) {
  emit('submit', data)
}
</script>
```

### v-model Implementation

**Correct: Custom v-model with defineModel (Vue 3.4+)**
```vue
<script setup lang="ts">
const model = defineModel<string>({ required: true })

// Or with default
const modelWithDefault = defineModel<string>({ default: '' })
</script>

<template>
  <input :value="model" @input="model = $event.target.value" />
</template>
```

**Correct: Custom v-model (Vue 3.3 and earlier)**
```vue
<script setup lang="ts">
const props = defineProps<{
  modelValue: string
}>()

const emit = defineEmits<{
  'update:modelValue': [value: string]
}>()

const value = computed({
  get: () => props.modelValue,
  set: (val) => emit('update:modelValue', val)
})
</script>

<template>
  <input v-model="value" />
</template>
```

### Template Ref Typing

**Correct: Typed template refs**
```vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import MyComponent from './MyComponent.vue'

// DOM element ref
const inputRef = ref<HTMLInputElement | null>(null)

// Component ref
const componentRef = ref<InstanceType<typeof MyComponent> | null>(null)

onMounted(() => {
  inputRef.value?.focus()
  componentRef.value?.someExposedMethod()
})
</script>

<template>
  <input ref="inputRef" />
  <MyComponent ref="componentRef" />
</template>
```

### Provide/Inject with Types

**Correct: Type-safe provide/inject**
```typescript
// types/injection-keys.ts
import type { InjectionKey, Ref } from 'vue'
import type { User } from './user'

export const UserKey: InjectionKey<Ref<User>> = Symbol('user')

// Parent component
import { provide, ref } from 'vue'
import { UserKey } from '@/types/injection-keys'

const user = ref<User>({ id: '1', name: 'John' })
provide(UserKey, user)

// Child component
import { inject } from 'vue'
import { UserKey } from '@/types/injection-keys'

const user = inject(UserKey)
if (!user) {
  throw new Error('User not provided')
}
```

### Error Boundary Component

**Correct: Error boundary with onErrorCaptured**
```vue
<script setup lang="ts">
import { ref, onErrorCaptured } from 'vue'

const error = ref<Error | null>(null)

onErrorCaptured((err) => {
  error.value = err
  // Return false to stop error propagation
  return false
})

function reset() {
  error.value = null
}
</script>

<template>
  <div v-if="error" class="error-boundary">
    <p>Something went wrong: {{ error.message }}</p>
    <button @click="reset">Try again</button>
  </div>
  <slot v-else />
</template>
```

### Async Component Loading

**Correct: Async components with loading/error states**
```typescript
import { defineAsyncComponent } from 'vue'

const AsyncDashboard = defineAsyncComponent({
  loader: () => import('./Dashboard.vue'),
  loadingComponent: LoadingSpinner,
  errorComponent: ErrorDisplay,
  delay: 200,  // Show loading after 200ms
  timeout: 10000  // Timeout after 10s
})
```

## Tailwind CSS Best Practices

Vue's component-based architecture pairs naturally with Tailwind's utility-first approach. Follow these patterns for maintainable, consistent styling.

### Utility-First Approach

Apply Tailwind utility classes directly in Vue templates for rapid, consistent styling:

**Correct: Utility classes in template**
```vue
<template>
  <div class="mx-auto max-w-md rounded-xl bg-white p-6 shadow-lg">
    <h2 class="text-xl font-semibold text-gray-900">{{ title }}</h2>
    <p class="mt-2 text-gray-600">{{ description }}</p>
    <button class="mt-4 rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700">
      {{ buttonText }}
    </button>
  </div>
</template>
```

### Class Ordering Convention

Maintain consistent class ordering for readability. Recommended order:

1. **Layout** - `flex`, `grid`, `block`, `hidden`
2. **Positioning** - `relative`, `absolute`, `fixed`
3. **Box Model** - `w-`, `h-`, `m-`, `p-`
4. **Typography** - `text-`, `font-`, `leading-`
5. **Visual** - `bg-`, `border-`, `rounded-`, `shadow-`
6. **Interactive** - `hover:`, `focus:`, `active:`

Use the official Prettier plugin (`prettier-plugin-tailwindcss`) to automatically sort classes.

### Responsive Design (Mobile-First)

Use Tailwind's responsive prefixes for mobile-first responsive design:

**Correct: Mobile-first responsive layout**
```vue
<template>
  <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
    <article
      v-for="item in items"
      :key="item.id"
      class="p-4 text-sm sm:p-6 sm:text-base lg:text-lg"
    >
      <h3 class="font-medium">{{ item.title }}</h3>
    </article>
  </div>
</template>
```

**Breakpoint Reference:**
- `sm:` - 640px and up
- `md:` - 768px and up
- `lg:` - 1024px and up
- `xl:` - 1280px and up
- `2xl:` - 1536px and up

### State Variants

Use state variants for interactive elements:

**Correct: State variants for buttons**
```vue
<template>
  <button
    class="rounded-lg bg-indigo-600 px-4 py-2 font-medium text-white
           transition-colors duration-150
           hover:bg-indigo-700
           focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2
           active:bg-indigo-800
           disabled:cursor-not-allowed disabled:opacity-50"
    :disabled="isLoading"
  >
    {{ isLoading ? 'Loading...' : 'Submit' }}
  </button>
</template>
```

### Dark Mode Support

Use the `dark:` prefix for dark mode styles:

**Correct: Dark mode support**
```vue
<template>
  <div class="bg-white dark:bg-gray-900">
    <h1 class="text-gray-900 dark:text-white">{{ title }}</h1>
    <p class="text-gray-600 dark:text-gray-400">{{ content }}</p>
    <div class="border-gray-200 dark:border-gray-700 rounded-lg border p-4">
      <slot />
    </div>
  </div>
</template>
```

### Dynamic Classes with Computed Properties

Use computed properties for conditional class binding:

**Correct: Computed classes for variants**
```vue
<script setup lang="ts">
import { computed } from 'vue'

type ButtonVariant = 'primary' | 'secondary' | 'danger'
type ButtonSize = 'sm' | 'md' | 'lg'

const props = withDefaults(defineProps<{
  variant?: ButtonVariant
  size?: ButtonSize
}>(), {
  variant: 'primary',
  size: 'md'
})

const variantClasses = computed(() => {
  const variants: Record<ButtonVariant, string> = {
    primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500',
    secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500',
    danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500'
  }
  return variants[props.variant]
})

const sizeClasses = computed(() => {
  const sizes: Record<ButtonSize, string> = {
    sm: 'px-3 py-1.5 text-sm',
    md: 'px-4 py-2 text-base',
    lg: 'px-6 py-3 text-lg'
  }
  return sizes[props.size]
})

const buttonClasses = computed(() => [
  'inline-flex items-center justify-center rounded-md font-medium',
  'transition-colors duration-150',
  'focus:outline-none focus:ring-2 focus:ring-offset-2',
  variantClasses.value,
  sizeClasses.value
])
</script>

<template>
  <button :class="buttonClasses">
    <slot />
  </button>
</template>
```

### Class Variance Authority (CVA) Pattern

For complex component variants, use the CVA pattern with a helper library:

**Correct: CVA-style variant management**
```vue
<script setup lang="ts">
import { computed } from 'vue'
import { cva, type VariantProps } from 'class-variance-authority'

const button = cva(
  'inline-flex items-center justify-center rounded-md font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2',
  {
    variants: {
      intent: {
        primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500',
        secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300 focus:ring-gray-500',
        danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500'
      },
      size: {
        sm: 'px-3 py-1.5 text-sm',
        md: 'px-4 py-2 text-base',
        lg: 'px-6 py-3 text-lg'
      }
    },
    defaultVariants: {
      intent: 'primary',
      size: 'md'
    }
  }
)

type ButtonProps = VariantProps<typeof button>

const props = defineProps<{
  intent?: ButtonProps['intent']
  size?: ButtonProps['size']
}>()

const classes = computed(() => button({ intent: props.intent, size: props.size }))
</script>

<template>
  <button :class="classes">
    <slot />
  </button>
</template>
```

### Component Extraction for Reusable Patterns

Extract repeated utility patterns into Vue components:

**Correct: Reusable card component**
```vue
<!-- components/BaseCard.vue -->
<script setup lang="ts">
withDefaults(defineProps<{
  padding?: 'none' | 'sm' | 'md' | 'lg'
  shadow?: 'none' | 'sm' | 'md' | 'lg'
}>(), {
  padding: 'md',
  shadow: 'md'
})
</script>

<template>
  <div
    class="rounded-xl bg-white dark:bg-gray-800"
    :class="[
      {
        'p-0': padding === 'none',
        'p-4': padding === 'sm',
        'p-6': padding === 'md',
        'p-8': padding === 'lg'
      },
      {
        'shadow-none': shadow === 'none',
        'shadow-sm': shadow === 'sm',
        'shadow-md': shadow === 'md',
        'shadow-lg': shadow === 'lg'
      }
    ]"
  >
    <slot />
  </div>
</template>
```

### Tailwind Configuration with Design Tokens

Define design tokens in your Tailwind config for consistency:

**Correct: tailwind.config.js with design tokens**
```javascript
/** @type {import('tailwindcss').Config} */
export default {
  content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
  theme: {
    extend: {
      colors: {
        // Semantic color tokens
        primary: {
          50: '#eff6ff',
          100: '#dbeafe',
          500: '#3b82f6',
          600: '#2563eb',
          700: '#1d4ed8'
        },
        surface: {
          light: '#ffffff',
          dark: '#1f2937'
        }
      },
      spacing: {
        // Custom spacing tokens
        '4.5': '1.125rem',
        '18': '4.5rem'
      },
      fontFamily: {
        sans: ['Inter', 'system-ui', 'sans-serif']
      },
      borderRadius: {
        '4xl': '2rem'
      }
    }
  },
  plugins: []
}
```

### Tailwind CSS v4 Configuration

For Tailwind CSS v4, use the CSS-first configuration approach:

**Correct: Tailwind v4 CSS configuration**
```css
/* main.css */
@import "tailwindcss";

@theme {
  /* Custom colors */
  --color-primary-500: #3b82f6;
  --color-primary-600: #2563eb;
  --color-primary-700: #1d4ed8;

  /* Custom spacing */
  --spacing-4-5: 1.125rem;
  --spacing-18: 4.5rem;

  /* Custom fonts */
  --font-family-sans: 'Inter', system-ui, sans-serif;
}
```

### Using `cn()` Helper for Conditional Classes

Use a class merging utility for conditional classes:

**Correct: cn() helper with clsx and tailwind-merge**
```typescript
// utils/cn.ts
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}
```

**Usage in component:**
```vue
<script setup lang="ts">
import { cn } from '@/utils/cn'

const props = defineProps<{
  class?: string
  isActive?: boolean
}>()
</script>

<template>
  <div
    :class="cn(
      'rounded-lg border p-4 transition-colors',
      isActive ? 'border-blue-500 bg-blue-50' : 'border-gray-200 bg-white',
      props.class
    )"
  >
    <slot />
  </div>
</template>
```

## PrimeVue Best Practices

PrimeVue is a comprehensive Vue UI component library with 90+ components. **Use it natively. Do not fight the framework.**

### Core Principle

PrimeVue v4 has a complete theming system based on design tokens and presets. This is the ONLY supported customization path. Do not bypass it.

### Installation & Setup

**Correct: PrimeVue v4 setup with theme preset**
```typescript
// main.ts
import { createApp } from 'vue'
import PrimeVue from 'primevue/config'
import Aura from '@primevue/themes/aura'
import App from './App.vue'

const app = createApp(App)

app.use(PrimeVue, {
  theme: {
    preset: Aura,
    options: {
      prefix: 'p',
      darkModeSelector: '.dark-mode',
      // Ensure clean coexistence with Tailwind
      cssLayer: {
        name: 'primevue',
        order: 'tailwind-base, primevue, tailwind-utilities'
      }
    }
  }
})

app.mount('#app')
```

**Correct: Component registration (tree-shakeable)**
```typescript
// main.ts - Register only components you use
import Button from 'primevue/button'
import DataTable from 'primevue/datatable'
import Column from 'primevue/column'

app.component('Button', Button)
app.component('DataTable', DataTable)
app.component('Column', Column)
```

### Theme Customization with Design Tokens

Customize PrimeVue appearance through the design token system, NOT by overriding component internals:

**Correct: Custom preset using definePreset()**
```typescript
// theme/my-preset.ts
import { definePreset } from '@primevue/themes'
import Aura from '@primevue/themes/aura'

const MyPreset = definePreset(Aura, {
  semantic: {
    primary: {
      50: '{indigo.50}',
      100: '{indigo.100}',
      200: '{indigo.200}',
      300: '{indigo.300}',
      400: '{indigo.400}',
      500: '{indigo.500}',
      600: '{indigo.600}',
      700: '{indigo.700}',
      800: '{indigo.800}',
      900: '{indigo.900}',
      950: '{indigo.950}'
    },
    colorScheme: {
      light: {
        surface: {
          0: '#ffffff',
          50: '{slate.50}',
          100: '{slate.100}',
          // ...
        }
      },
      dark: {
        surface: {
          0: '#0a0a0a',
          50: '{slate.950}',
          // ...
        }
      }
    }
  }
})

export default MyPreset
```

**Usage in main.ts:**
```typescript
import MyPreset from './theme/my-preset'

app.use(PrimeVue, {
  theme: {
    preset: MyPreset
  }
})
```

### Using Components Natively

Use PrimeVue components with their built-in props — do not restyle them:

**Correct: Use built-in severity and variant props**
```vue
<template>
  <!-- Use severity prop for color variants -->
  <Button label="Save" severity="success" />
  <Button label="Delete" severity="danger" />
  <Button label="Info" severity="info" outlined />

  <!-- Use size prop -->
  <Button label="Small" size="small" />
  <Button label="Large" size="large" />

  <!-- Use style variant props -->
  <Button label="Text" text />
  <Button label="Outlined" outlined />
  <Button label="Rounded" rounded />
  <Button label="Raised" raised />

  <!-- Use icon prop -->
  <Button label="Search" icon="pi pi-search" />
  <Button icon="pi pi-check" rounded aria-label="Confirm" />
</template>
```

**Correct: Use slot API for content customization**
```vue
<template>
  <DataTable :value="users" stripedRows paginator :rows="10">
    <Column field="name" header="Name" sortable />
    <Column field="status" header="Status">
      <template #body="{ data }">
        <Tag :value="data.status" :severity="getStatusSeverity(data.status)" />
      </template>
    </Column>
  </DataTable>
</template>
```

### What NEVER to Do with PrimeVue

**NEVER use PassThrough (pt) to restyle components:**
```vue
<!-- INCORRECT: Fighting the framework -->
<Button
  label="Submit"
  :pt="{
    root: { class: 'bg-blue-600 rounded-lg px-4 py-2' },
    label: { class: 'font-medium' }
  }"
/>

<!-- CORRECT: Use the component natively -->
<Button label="Submit" />
```

**NEVER use unstyled mode to rebuild components:**
```typescript
// INCORRECT: Stripping the framework and rebuilding it
app.use(PrimeVue, { unstyled: true })

// CORRECT: Use a theme preset
app.use(PrimeVue, { theme: { preset: Aura } })
```

**NEVER create wrapper components to override styling:**
```vue
<!-- INCORRECT: Unnecessary abstraction -->
<!-- components/AppButton.vue -->
<Button v-bind="$attrs" :pt="{ root: { class: customClasses } }">
  <slot />
</Button>

<!-- CORRECT: Use Button directly everywhere -->
<Button label="Submit" severity="primary" />
```

**NEVER target PrimeVue internal CSS classes:**
```css
/* INCORRECT: Fragile, breaks on updates */
.p-button-label { font-weight: 700; }
.p-datatable-header { background: #f0f0f0; }

/* CORRECT: Customize through design tokens in definePreset() */
```

### Tailwind and PrimeVue Coexistence

Tailwind and PrimeVue serve different roles — do not mix their responsibilities:

- **PrimeVue owns**: component appearance, internal spacing, interactive states, component variants
- **Tailwind owns**: page layout, spacing between components, custom non-library elements, typography on non-library elements

**Correct: Tailwind for layout, PrimeVue components used natively**
```vue
<template>
  <!-- Tailwind handles the page layout -->
  <div class="mx-auto max-w-4xl space-y-6 p-6">
    <h1 class="text-2xl font-bold text-gray-900 dark:text-white">Users</h1>

    <!-- PrimeVue components used as-is -->
    <DataTable :value="users" stripedRows paginator :rows="10">
      <Column field="name" header="Name" sortable />
      <Column field="email" header="Email" sortable />
      <Column field="role" header="Role" />
    </DataTable>

    <!-- Tailwind for spacing between PrimeVue components -->
    <div class="flex gap-3">
      <Button label="Add User" icon="pi pi-plus" />
      <Button label="Export" icon="pi pi-download" severity="secondary" outlined />
    </div>
  </div>
</template>
```

**INCORRECT: Using Tailwind to restyle PrimeVue components**
```vue
<template>
  <!-- WRONG: Tailwind classes overriding PrimeVue button appearance -->
  <Button label="Submit" class="rounded-full bg-indigo-600 px-8 py-3 shadow-xl" />
</template>
```

### DataTable Best Practices

**Correct: Typed DataTable with Composition API**
```vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import DataTable from 'primevue/datatable'
import Column from 'primevue/column'

interface User {
  id: number
  name: string
  email: string
  role: string
  status: 'active' | 'inactive'
}

const users = ref<User[]>([])
const loading = ref(true)
const selectedUsers = ref<User[]>([])

// Pagination
const first = ref(0)
const rows = ref(10)

// Sorting
const sortField = ref<string>('name')
const sortOrder = ref<1 | -1>(1)

onMounted(async () => {
  loading.value = true
  try {
    users.value = await fetchUsers()
  } finally {
    loading.value = false
  }
})
</script>

<template>
  <DataTable
    v-model:selection="selectedUsers"
    :value="users"
    :loading="loading"
    :paginator="true"
    :rows="rows"
    :first="first"
    :sortField="sortField"
    :sortOrder="sortOrder"
    dataKey="id"
    stripedRows
    removableSort
    @page="(e) => first = e.first"
    @sort="(e) => { sortField = e.sortField; sortOrder = e.sortOrder }"
  >
    <Column selectionMode="multiple" headerStyle="width: 3rem" />
    <Column field="name" header="Name" sortable />
    <Column field="email" header="Email" sortable />
    <Column field="role" header="Role" sortable />
    <Column field="status" header="Status">
      <template #body="{ data }">
        <span
          :class="[
            'px-2 py-1 rounded-full text-xs font-medium',
            data.status === 'active'
              ? 'bg-green-100 text-green-800'
              : 'bg-red-100 text-red-800'
          ]"
        >
          {{ data.status }}
        </span>
      </template>
    </Column>
  </DataTable>
</template>
```

### Form Components Pattern

**Correct: Form with validation using PrimeVue**
```vue
<script setup lang="ts">
import { ref, computed } from 'vue'
import InputText from 'primevue/inputtext'
import Password from 'primevue/password'
import Dropdown from 'primevue/dropdown'
import Button from 'primevue/button'
import Message from 'primevue/message'

interface FormData {
  email: string
  password: string
  role: string | null
}

const formData = ref<FormData>({
  email: '',
  password: '',
  role: null
})

const errors = ref<Partial<Record<keyof FormData, string>>>({})
const submitted = ref(false)

const roles = [
  { label: 'Admin', value: 'admin' },
  { label: 'User', value: 'user' },
  { label: 'Guest', value: 'guest' }
]

const isValid = computed(() => {
  return Object.keys(errors.value).length === 0
})

function validate(): boolean {
  errors.value = {}

  if (!formData.value.email) {
    errors.value.email = 'Email is required'
  } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.value.email)) {
    errors.value.email = 'Invalid email format'
  }

  if (!formData.value.password) {
    errors.value.password = 'Password is required'
  } else if (formData.value.password.length < 8) {
    errors.value.password = 'Password must be at least 8 characters'
  }

  if (!formData.value.role) {
    errors.value.role = 'Role is required'
  }

  return Object.keys(errors.value).length === 0
}

function handleSubmit() {
  submitted.value = true
  if (validate()) {
    // Submit form
    console.log('Form submitted:', formData.value)
  }
}
</script>

<template>
  <form @submit.prevent="handleSubmit" class="space-y-4">
    <div class="flex flex-col gap-2">
      <label for="email" class="font-medium">Email</label>
      <InputText
        id="email"
        v-model="formData.email"
        :class="{ 'p-invalid': errors.email }"
        aria-describedby="email-error"
      />
      <Message v-if="errors.email" severity="error" :closable="false">
        {{ errors.email }}
      </Message>
    </div>

    <div class="flex flex-col gap-2">
      <label for="password" class="font-medium">Password</label>
      <Password
        id="password"
        v-model="formData.password"
        :class="{ 'p-invalid': errors.password }"
        toggleMask
        :feedback="false"
        aria-describedby="password-error"
      />
      <Message v-if="errors.password" severity="error" :closable="false">
        {{ errors.password }}
      </Message>
    </div>

    <div class="flex flex-col gap-2">
      <label for="role" class="font-medium">Role</label>
      <Dropdown
        id="role"
        v-model="formData.role"
        :options="roles"
        optionLabel="label"
        optionValue="value"
        placeholder="Select a role"
        :class="{ 'p-invalid': errors.role }"
        aria-describedby="role-error"
      />
      <Message v-if="errors.role" severity="error" :closable="false">
        {{ errors.role }}
      </Message>
    </div>

    <Button type="submit" label="Submit" class="w-full" />
  </form>
</template>
```

### Dialog & Overlay Patterns

**Correct: Confirmation dialog with composable**
```typescript
// composables/useConfirmDialog.ts
import { useConfirm } from 'primevue/useconfirm'

export function useConfirmDialog() {
  const confirm = useConfirm()

  function confirmDelete(
    message: string,
    onAccept: () => void,
    onReject?: () => void
  ) {
    confirm.require({
      message,
      header: 'Confirm Delete',
      icon: 'pi pi-exclamation-triangle',
      rejectClass: 'p-button-secondary p-button-outlined',
      acceptClass: 'p-button-danger',
      rejectLabel: 'Cancel',
      acceptLabel: 'Delete',
      accept: onAccept,
      reject: onReject
    })
  }

  function confirmAction(options: {
    message: string
    header: string
    onAccept: () => void
    onReject?: () => void
  }) {
    confirm.require({
      message: options.message,
      header: options.header,
      icon: 'pi pi-info-circle',
      rejectClass: 'p-button-secondary p-button-outlined',
      acceptClass: 'p-button-primary',
      accept: options.onAccept,
      reject: options.onReject
    })
  }

  return {
    confirmDelete,
    confirmAction
  }
}
```

**Usage:**
```vue
<script setup lang="ts">
import { useConfirmDialog } from '@/composables/useConfirmDialog'
import ConfirmDialog from 'primevue/confirmdialog'

const { confirmDelete } = useConfirmDialog()

function handleDelete(item: Item) {
  confirmDelete(
    `Are you sure you want to delete "${item.name}"?`,
    () => deleteItem(item.id)
  )
}
</script>

<template>
  <ConfirmDialog />
  <Button label="Delete" severity="danger" @click="handleDelete(item)" />
</template>
```

### Toast Notifications

**Correct: Toast service with composable**
```typescript
// composables/useNotifications.ts
import { useToast } from 'primevue/usetoast'

export function useNotifications() {
  const toast = useToast()

  function success(summary: string, detail?: string) {
    toast.add({
      severity: 'success',
      summary,
      detail,
      life: 3000
    })
  }

  function error(summary: string, detail?: string) {
    toast.add({
      severity: 'error',
      summary,
      detail,
      life: 5000
    })
  }

  function warn(summary: string, detail?: string) {
    toast.add({
      severity: 'warn',
      summary,
      detail,
      life: 4000
    })
  }

  function info(summary: string, detail?: string) {
    toast.add({
      severity: 'info',
      summary,
      detail,
      life: 3000
    })
  }

  return { success, error, warn, info }
}
```

### Accessibility Best Practices

PrimeVue components are WCAG 2.0 compliant. Ensure proper usage:

**Correct: Accessible form fields**
```vue
<template>
  <div class="flex flex-col gap-2">
    <label :for="id" class="font-medium">
      {{ label }}
      <span v-if="required" class="text-red-500" aria-hidden="true">*</span>
    </label>
    <InputText
      :id="id"
      v-model="modelValue"
      :aria-required="required"
      :aria-invalid="!!error"
      :aria-describedby="error ? `${id}-error` : undefined"
    />
    <small
      v-if="error"
      :id="`${id}-error`"
      class="text-red-500"
      role="alert"
    >
      {{ error }}
    </small>
  </div>
</template>
```

### Lazy Loading Components

**Correct: Async component loading for large PrimeVue components**
```typescript
// components/lazy/index.ts
import { defineAsyncComponent } from 'vue'

export const LazyDataTable = defineAsyncComponent({
  loader: () => import('primevue/datatable'),
  loadingComponent: () => import('@/components/ui/TableSkeleton.vue'),
  delay: 200
})

export const LazyEditor = defineAsyncComponent({
  loader: () => import('primevue/editor'),
  loadingComponent: () => import('@/components/ui/EditorSkeleton.vue'),
  delay: 200
})

export const LazyChart = defineAsyncComponent({
  loader: () => import('primevue/chart'),
  loadingComponent: () => import('@/components/ui/ChartSkeleton.vue'),
  delay: 200
})
```

## Anti-Patterns to Avoid

### Don't Mutate Props

**Incorrect:**
```vue
<script setup>
const props = defineProps(['items'])

function addItem(item) {
  props.items.push(item)  // Never mutate props!
}
</script>
```

**Correct:**
```vue
<script setup>
const props = defineProps(['items'])
const emit = defineEmits(['update:items'])

function addItem(item) {
  emit('update:items', [...props.items, item])
}
</script>
```

### Don't Use v-if with v-for

**Incorrect:**
```vue
<template>
  <div v-for="item in items" v-if="item.isActive" :key="item.id">
    {{ item.name }}
  </div>
</template>
```

**Correct:**
```vue
<script setup>
const activeItems = computed(() => items.value.filter(item => item.isActive))
</script>

<template>
  <div v-for="item in activeItems" :key="item.id">
    {{ item.name }}
  </div>
</template>
```

### Don't Store Derived State

**Incorrect:**
```vue
<script setup>
const items = ref([])
const itemCount = ref(0)  // Derived state stored separately

watch(items, () => {
  itemCount.value = items.value.length  // Manually syncing
})
</script>
```

**Correct:**
```vue
<script setup>
const items = ref([])
const itemCount = computed(() => items.value.length)  // Computed property
</script>
```

### Don't Destructure Reactive Objects

**Incorrect:**
```vue
<script setup>
const state = reactive({ count: 0, name: 'Vue' })
const { count, name } = state  // Loses reactivity!
</script>
```

**Correct:**
```vue
<script setup>
const state = reactive({ count: 0, name: 'Vue' })
const { count, name } = toRefs(state)  // Preserves reactivity
</script>
```

### Don't Concatenate Tailwind Class Names

Dynamic class concatenation breaks Tailwind's compiler and classes get purged in production:

**Incorrect:**
```vue
<script setup>
const color = ref('blue')
</script>

<template>
  <!-- Classes will be purged in production! -->
  <div :class="`bg-${color}-500 text-${color}-900`">
    Content
  </div>
</template>
```

**Correct:**
```vue
<script setup>
const color = ref<'blue' | 'green' | 'red'>('blue')

const colorClasses = computed(() => {
  const colors = {
    blue: 'bg-blue-500 text-blue-900',
    green: 'bg-green-500 text-green-900',
    red: 'bg-red-500 text-red-900'
  }
  return colors[color.value]
})
</script>

<template>
  <div :class="colorClasses">
    Content
  </div>
</template>
```

### Don't Overuse @apply

Excessive `@apply` usage defeats the purpose of utility-first CSS:

**Incorrect:**
```css
/* styles.css */
.card {
  @apply mx-auto max-w-md rounded-xl bg-white p-6 shadow-lg;
}

.card-title {
  @apply text-xl font-semibold text-gray-900;
}

.card-description {
  @apply mt-2 text-gray-600;
}

.card-button {
  @apply mt-4 rounded-md bg-blue-600 px-4 py-2 text-white hover:bg-blue-700;
}
```

**Correct: Use Vue components instead**
```vue
<!-- components/Card.vue -->
<template>
  <div class="mx-auto max-w-md rounded-xl bg-white p-6 shadow-lg">
    <h2 class="text-xl font-semibold text-gray-900">
      <slot name="title" />
    </h2>
    <p class="mt-2 text-gray-600">
      <slot name="description" />
    </p>
    <div class="mt-4">
      <slot name="actions" />
    </div>
  </div>
</template>
```

### Don't Use Conflicting Utilities

Applying multiple utilities that target the same CSS property causes unpredictable results:

**Incorrect:**
```vue
<template>
  <!-- Both flex and grid target display property -->
  <div class="flex grid">Content</div>

  <!-- Multiple margin utilities conflict -->
  <div class="m-4 mx-6">Content</div>
</template>
```

**Correct:**
```vue
<template>
  <div :class="isGrid ? 'grid' : 'flex'">Content</div>

  <!-- Use specific margin utilities -->
  <div class="mx-6 my-4">Content</div>
</template>
```

### Don't Ignore Accessibility

Always include proper accessibility attributes alongside visual styling:

**Incorrect:**
```vue
<template>
  <button class="rounded bg-blue-600 p-2 text-white">
    <IconX />
  </button>
</template>
```

**Correct:**
```vue
<template>
  <button
    class="rounded bg-blue-600 p-2 text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
    aria-label="Close dialog"
  >
    <IconX aria-hidden="true" />
  </button>
</template>
```

### Don't Create Overly Long Class Strings

Break down complex class combinations into logical groups or components:

**Incorrect:**
```vue
<template>
  <div class="mx-auto mt-8 flex max-w-4xl flex-col items-center justify-between gap-4 rounded-xl border border-gray-200 bg-white p-6 shadow-lg transition-all duration-300 hover:border-blue-500 hover:shadow-xl dark:border-gray-700 dark:bg-gray-800 sm:flex-row sm:gap-6 md:p-8 lg:gap-8">
    <!-- 15+ utilities on one element -->
  </div>
</template>
```

**Correct: Extract to component or use computed**
```vue
<script setup>
const containerClasses = [
  // Layout
  'mx-auto max-w-4xl flex flex-col sm:flex-row',
  'items-center justify-between',
  'gap-4 sm:gap-6 lg:gap-8',
  // Spacing
  'mt-8 p-6 md:p-8',
  // Visual
  'rounded-xl border bg-white shadow-lg',
  'border-gray-200 dark:border-gray-700 dark:bg-gray-800',
  // Interactive
  'transition-all duration-300',
  'hover:border-blue-500 hover:shadow-xl'
]
</script>

<template>
  <div :class="containerClasses">
    <slot />
  </div>
</template>
```

### Don't Override PrimeVue Styles with CSS

Using CSS overrides bypasses the design system and causes maintenance issues:

**Incorrect:**
```css
/* styles.css - Avoid this approach */
.p-button {
  background-color: #3b82f6 !important;
  border-radius: 8px !important;
}

.p-datatable .p-datatable-thead > tr > th {
  background: #f3f4f6 !important;
}
```

**Correct: Use design tokens or PassThrough**
```typescript
// main.ts - Use design tokens
app.use(PrimeVue, {
  theme: {
    preset: Aura,
    options: {
      cssLayer: {
        name: 'primevue',
        order: 'tailwind-base, primevue, tailwind-utilities'
      }
    }
  },
  pt: {
    button: {
      root: { class: 'rounded-lg' }
    }
  }
})
```

### Don't Import Entire PrimeVue Library

Importing everything bloats bundle size:

**Incorrect:**
```typescript
// main.ts - Don't do this
import PrimeVue from 'primevue/config'
import * as PrimeVueComponents from 'primevue'  // Imports everything!

Object.entries(PrimeVueComponents).forEach(([name, component]) => {
  app.component(name, component)
})
```

**Correct: Import only what you need**
```typescript
// main.ts - Tree-shakeable imports
import Button from 'primevue/button'
import DataTable from 'primevue/datatable'
import Column from 'primevue/column'

app.component('Button', Button)
app.component('DataTable', DataTable)
app.component('Column', Column)
```

### Don't Mix Styled and Unstyled Inconsistently

Mixing modes creates visual inconsistency:

**Incorrect:**
```typescript
// main.ts
app.use(PrimeVue, {
  unstyled: true  // Global unstyled
})

// SomeComponent.vue - Using styled component anyway
<Button label="Click" />  // No styles applied, looks broken
```

**Correct: Choose one approach consistently**
```typescript
// Option 1: Styled mode with PT customization
app.use(PrimeVue, {
  theme: { preset: Aura },
  pt: { /* global customizations */ }
})

// Option 2: Unstyled mode with complete PT styling
app.use(PrimeVue, {
  unstyled: true,
  pt: {
    button: {
      root: { class: 'px-4 py-2 bg-primary-600 text-white rounded-lg' }
    }
    // ... complete styling for all components
  }
})
```

### Don't Ignore Accessibility Attributes

PrimeVue provides accessibility out of the box, don't disable or ignore it:

**Incorrect:**
```vue
<template>
  <!-- Missing aria attributes and label -->
  <Button icon="pi pi-trash" @click="deleteItem" />

  <!-- No error message association -->
  <InputText v-model="email" :class="{ 'p-invalid': hasError }" />
  <span class="error">Invalid email</span>
</template>
```

**Correct: Maintain accessibility**
```vue
<template>
  <Button
    icon="pi pi-trash"
    aria-label="Delete item"
    @click="deleteItem"
  />

  <div class="flex flex-col gap-2">
    <label for="email">Email</label>
    <InputText
      id="email"
      v-model="email"
      :class="{ 'p-invalid': hasError }"
      :aria-invalid="hasError"
      aria-describedby="email-error"
    />
    <small id="email-error" v-if="hasError" class="text-red-500" role="alert">
      Invalid email
    </small>
  </div>
</template>
```

### Don't Hardcode PassThrough in Every Component

Repeating PT configuration across components creates duplication:

**Incorrect:**
```vue
<!-- ComponentA.vue -->
<Button :pt="{ root: { class: 'rounded-lg shadow-md' } }" />

<!-- ComponentB.vue -->
<Button :pt="{ root: { class: 'rounded-lg shadow-md' } }" />

<!-- ComponentC.vue -->
<Button :pt="{ root: { class: 'rounded-lg shadow-md' } }" />
```

**Correct: Use global PT or wrapper components**
```typescript
// main.ts - Global configuration
app.use(PrimeVue, {
  pt: {
    button: {
      root: { class: 'rounded-lg shadow-md' }
    }
  }
})

// Or use wrapper components (see Wrapper Components Pattern above)
```

## Nuxt.js Specific Guidelines

When using Nuxt.js, follow these additional patterns:

- **Auto-imports**: Leverage Nuxt's auto-imports for Vue APIs and composables
- **useFetch/useAsyncData**: Use Nuxt's data fetching composables for SSR-compatible data loading
- **definePageMeta**: Use for page-level metadata and middleware
- **Server routes**: Use `server/api/` for API endpoints
- **Runtime config**: Use `useRuntimeConfig()` for environment variables

## References

### Vue.js
- [Vue.js Documentation](https://vuejs.org)
- [Vue.js Style Guide](https://vuejs.org/style-guide/)
- [Composition API FAQ](https://vuejs.org/guide/extras/composition-api-faq.html)
- [VueUse - Collection of Vue Composition Utilities](https://vueuse.org)
- [Nuxt Documentation](https://nuxt.com)
- [Pinia Documentation](https://pinia.vuejs.org)

### Tailwind CSS
- [Tailwind CSS Documentation](https://tailwindcss.com/docs)
- [Styling with Utility Classes](https://tailwindcss.com/docs/styling-with-utility-classes)
- [Tailwind CSS v4 Release](https://tailwindcss.com/blog/tailwindcss-v4)
- [Class Variance Authority (CVA)](https://cva.style/docs)
- [tailwind-merge](https://github.com/dcastil/tailwind-merge)
- [prettier-plugin-tailwindcss](https://github.com/tailwindlabs/prettier-plugin-tailwindcss)
- [Vue School - Tailwind CSS Fundamentals](https://vueschool.io/courses/tailwind-css-fundamentals)

### PrimeVue
- [PrimeVue Documentation](https://primevue.org/)
- [PrimeVue PassThrough API](https://primevue.org/passthrough/)
- [PrimeVue Theming - Styled Mode](https://primevue.org/theming/styled/)
- [PrimeVue GitHub Repository](https://github.com/primefaces/primevue)
- [PrimeVue v4 Component Changes](https://github.com/primefaces/primevue/wiki/v4-Component-Changes)
- [Volt - Tailwind CSS based PrimeVue Components](https://volt.primevue.org/)
- [Deep Dive into PrimeVue PassThrough Props](https://dev.to/cagataycivici/deep-dive-into-primevue-passthrough-props-2im8)
- [Build Your Own Vue UI Library with Unstyled PrimeVue](https://dev.to/cagataycivici/build-your-own-vue-ui-library-with-unstyled-primevue-core-and-tailwind-css-23ll)
- [PrimeIcons](https://primevue.org/icons/)

More Refactoring skills

← All Refactoring skills

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY