mcpField
V
vue-best-practices

vue-best-practices

github.com/dedalus-erp-pas開発252 回インストール
リポジトリ

vue-best-practices について

Vue.js 3 アプリの開発、レビュー、リファクタリングに使うベストプラクティスのスキルです。Nuxt.js にも対応し、Composition API、リアクティビティ、コンポーネント設計、TypeScript、Tailwind CSS、PrimeVue の指針をまとめています。

主な機能

  1. 01Composition API とコンポーザブルの指針
  2. 02コンポーネントとリアクティビティのパターン
  3. 03型付き Props・イベント・テンプレート
  4. 04エラー処理と非同期コンポーネント
  5. 05Tailwind CSS のスタイル実践
  6. 06PrimeVue の統合とテーマ設定

活用シーン

  1. 01Vue コンポーネントとコンポーザブルの作成
  2. 02Vue アプリのコードレビューやリファクタリング
  3. 03コンポーネント構成の設計
  4. 04Tailwind CSS と PrimeVue を使ったスタイル作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dedalus-erp-pas/foundation-skills --skill vue-best-practices

インストール後、Claude Code で/vue-best-practicesを入力して読み込まれたか確認します。

詳細

Vue.js のベストプラクティス

Vue.js 3 アプリケーション向けの包括的なベストプラクティスガイドです。Tailwind CSS によるユーティリティファーストのスタイリングパターンや、PrimeVue コンポーネントライブラリのベストプラクティスを含め、慣用的で保守しやすく、拡張性の高い Vue.js コードを実現するための複数のカテゴリにわたるガイドラインを紹介します。

適用するタイミング

次のような場合に、これらのガイドラインを参照してください。

  • 新しい Vue コンポーネントやコンポーザブルを作成するとき
  • Composition API で機能を実装するとき
  • Vue.js のパターンへの準拠についてコードをレビューするとき
  • 既存の Vue.js コードをリファクタリングするとき
  • コンポーネントアーキテクチャを構築するとき
  • Nuxt.js アプリケーションで作業するとき
  • Tailwind CSS のユーティリティクラスで Vue コンポーネントをスタイリングするとき
  • Tailwind と Vue でデザインシステムを作成するとき
  • PrimeVue コンポーネントライブラリをネイティブに使用するとき
  • デザイントークンと definePreset() を通じて PrimeVue のテーマをカスタマイズするとき

ルールのカテゴリ

カテゴリ対象プレフィックス
Composition APIComposition API パターンの適切な使用composition-
コンポーネント設計コンポーネントの構造と整理component-
リアクティビティリアクティブな状態管理パターンreactive-
Props とイベントコンポーネントの通信パターンprops-
テンプレートパターンテンプレート構文のベストプラクティスtemplate-
コードの整理プロジェクトとコードの構造organization-
TypeScript型安全な Vue.js パターンtypescript-
エラー処理エラーバウンダリーとエラー処理error-
Tailwind CSSユーティリティファーストのスタイリングパターンtailwind-
PrimeVueコンポーネントライブラリの統合パターンprimevue-

クイックリファレンス

1. Composition API のベストプラクティス
  • composition-script-setup - 単一ファイルコンポーネントでは常に <script setup> を使用します
  • composition-ref-vs-reactive - プリミティブには ref()、オブジェクトには reactive() を使用します
  • composition-computed-derived - 派生状態にはすべて computed() を使用します
  • composition-watch-side-effects - watch()/watchEffect() は副作用にのみ使用します
  • composition-composables - 再利用可能なロジックをコンポーザブルに切り出します
  • composition-lifecycle-order - リアクティブな状態の宣言後にライフサイクルフックを配置します
  • composition-avoid-this - Composition API では this を使用しません
