Skip to content
Grok

Styling in Vue Components

1. Built-in Styling

Scoped Styles

vue
<template>
  <button class="btn">Click me</button>
</template>

<style scoped>
/* 
  สไตล์ในส่วนนี้จะถูกจำกัดเฉพาะ component นี้เท่านั้น
  Vue จะเพิ่ม data attribute ให้โดยอัตโนมัติ
*/
.btn {
  padding: 0.5rem 1rem;
  background-color: var(--primary-color);
  color: white;
  border-radius: 0.25rem;
  transition: background-color 0.2s;
}

/* ใช้ :deep() สำหรับสไตล์ child components */
:deep(.child-component) {
  border: 1px solid #eee;
}

/* ใช้ :slotted() สำหรับสไตล์ slotted content */
:slotted(p) {
  margin-bottom: 1rem;
}

/* ใช้ :global() สำหรับสไตล์ global ภายใน scoped style */
:global(.some-global-class) {
  font-weight: bold;
}
</style>

Global Styles

vue
<template>
  <div class="app">
    <h1>My App</h1>
  </div>
</template>

<style>
/* 
  สไตล์ในส่วนนี้จะเป็น global ทั้งแอป 
  ควรใช้เฉพาะเมื่อจำเป็นเท่านั้น
*/
body {
  margin: 0;
  font-family: "Segoe UI", system-ui, sans-serif;
  line-height: 1.5;
}

/* ใช้ CSS Custom Properties สำหรับ theme */
:root {
  --primary-color: #42b983;
  --secondary-color: #2c3e50;
  --danger-color: #e74c3c;
}

/* ใช้ @layer สำหรับจัดการ specificity */
@layer base {
  h1, h2, h3, h4 {
    @apply font-bold text-gray-900;
  }
}
</style>

Dynamic Classes

vue
<template>
  <div>
    <!-- Static class -->
    <div class="static-class">Static</div>

    <!-- Dynamic class -->
    <div :class="dynamicClass">Dynamic</div>

    <!-- Array syntax -->
    <div :class="['base-class', { active: isActive }]">
      Array Syntax
    </div>

    <!-- Object syntax -->
    <div
      :class="
        {
          'text-red-500': hasError,
          'font-bold': isImportant,
          'underline': isUnderlined,
        }
      "
    >
      Object Syntax
    </div>

    <!-- Computed property -->
    <div :class="classObject">Computed</div>
  </div>
</template>

<script setup lang="ts">
import { computed, ref } from "vue";

const isActive = ref(true);
const hasError = ref(false);
const isImportant = ref(true);
const isUnderlined = ref(true);

// Dynamic class
const dynamicClass = "dynamic-class";

// Computed class
const classObject = computed(() => ({
  "text-blue-500": isActive.value && !hasError.value,
  "text-red-500": hasError.value,
  "font-bold": isImportant.value,
  "opacity-50": !isActive.value,
}));
</script>

<style scoped>
.static-class {
  padding: 0.5rem;
  margin-bottom: 0.5rem;
  border: 1px solid #ddd;
}

.dynamic-class {
  background-color: #f0f0f0;
  padding: 0.5rem;
  margin-bottom: 0.5rem;
  border-left: 3px solid var(--primary-color);
}

.text-red-500 {
  color: #ef4444;
}

.text-blue-500 {
  color: #3b82f6;
}

.font-bold {
  font-weight: 700;
}

.underline {
  text-decoration: underline;
}

.opacity-50 {
  opacity: 0.5;
}
</style>

2. CSS Pre-processors

SCSS/SASS

vue
<template>
  <div class="card">
    <h3 class="card__title">Card Title</h3>
    <p class="card__content">Card content goes here</p>
  </div>
</template>

<style lang="scss" scoped>
// ใช้ nesting และ features อื่นๆ ของ SCSS
.card {
  border: 1px solid #e2e8f0;
  border-radius: 0.5rem;
  padding: 1.5rem;
  background: white;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);

  &__title {
    margin-top: 0;
    color: #1a202c;
    font-size: 1.25rem;
    margin-bottom: 0.75rem;
  }

  &__content {
    margin: 0;
    color: #4a5568;
    line-height: 1.6;
  }

  // Media queries
  @media (min-width: 768px) {
    padding: 2rem;
  }
}
</style>

Less

vue
<style lang="less" scoped>
@primary-color: #42b983;
@border-radius: 4px;

.button {
  display: inline-block;
  padding: 8px 16px;
  border-radius: @border-radius;
  background-color: @primary-color;
  color: white;
  text-decoration: none;
  transition: background-color 0.2s;

  &:hover {
    background-color: darken(@primary-color, 10%);
  }

  &.outline {
    background: transparent;
    border: 1px solid @primary-color;
    color: @primary-color;
  }
}
</style>

3. Utility-First CSS Frameworks

Tailwind CSS

