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.ts에서 darkMode'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