2. コンポーネント設計
  • component-single-responsibility - 1 コンポーネントにつき目的は 1 つにします
  • component-naming-convention - コンポーネントには PascalCase、テンプレートには kebab-case を使用します
  • component-small-focused - コンポーネントは 200 行未満に保ちます
  • component-presentational-container - 効果的な場合は、ロジックとプレゼンテーションを分離します
  • component-slots-flexibility - 柔軟なコンポーネント構成にはスロットを使用します
  • component-expose-minimal - defineExpose() で公開するのは必要なものだけにします
3. リアクティビティのパターン
  • reactive-const-refs - ref は常に const で宣言します
  • reactive-unwrap-template - テンプレートでは Vue に ref のアンラップを任せます(.value は不要です)
  • reactive-shallow-large-data - 大きな非リアクティブデータには shallowRef()/shallowReactive() を使用します
  • reactive-readonly-props - 変更を防ぐには readonly() を使用します
  • reactive-toRefs-destructure - リアクティブオブジェクトを分割代入するときは toRefs() を使用します
  • reactive-avoid-mutation - 複雑な状態では不変な更新を優先します
4. Props とイベント
  • props-define-types - defineProps<T>() で prop の型を常に定義します
  • props-required-explicit - 必須 prop とオプションの prop を明確に指定します
  • props-default-values - withDefaults() で妥当なデフォルト値を指定します
  • props-immutable - prop を直接変更しません
  • props-validation - 複雑な prop の検証にはバリデーター関数を使用します
  • events-define-emits - defineEmits<T>() で emit を常に定義します
  • events-naming - テンプレート内のイベント名には kebab-case を使用します
  • events-payload-objects - 複数の値を持つイベントにはオブジェクトを渡します
5. テンプレートのパターン
  • template-v-if-v-show - 条件付きレンダリングにはv-ifを、表示の切り替えにはv-showを使用します
  • template-v-for-key - v-forでは、常に一意で安定した:keyを使用します
  • template-v-if-v-for - 同じ要素でv-ifとv-forを併用しないでください
  • template-computed-expressions - 複雑な式はcomputedプロパティに移動します
  • template-event-modifiers - イベント修飾子(.prevent、.stop)を適切に使用します
  • template-v-bind-shorthand - 省略構文(v-bindには:、v-onには@)を使用します
  • template-v-model-modifiers - v-model修飾子(.trim、.number、.lazy)を使用します
6. コードの構成
  • organization-feature-folders - 種類別ではなく、機能別に整理します
  • organization-composables-folder - composableは専用のcomposables/フォルダーに配置します
  • organization-barrel-exports - インポートをすっきりさせるため、indexファイルを使用します
  • organization-consistent-naming - 一貫した命名規則に従います
  • organization-colocation - 関連ファイル(コンポーネント、テスト、スタイル)を同じ場所に配置します
7. TypeScriptとの統合
  • typescript-generic-components - 再利用可能な型付きコンポーネントにはジェネリクスを使用します
  • typescript-prop-types - propsの定義にはTypeScriptのインターフェースを使用します
  • typescript-emit-types - emitのペイロードを明示的に型付けします
  • typescript-ref-typing - 型が推論されない場合は、refの型を指定します
  • typescript-template-refs - template refをref<InstanceType<typeof Component> | null>(null)で型付けします
8. エラー処理
  • error-boundaries - コンポーネントのエラーバウンダリーにはonErrorCaptured()を使用します
  • error-async-handling - 非同期処理で発生するエラーを明示的に処理します
  • error-provide-fallbacks - エラー状態に対するフォールバックUIを用意します
  • error-logging - デバッグに役立つよう、エラーを適切にログに記録します
