Tại sao kiểu dáng lại quan trọng 🎨
Mã nguồn làm cho ứng dụng hoạt động, nhưng kiểu dáng làm cho chúng hấp dẫn.
Hãy tưởng tượng một ứng dụng to-do hoạt động hoàn hảo — nhưng mọi thứ đều là phông chữ Times New Roman, ô checkbox và nút không được định dạng, trông như từ năm 1995. Nó hoạt động… nhưng không ai muốn sử dụng nó.
Đó là lý do tại sao kiểu dáng lại quan trọng. Một giao diện người dùng sạch sẽ, được thiết kế tốt giúp ứng dụng của bạn cảm thấy hiện đại, dễ sử dụng và đáng tin cậy.
Svelte không chỉ cung cấp cho bạn các thành phần phản ứng — nó còn cung cấp cho bạn một mô hình kiểu dáng tích hợp. Khác với React (nơi bạn thường phải sử dụng các công cụ bổ sung như CSS Modules hoặc styled-components), Svelte xem kiểu dáng là một yếu tố quan trọng, ngay trong các tệp .svelte
của bạn.
👉 Cuối cùng hướng dẫn này, bạn sẽ có thể:
- Viết CSS scoped chỉ áp dụng cho một thành phần.
- Thoát khỏi phạm vi với
:global
khi cần thiết. - Sử dụng class directives cho kiểu dáng động, phản ứng.
- Tăng cường quy trình làm việc của bạn với các tiện ích Tailwind CSS.
Hãy bắt đầu với nền tảng: kiểu dáng scoped — bí quyết của Svelte để tránh tình trạng CSS spaghetti.
Bước 1: CSS Scoped — Cách của Svelte
Mỗi thành phần Svelte (.svelte
file) có thể chứa một khối <style>
.
Sự khác biệt chính so với HTML/CSS thông thường:
- Trong HTML thông thường, thẻ
<style>
áp dụng toàn cầu trên toàn bộ trang. - Trong Svelte, thẻ
<style>
bên trong một thành phần là scoped: nó chỉ ảnh hưởng đến mã HTML trong cùng một tệp đó.
Điều này làm cho việc định dạng dựa trên thành phần trở nên an toàn và dự đoán được.
Ví dụ: Một nút được định dạng
src/lib/Button.svelte
svelte
<script>
// Đây là một thành phần nút tái sử dụng.
// "label" là một prop được truyền từ thành phần cha.
export let label = "Nhấn vào tôi";
</script>
<!-- Mã HTML sẽ được định dạng -->
<button>{label}</button>
<!--
🔽 Thẻ style này chỉ thuộc về *thành phần này*.
Nó sẽ không bị rò rỉ đến các <button> khác ở nơi khác trong ứng dụng của bạn.
-->
<style>
button {
background: royalblue;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 6px;
cursor: pointer;
}
</style>
Sử dụng thành phần
src/routes/+page.svelte
svelte
<script>
import Button from '$lib/Button.svelte';
</script>
<h1>Demo CSS Scoped</h1>
<!-- Hai nút, được định dạng giống nhau -->
<Button label="Lưu" />
<Button label="Hủy" />
<!-- Một nút HTML thông thường (không được định dạng bởi Button.svelte) -->
<button>Nút thông thường</button>
✅ Các thành phần <Button />
có kiểu dáng màu xanh hoàng gia.
✅ Nút <button>
ở cuối trông không được định dạng — chứng minh rằng kiểu dáng không bị rò rỉ ra ngoài.
✅ Bạn không cần tên lớp duy nhất như .button-primary
hoặc .button-danger
để tránh xung đột — việc scoping là tự động.
Bước 2: Cách hoạt động của Scope
Vậy làm thế nào để Svelte giữ cho kiểu dáng của bạn không bị rò rỉ ra ngoài?
Phía sau, trình biên dịch viết lại các bộ chọn CSS của bạn để trở nên duy nhất cho từng thành phần.
Ví dụ: Mã của bạn
Nếu bạn viết điều này trong Button.svelte
:
css
button {
background: royalblue;
}
Điều Svelte biên dịch thành
Svelte tạo ra một mã băm ngẫu nhiên (như svelte-xyz123
) và viết lại mã của bạn:
css
button.svelte-xyz123 {
background: royalblue;
}
Và phần tử <button>
trong DOM sẽ trở thành:
html
<button class="svelte-xyz123">Lưu</button>
✅ Kết quả: kiểu dáng chỉ áp dụng cho các nút trong thành phần này.
❌ Các <button>
khác ở nơi khác trong ứng dụng của bạn không bị ảnh hưởng.
Bạn không cần phải lo lắng về các lớp svelte-xyz123
đó — Svelte quản lý chúng tự động. Nhưng biết điều này giải thích tại sao CSS scoped "chỉ hoạt động".
👉 Đây là lý do tại sao các dự án Svelte không rơi vào tình trạng CSS spaghetti, nơi một stylesheet vô tình ghi đè lên một stylesheet khác.
Bước 3: Thoát khỏi phạm vi với :global
Theo mặc định, kiểu dáng bên trong một thành phần là scoped — chúng chỉ áp dụng cục bộ.
Nhưng thỉnh thoảng, bạn thực sự cần các kiểu dáng ảnh hưởng đến toàn bộ ứng dụng:
- Một reset CSS (loại bỏ các mặc định của trình duyệt).
- Kiểu dáng toàn cầu (đặt phông chữ và chiều cao dòng).
- Chủ đề (ví dụ: nền chế độ tối).
Đó là lúc :global
phát huy tác dụng.
Ví dụ: Một Reset Toàn Cầu
src/routes/+layout.svelte
svelte
<style>
/* Điều này nhắm đến <body> trên toàn bộ ứng dụng */
:global(body) {
margin: 0;
font-family: system-ui, sans-serif;
background: #fafafa;
}
</style>
<slot />
Bây giờ mỗi trang trong ứng dụng của bạn thừa hưởng các kiểu dáng toàn cầu này.
Ví dụ: Kết hợp Scoped và Global
Bạn cũng có thể kết hợp các bộ chọn scoped với các bộ chọn toàn cầu:
svelte
<style>
/* Chỉ <span> bên trong <button> của thành phần này được định dạng */
button :global(span) {
color: yellow;
}
</style>
<button>
Văn bản bình thường <span>nổi bật</span>
</button>
Ở đây, <button>
vẫn được scoped, nhưng span
bên trong nó được định dạng bằng một bộ chọn toàn cầu.
⚠️ Nguyên tắc chung:
Giữ scoped theo mặc định (an toàn, dự đoán được).
Sử dụng :global
chỉ cho reset, kiểu dáng chia sẻ, hoặc chủ đề toàn ứng dụng.
Bước 4: Các lớp động với class:
Directive
Các kiểu dáng tĩnh là tốt, nhưng các UI thực sự là động.
Hãy nghĩ về:
- Một nút “được chọn” trong thanh công cụ.
- Một tab “đang hoạt động” trong điều hướng.
- Một trường biểu mẫu chuyển sang màu đỏ khi không hợp lệ.
Bạn không muốn mã hóa cứng các lớp này — bạn muốn chúng phản ứng với trạng thái.
Ví dụ: Chuyển đổi lớp Active
src/lib/ToggleButton.svelte
svelte
<script>
let active = false; // trạng thái cục bộ
</script>
<!--
class:active={active} có nghĩa là:
→ thêm lớp "active" khi active === true
→ loại bỏ nó khi active === false
-->
<button
class:active={active}
on:click={() => active = !active}
>
{active ? "Đang hoạt động" : "Không hoạt động"}
</button>
<style>
/* Kiểu dáng bình thường */
button {
padding: 0.5rem 1rem;
border: none;
cursor: pointer;
}
/* Kiểu dáng thêm khi đang hoạt động */
.active {
background: green;
color: white;
}
</style>
✅ Bây giờ khi bạn nhấn nút, lớp active
tự động chuyển đổi bật và tắt.
❌ Nếu không có class:
, bạn sẽ phải viết một thứ lộn xộn như:
svelte
<button class={active ? "active" : ""}>...</button>
Svelte’s class:
directive giữ cho mã của bạn sạch, phản ứng và tuyên bố.
Bước 5: Nhiều Class Directives
Đôi khi một thành phần có thể có các biến thể hình ảnh khác nhau.
Hãy nghĩ về các nút: chính, nguy hiểm, thành công, viền… bạn không muốn tạo một thành phần mới cho mỗi loại.
Với Svelte, bạn có thể xếp chồng nhiều class:
directives để chuyển đổi các lớp khác nhau dựa trên trạng thái hoặc props.
Ví dụ: Các Biến Thể Nút
src/lib/VariantButton.svelte
svelte
<script>
// Các props của Svelte 5
let { type = "primary" } = $props(); // "primary" | "danger" | "success"
</script>
<button
type="button"
class:primary={type === "primary"}
class:danger={type === "danger"}
class:success={type === "success"}
>
{type === "primary" ? "Lưu" : type === "danger" ? "Xóa" : "OK"}
</button>
<style>
button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
color: white;
}
.primary {
background: royalblue;
}
.danger {
background: crimson;
}
.success {
background: seagreen;
}
</style>
Cách sử dụng
svelte
<script>
import VariantButton from '$lib/VariantButton.svelte';
</script>
<VariantButton type="primary" />
<VariantButton type="danger" />
<VariantButton type="success" />
✅ Các nút tự động chuyển đổi giữa màu xanh, đỏ và xanh lá dựa trên type
.
✅ Bạn có thể thêm bao nhiêu biến thể tùy thích (success
, warning
, v.v.).
❌ Nếu không có class directives, bạn sẽ cần phải nối các lớp một cách thủ công, điều này nhanh chóng trở nên lộn xộn.
Bước 6: Kiểu dáng nội tuyến
Hầu hết thời gian bạn sẽ sử dụng lớp CSS để định dạng, nhưng đôi khi bạn cần một kiểu dáng động nhanh — như kiểm soát chiều rộng, chiều cao hoặc màu sắc của một phần tử từ một biến.
Svelte cho phép bạn liên kết các kiểu dáng trực tiếp bên trong thuộc tính style
.
Ví dụ: Hộp Động
src/lib/DynamicBox.svelte
svelte
<script>
// Biến phản ứng kiểm soát kích thước
let size = $state(50);
</script>
<div
style="width: {size}px; height: {size}px; background: coral;"
></div>
<button type="button" onclick={() => size += 20}>
Tăng kích thước hộp
</button>
✅ Hộp sẽ lớn lên mỗi khi bạn nhấn nút.
✅ Kiểu dáng nội tuyến là hoàn toàn phản ứng — bất kỳ thay đổi nào đối với size
sẽ cập nhật DOM ngay lập tức.
⚠️ Thực hành tốt nhất:
Sử dụng kiểu dáng nội tuyến chỉ cho các giá trị rất động (như kích thước, vị trí hoặc biến đổi).
Đối với các kiểu dáng tái sử dụng (màu sắc, đệm, trạng thái hover), hãy giữ lại các lớp CSS, dễ duy trì và chủ đề hơn.
Tóm tắt nhanh
Cho đến nay, bạn đã học được:
- Các kiểu dáng Svelte là scoped theo mặc định.
:global
cho phép bạn thoát khỏi phạm vi cho các reset hoặc quy tắc chia sẻ.- Các chỉ thị
class:
chuyển đổi các lớp một cách phản ứng. - Kiểu dáng nội tuyến hoạt động cho các giá trị động.
Bạn đã nắm vững nền tảng của kiểu dáng Svelte: CSS scoped, ghi đè toàn cầu, chuyển đổi lớp và kiểu dáng nội tuyến. Với những công cụ này, bạn có thể xây dựng các thành phần sạch sẽ, tái sử dụng mà trông và hành động nhất quán.
👉 Trong bài viết tiếp theo, chúng ta sẽ đi sâu hơn với chuyển động và chủ đề - các hiệu ứng chuyển tiếp, hoạt hình, và các chiến lược để xử lý chế độ sáng/tối.