Dark mode
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">×</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>