9. Tailwind CSS
  • tailwind-utility-first - テンプレート内でユーティリティクラスを直接適用し、カスタムCSSは避けます
  • tailwind-class-order - 一貫したクラス順序(レイアウト → 余白 → タイポグラフィ → 外観)を使用します
  • tailwind-responsive-mobile-first - モバイルファーストのレスポンシブデザイン(sm:、md:、lg:)を使用します
  • tailwind-component-extraction - 繰り返し使うユーティリティパターンをVueコンポーネントに切り出します
  • tailwind-dynamic-classes - 動的クラスにはcomputedプロパティまたはヘルパー関数を使用します
  • tailwind-complete-class-strings - 常に完全なクラス文字列を使用し、連結しないでください
  • tailwind-state-variants - インタラクションには状態バリアント(hover:、focus:、active:)を使用します
  • tailwind-dark-mode - ダークモードをサポートするにはdark:プレフィックスを使用します
  • tailwind-design-tokens - 一貫性を保つため、Tailwindの設定でデザイントークンを構成します
  • tailwind-avoid-apply-overuse - @applyの使用を最小限に抑え、抽象化にはVueコンポーネントを優先します
10. PrimeVue
  • primevue-use-natively - ドキュメントに記載されたpropsとAPIを使用し、PrimeVueコンポーネントをそのまま使用します
  • primevue-design-tokens - 外観のカスタマイズには、デザイントークンとdefinePreset()のみを使用します
  • primevue-no-pt-overrides - コンポーネントのスタイル変更にPassThrough(pt)APIを使用しないでください
  • primevue-no-unstyled-mode - コンポーネントのスタイルを取り除いて作り直すために、unstyledモードを使用しないでください
  • primevue-no-wrapper-components - スタイルを上書きするだけの目的でPrimeVueコンポーネントをラップしないでください
  • primevue-props-api - バリエーションには組み込みprops(severity、size、outlined、rounded、raised、text)を使用します
  • primevue-css-layers - Tailwindとすっきり共存できるよう、CSSレイヤーの順序を設定します
  • primevue-typed-components - 型安全性のため、PrimeVueのTypeScriptサポートを活用します
  • primevue-accessibility - 適切なaria属性を使用し、WCAGへの準拠を維持します
  • primevue-lazy-loading - 大規模なPrimeVueインポートには非同期コンポーネントを使用します

主要な原則

Composition API のベストプラクティス

Composition API は Vue.js 3 で推奨されるアプローチです。以下のパターンに従ってください。

  • 常に <script setup> を使用する:より簡潔で、TypeScript の型推論が向上し、パフォーマンスも改善されます
  • 論理的な関心事ごとにコードを整理する:関連する状態、算出プロパティ、関数をまとめます
  • 再利用可能なロジックはコンポーザブルに切り出す:use 接頭辞の規則に従います(例:useAuth、useFetch)
  • setup コードの読みやすさを保つ:props/emits、リアクティブな状態、算出プロパティ、ウォッチャー、メソッド、ライフサイクルフックの順に並べます
コンポーネント設計の原則

適切に設計されたコンポーネントは、保守しやすい Vue アプリケーションの基盤です。

  • 単一責任:各コンポーネントは、1 つのことを適切に行うようにします
  • Props Down, Events Up:単方向のデータフローに従います
  • 継承よりコンポジションを優先する:コードの再利用にはコンポーザブルとスロットを使用します
  • コンポーネントは小さく保つ:コンポーネントが 200 行を超える場合は、分割を検討します
リアクティビティのガイドライン

Vue のリアクティビティシステムを理解することは重要です。

  • ref と reactive の使い分け:プリミティブ値や再代入する値には ref() を使用し、変更するオブジェクトには reactive() を使用します
  • 派生状態には computed を使用する:派生状態を ref に保存せず、代わりに computed() を使用します
  • 副作用には watch を使用する:API 呼び出しや localStorage へのアクセスなど、副作用がある場合にのみ watch() を使用します
  • リアクティビティの喪失に注意する:toRefs() を使わずにリアクティブオブジェクトを分割代入しないでください
Props とイベントのパターン

適切なコンポーネント間通信によって、保守しやすいコードを実現できます。

  • props に型を付ける:defineProps<T>() と TypeScript のインターフェースを使用します
  • 複雑な props を検証する:ビジネスロジックの検証にはバリデーター関数を使用します
  • 型付きイベントを発行する:型安全なイベント処理には defineEmits<T>() を使用します
  • 双方向バインディングには v-model を使用する:modelValue prop と update:modelValue emit を実装します