vue
<template>
  <div class="container mx-auto p-4">
    <div
      class="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl"
    >
      <div class="md:flex">
        <div class="md:shrink-0">
          <img
            class="h-48 w-full object-cover md:h-full md:w-48"
            src="/img/image.jpg"
            alt="Modern building"
          >
        </div>
        <div class="p-8">
          <div
            class="uppercase tracking-wide text-sm text-indigo-500 font-semibold"
          >
            Company Retreats
          </div>
          <a
            href="#"
            class="block mt-1 text-lg leading-tight font-medium text-black hover:underline"
          >Incredible accommodation for your team</a>
          <p class="mt-2 text-slate-500">
            Looking to take your team away on a retreat to enjoy awesome food
            and take in some sunshine? We have a list of places to do just that.
          </p>
          <div class="mt-4">
            <span class="text-slate-900 font-medium">$1,420</span>
            <span class="text-slate-500 text-sm"> / night</span>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<!-- ใช้ @apply ใน style block -->
<style>
.custom-card {
  @apply bg-white rounded-lg shadow-md p-6 transition-shadow duration-300
    hover:shadow-lg;
}
</style>

UnoCSS

vue
<template>
  <div class="flex flex-col items-center p-4">
    <h1 class="text-2xl font-bold text-gray-900 mb-4">UnoCSS Example</h1>

    <button class="btn">
      <i class="i-mdi-weather-sunny text-lg"></i>
      <span>Toggle Theme</span>
    </button>

    <div
      class="mt-6 p-4 rounded-lg bg-gray-50 dark:bg-gray-800 transition-colors"
    >
      <p class="text-gray-700 dark:text-gray-300">
        This is a responsive card with dark mode support.
      </p>
    </div>
  </div>
</template>

<style>
/* ใช้ @apply ใน style block */
.btn {
  @apply px-4 py-2 rounded-md bg-blue-500 text-white flex items-center gap-2
    hover:bg-blue-600 transition-colors;
}

/* ใช้ layer components */
@layer components {
  .card {
    @apply bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 transition-all
      duration-300 hover:shadow-lg;
  }
}

/* ใช้ custom variants */
@variants hover, focus {
  .scale-105 {
    transform: scale(1.05);
  }
  .scale-110 {
    transform: scale(1.10);
  }
}
</style>

4. CSS Modules

vue
<template>
  <div :class="$style.container">
    <h3 :class="$style.title">CSS Modules</h3>
    <p :class="[$style.text, { [$style.highlight]: isHighlighted }]">
      This text can be highlighted
    </p>
    <button
      :class="$style.button"
      @click="isHighlighted = !isHighlighted"
    >
      Toggle Highlight
    </button>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";

const isHighlighted = ref(false);
</script>

<style module>
.container {
  max-width: 600px;
  margin: 0 auto;
  padding: 1.5rem;
  border-radius: 0.5rem;
  background-color: #f8fafc;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

.title {
  margin-top: 0;
  color: #1e293b;
  font-size: 1.25rem;
  margin-bottom: 1rem;
}

.text {
  color: #475569;
  line-height: 1.6;
  margin-bottom: 1rem;
}

.highlight {
  background-color: #fef9c3;
  padding: 0.25rem 0.5rem;
  border-radius: 0.25rem;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.5rem 1rem;
  background-color: #3b82f6;
  color: white;
  border: none;
  border-radius: 0.375rem;
  font-weight: 500;
  cursor: pointer;
  transition: background-color 0.2s;
}

.button:hover {
  background-color: #2563eb;
}
</style>

5. Best Practices

1. Organizing Styles

styles/
├── base/             # Base styles (resets, typography)
│   ├── _reset.css
│   ├── _typography.css
│   └── _animations.css
├── components/       # Component-specific styles
│   ├── _button.css
│   ├── _card.css
│   └── _form.css
├── layouts/          # Layout styles
│   ├── _header.css
│   └── _footer.css
├── utils/            # Utility classes
│   ├── _variables.css
│   └── _mixins.css
└── main.css          # Main stylesheet (imports all others)

2. Performance Optimization

vue
<template>
  <div>
    <!-- ใช้ v-show แทน v-if เมื่อมีการสลับบ่อย -->
    <div v-show="isVisible">Content</div>

    <!-- ใช้ v-once สำหรับ element ที่ไม่มีการเปลี่ยนแปลง -->
    <h1 v-once>{{ title }}</h1>

    <!-- ใช้ v-memo สำหรับ optimize การ render -->
    <div v-memo="[user.id]">
      {{ user.name }}
    </div>
  </div>
</template>

<style>
/* ใช้ contain: content สำหรับคอมโพเนนต์ที่แยกส่วนชัดเจน */
.component {
  contain: content;
}

/* ใช้ will-change เฉพาะเมื่อจำเป็น */
.animated {
  will-change: transform, opacity;
}
</style>

3. Accessibility

vue
<template>
  <button
    class="btn"
    aria-label="Close dialog"
    @click="closeDialog"
  >
    <span aria-hidden="true">&times;</span>
  </button>

  <div
    class="visually-hidden"
    role="alert"
    aria-live="polite"
  >
    Form submitted successfully
  </div>
</template>

<style>
/* ซ่อน element จาก viewport แต่ยังให้ screen reader อ่านได้ */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* เพิ่ม focus styles สำหรับ keyboard navigation */
button:focus-visible {
  outline: 2px solid #3b82f6;
  outline-offset: 2px;
}
</style>

6. Resources

Official Documentation

CSS Frameworks

CSS Pre-processors

Tools

Learning Resources