Skip to content

Tailwind CSS コンポーネントスタイリング

18.3.1. コンポーネント別スタイルパターン

コンポーネントスタイリングの基本原則は ユーティリティクラスの直接使用 です。

vue
<template>
  <div class="flex items-center gap-4 rounded-lg border border-gray-200 p-4">
    <img
      :src="user.avatar"
      :alt="user.name"
      class="h-12 w-12 rounded-full object-cover"
    />
    <div>
      <p class="text-sm font-semibold text-gray-900">{{ user.name }}</p>
      <p class="text-xs text-gray-500">{{ user.email }}</p>
    </div>
  </div>
</template>
  • 一般的なページコンポーネントではテンプレートにユーティリティクラスを直接記述します。
  • @apply は3回以上繰り返される再利用コンポーネントでのみ許可します。
  • <style scoped> ブロックにカスタム CSS を記述することは最小限にします。

18.3.2. 動的クラスバインディング

Vue の :class ディレクティブを使用して条件付きクラスを適用します。

オブジェクト構文

vue
<template>
  <button
    :class="{
      'bg-brand-600 text-white': isActive,
      'bg-gray-100 text-gray-700': !isActive,
    }"
    class="rounded-md px-4 py-2 text-sm font-medium"
    @click="toggle"
  >
    {{ label }}
  </button>
</template>

配列構文

vue
<template>
  <div :class="[baseClass, sizeClass, isDisabled ? 'opacity-50 cursor-not-allowed' : '']">
    <slot />
  </div>
</template>

computed の活用

クラスの組み合わせが複雑な場合は computed で分離します。

vue
<script setup lang="ts">
import { computed } from 'vue'

const props = defineProps<{
  status: 'success' | 'warning' | 'error'
}>()

const statusClass = computed(() => {
  const classMap: Record<string, string> = {
    success: 'bg-green-100 text-green-800 border-green-200',
    warning: 'bg-yellow-100 text-yellow-800 border-yellow-200',
    error: 'bg-red-100 text-red-800 border-red-200',
  }
  return classMap[props.status]
})
</script>

<template>
  <span :class="statusClass" class="inline-flex rounded-full border px-3 py-1 text-xs font-medium">
    <slot />
  </span>
</template>
  • 静的クラスは class 属性に、動的クラスは :class に分離して記述します。
  • 3つ以上の条件分岐が存在する場合は computed に抽出しなければなりません。

18.3.3. バリエーションパターン

再利用コンポーネントは props を通じてスタイルバリエーションを提供します。

vue
<script setup lang="ts">
import { computed } from 'vue'

const props = withDefaults(
  defineProps<{
    size?: 'sm' | 'md' | 'lg'
    variant?: 'primary' | 'secondary' | 'outline'
  }>(),
  {
    size: 'md',
    variant: 'primary',
  },
)

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

const variantClass = computed(() => {
  const map: Record<string, string> = {
    primary: 'bg-brand-600 text-white hover:bg-brand-700',
    secondary: 'bg-gray-600 text-white hover:bg-gray-700',
    outline: 'border border-brand-600 text-brand-600 hover:bg-brand-50',
  }
  return map[props.variant]
})
</script>

<template>
  <button
    :class="[sizeClass, variantClass]"
    class="inline-flex items-center justify-center rounded-md font-medium transition-colors
           focus:outline-none focus:ring-2 focus:ring-brand-500 focus:ring-offset-2"
  >
    <slot />
  </button>
</template>
  • バリエーション値はユニオン型で制限し、任意の値の受け渡しを防止します。
  • クラスマップは Record 型を使用して型安全性を確保します。
  • withDefaults でデフォルト値を明示し、props が省略された場合でも安定的にレンダリングします。

18.3.4. ダークモード

Tailwind CSS の dark: プレフィックスを使用してダークモードを実装します。

tailwind.config.tsdarkMode'class' に設定します。

ts
// tailwind.config.ts
export default {
  darkMode: 'class',
  // ...
} satisfies Config
  • media 戦略の代わりに class 戦略を使用しなければなりません。class 戦略はユーザーの選択を尊重します。

ダークモードクラスの適用

vue
<template>
  <div class="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
    <h2 class="text-lg font-bold text-gray-800 dark:text-gray-200">
      タイトル
    </h2>
    <p class="text-sm text-gray-600 dark:text-gray-400">
      本文の内容です。
    </p>
  </div>
</template>

テーマ切り替えの実装

vue
<script setup lang="ts">
import { ref, onMounted } from 'vue'

const isDark = ref(false)

function toggleTheme() {
  isDark.value = !isDark.value
  document.documentElement.classList.toggle('dark', isDark.value)
  localStorage.setItem('theme', isDark.value ? 'dark' : 'light')
}

onMounted(() => {
  const saved = localStorage.getItem('theme')
  isDark.value = saved === 'dark'
  document.documentElement.classList.toggle('dark', isDark.value)
})
</script>

<template>
  <button
    class="rounded-md p-2 text-gray-600 hover:bg-gray-100
           dark:text-gray-400 dark:hover:bg-gray-800"
    @click="toggleTheme"
  >
    {{ isDark ? 'ライトモード' : 'ダークモード' }}
  </button>
</template>
  • localStorage にユーザーの選択を保存し、リロード時にもテーマを維持します。
  • <html> 要素の dark クラスをトグルして、ページ全体に適用します。

TIENIPIA QUALIFIED STANDARD