よくあるパターン

Script Setup の構成

推奨される <script setup> の構成:

<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>
コンポーザブルのパターン

正しい例:適切に構成されたコンポーザブル

// 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

正しい例:デフォルト値を持つ型付き props

<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>
イベント処理

正しい例:ペイロード付きの型付き emits

<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 の実装

正しい例:defineModel を使ったカスタム v-model(Vue 3.4 以降)

<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>

正しい例:カスタム v-model(Vue 3.3 以前)

<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>
テンプレート ref の型付け

正しい例:型付きテンプレート ref

<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

正しい例:型安全な provide/inject

// 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')
}
エラーバウンダリーコンポーネント

正しい例:onErrorCaptured を使ったエラーバウンダリー

<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>
非同期コンポーネントの読み込み

正しい例:読み込み状態とエラー状態を備えた非同期コンポーネント

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 のベストプラクティス

Vue のコンポーネントベースのアーキテクチャは、Tailwind のユーティリティファーストのアプローチと自然に組み合わせられます。保守しやすく一貫性のあるスタイル設定のため、以下のパターンに従ってください。

ユーティリティファーストのアプローチ

迅速で一貫性のあるスタイル設定のため、Vue テンプレート内で Tailwind のユーティリティクラスを直接適用します。

正しい例:テンプレート内のユーティリティクラス

<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>
クラスの並べ替え規則

読みやすさを保つため、クラスの並び順を統一します。推奨される順序は次のとおりです。

  1. レイアウト - flex、grid、block、hidden
  2. 配置 - relative、absolute、fixed
  3. ボックスモデル - w-、h-、m-、p-
  4. タイポグラフィ - text-、font-、leading-
  5. 視覚効果 - bg-、border-、rounded-、shadow-
  6. インタラクション - hover:、focus:、active:

クラスを自動的に並べ替えるには、公式の Prettier プラグイン(prettier-plugin-tailwindcss)を使用します。

レスポンシブデザイン(モバイルファースト)

Tailwind のレスポンシブプレフィックスを使用して、モバイルファーストのレスポンシブデザインを実現します。

正しい例:モバイルファーストのレスポンシブレイアウト

<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>

ブレークポイントの参照:

  • sm: - 640px 以上
  • md: - 768px 以上
  • lg: - 1024px 以上
  • xl: - 1280px 以上
  • 2xl: - 1536px 以上
状態バリアント

インタラクティブな要素には状態バリアントを使用します。

正しい例:ボタンの状態バリアント

<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: プレフィックスを使用します。

正しい例:ダークモードのサポート

<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>
算出プロパティを使った動的クラス

条件に応じたクラスのバインドには、算出プロパティを使用します。

正しい例:バリエーションに応じた算出クラス

<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)パターン

複雑なコンポーネントのバリエーションには、ヘルパーライブラリを使って CVA パターンを使用します。

正しい例:CVA スタイルのバリエーション管理

<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>
再利用可能なパターンのためのコンポーネント抽出

繰り返し使用するユーティリティパターンは、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 の設定

一貫性を保つため、Tailwind の設定でデザイントークンを定義します。

正しい例:デザイントークンを含む tailwind.config.js

/** @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 の設定

Tailwind CSS v4 では、CSS ファーストの設定方法を使用します。

正しい例:Tailwind v4 の 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;
}
条件付きクラスに cn() ヘルパーを使用する

条件付きクラスには、クラスを結合するユーティリティを使用します。

正しい例:clsx と tailwind-merge を使った cn() ヘルパー

// utils/cn.ts
import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'

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

コンポーネントでの使用例:

<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 のベストプラクティス

PrimeVue は、90 以上のコンポーネントを備えた包括的な Vue UI コンポーネントライブラリです。そのまま使用してください。フレームワークに逆らわないでください。

基本原則

PrimeVue v4 には、デザイントークンとプリセットに基づく完全なテーマシステムがあります。これがサポートされている唯一のカスタマイズ方法です。迂回しないでください。

インストールとセットアップ

正しい例:テーマプリセットを使った PrimeVue v4 のセットアップ

// 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')

正しい例:コンポーネントの登録(ツリーシェイキング可能)

// 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)
デザイントークンを使ったテーマのカスタマイズ

コンポーネント内部をオーバーライドするのではなく、デザイントークンシステムを通じて PrimeVue の外観をカスタマイズします。

正しい例:definePreset() を使ったカスタムプリセット

// 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

main.ts での使用例:

import MyPreset from './theme/my-preset'

app.use(PrimeVue, {
  theme: {
    preset: MyPreset
  }
})
コンポーネントをそのまま使用する

PrimeVue コンポーネントは組み込みの props とともに使用し、スタイルを変更しないでください。

正しい例:組み込みの severity と variant props を使用する

<template>
  
  <Button label="Save" severity="success" />
  <Button label="Delete" severity="danger" />
  <Button label="Info" severity="info" outlined />

  
  <Button label="Small" size="small" />
  <Button label="Large" size="large" />

  
  <Button label="Text" text />
  <Button label="Outlined" outlined />
  <Button label="Rounded" rounded />
  <Button label="Raised" raised />

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

正しい例:コンテンツのカスタマイズにスロット API を使用する

<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>
PrimeVue で絶対にしてはいけないこと

PassThrough(pt)を使ってコンポーネントのスタイルを変更しないでください。


<Button
  label="Submit"
  :pt="{
    root: { class: 'bg-blue-600 rounded-lg px-4 py-2' },
    label: { class: 'font-medium' }
  }"
/>


<Button label="Submit" />

unstyled モードを使ってコンポーネントを作り直さないでください。

// INCORRECT: Stripping the framework and rebuilding it
app.use(PrimeVue, { unstyled: true })

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

スタイルをオーバーライドするラッパーコンポーネントを作成しないでください。



<Button v-bind="$attrs" :pt="{ root: { class: customClasses } }">
  <slot />
</Button>


<Button label="Submit" severity="primary" />

PrimeVue の内部 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 と PrimeVue の併用

Tailwind と PrimeVue は異なる役割を担います。それぞれの役割を混同しないでください。

  • PrimeVue の役割:コンポーネントの外観、内部の余白、インタラクティブな状態、コンポーネントのバリエーション
  • Tailwind の役割:ページレイアウト、コンポーネント間の余白、ライブラリに含まれないカスタム要素、ライブラリに含まれない要素のタイポグラフィ

正しい例:レイアウトには Tailwind を使い、PrimeVue コンポーネントはそのまま使用する

<template>
  
  <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>

    
    <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>

    
    <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>

誤った例:Tailwind を使って PrimeVue コンポーネントのスタイルを変更する

<template>
  
  <Button label="Submit" class="rounded-full bg-indigo-600 px-8 py-3 shadow-xl" />
</template>
DataTable のベストプラクティス

正しい例:Composition API を使った型付き DataTable

<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>
フォームコンポーネントのパターン

正しい例:PrimeVue を使ったバリデーション付きフォーム

<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>
ダイアログとオーバーレイのパターン

正しい例:コンポーザブルを使った確認ダイアログ

// 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
  }
}

使用例:

<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>
トースト通知

正しい例:コンポーザブルを使ったトーストサービス

// 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 }
}
アクセシビリティのベストプラクティス

PrimeVue コンポーネントは WCAG 2.0 に準拠しています。適切に使用してください。

正しい例:アクセシブルなフォームフィールド

<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>
コンポーネントの遅延読み込み

正しい例:大きな PrimeVue コンポーネントの非同期読み込み

// 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
})

避けるべきアンチパターン

Props を変更しない

誤り:

<script setup>
const props = defineProps(['items'])

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

正しい例:

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

function addItem(item) {
  emit('update:items', [...props.items, item])
}
</script>
v-if と v-for を併用しない

誤り:

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

正しい例:

<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>
派生状態を保存しない

誤り:

<script setup>
const items = ref([])
const itemCount = ref(0)  // Derived state stored separately

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

正しい例:

<script setup>
const items = ref([])
const itemCount = computed(() => items.value.length)  // Computed property
</script>
リアクティブオブジェクトを分割代入しない

誤り:

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

正しい例:

<script setup>
const state = reactive({ count: 0, name: 'Vue' })
const { count, name } = toRefs(state)  // Preserves reactivity
</script>
Tailwind のクラス名を連結しない

クラス名を動的に連結すると Tailwind のコンパイラーが正常に動作せず、本番環境でクラスが削除されます。

誤り:

<script setup>
const color = ref('blue')
</script>

<template>
  
  <div :class="`bg-${color}-500 text-${color}-900`">
    Content
  </div>
</template>

正しい例:

<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>
@apply を使いすぎない

@apply を過度に使うと、ユーティリティファースト 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;
}

正しい例:代わりに 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>
競合するユーティリティを使用しない

同じ CSS プロパティを対象とする複数のユーティリティを適用すると、予測できない結果になります。

誤り:

<template>
  
  <div class="flex grid">Content</div>

  
  <div class="m-4 mx-6">Content</div>
</template>

正しい例:

<template>
  <div :class="isGrid ? 'grid' : 'flex'">Content</div>

  
  <div class="mx-6 my-4">Content</div>
</template>
アクセシビリティを無視しない

視覚的なスタイル設定とともに、適切なアクセシビリティ属性を必ず含めてください。

誤り:

<template>
  <button class="rounded bg-blue-600 p-2 text-white">
    <IconX />
  </button>
</template>

正しい例:

<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>
クラス文字列を長くしすぎない

複雑なクラスの組み合わせは、論理的なグループやコンポーネントに分けてください。

誤り:

<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">
    
  </div>
</template>

正しい例:コンポーネントに切り出すか、computed を使用する

<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>
CSS で PrimeVue のスタイルを上書きしない

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;
}

正しい例:デザイントークンまたは PassThrough を使用する

// 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' }
    }
  }
})
PrimeVue ライブラリ全体をインポートしない

すべてをインポートすると、バンドルサイズが肥大化します。

誤り:

// 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)
})

正しい例:必要なものだけをインポートする

// 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)
スタイル付きモードとスタイルなしモードを一貫性なく混在させない

モードを混在させると、視覚的な一貫性が失われます。

誤り:

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

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

正しい例:一貫してどちらか一方の方法を選ぶ

// 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
  }
})
アクセシビリティ属性を無視しない

PrimeVue は標準でアクセシビリティに対応しています。無効にしたり、無視したりしないでください。

誤り:

<template>
  
  <Button icon="pi pi-trash" @click="deleteItem" />

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

正しい例:アクセシビリティを維持する

<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>
すべてのコンポーネントに PassThrough をハードコードしない

コンポーネントごとに PT 設定を繰り返すと、重複が発生します。

誤り:


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


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


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

正しい例:グローバル PT またはラッパーコンポーネントを使用する

// 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 固有のガイドライン

Nuxt.js を使用する場合は、次のパターンも取り入れてください。

  • 自動インポート:Vue API とコンポーザブルには、Nuxt の自動インポートを活用します
  • useFetch/useAsyncData:SSR に対応したデータ読み込みには、Nuxt のデータ取得用コンポーザブルを使用します
  • definePageMeta:ページ単位のメタデータやミドルウェアに使用します
  • サーバールート:API エンドポイントには server/api/ を使用します
  • ランタイム設定:環境変数には useRuntimeConfig() を使用します

参考資料

Vue.js
Tailwind CSS
PrimeVue

コメント