CSS vanilla (thuần) có một vấn đề: nó lặp lại nhiều. Mỗi khi đổi màu primary, bạn phải tìm và thay đổi ở 20 chỗ. Mỗi lần tạo button, bạn lại viết 10 dòng CSS giống nhau.
SCSS/SASS (CSS Preprocessor) giải quyết vấn đề này bằng cách thêm các tính năng lập trình vào CSS: variables, functions, mixins, nesting… Sau đó, nó tự động compile thành CSS thuần để trình duyệt hiểu được.
Bài này sẽ dạy bạn cách dùng SCSS để viết CSS DRY (Don’t Repeat Yourself), dễ bảo trì, và mạnh mẽ hơn.
SCSS vs CSS vs SASS
| Tính năng | CSS | SASS | SCSS |
|---|---|---|---|
| Syntax | CSS tiêu chuẩn | Không cần braces {} | Giống CSS |
| Variables | ❌ Không | ✅ Có | ✅ Có |
| Nesting | ❌ Không | ✅ Có | ✅ Có |
| Mixins | ❌ Không | ✅ Có | ✅ Có |
| Functions | ❌ Không | ✅ Có | ✅ Có |
| File extension | .css | .sass | .scss |
| Sử dụng phổ biến | Cơ bản | Ít hơn | ✅ Phổ biến nhất |
💡 Khuyến khích: Dùng SCSS vì nó có syntax giống CSS (dễ học) nhưng có tất cả tính năng của SASS.
Setup SCSS – Cài đặt
Cách 1: Online Compiler (Nhanh nhất)
Truy cập https://sassmeister.com hoặc https://jsoncrack.com/scss để test SCSS online mà không cần cài đặt.
Cách 2: Node-sass (Local)
# Cài đặt node-sass
npm install -g node-sass
# Hoặc dùng sass package (mới hơn)
npm install -g sass
# Compile .scss thành .css
scss input.scss output.css
# Watch mode: tự động compile khi file thay đổi
scss --watch . --pollCách 3: VS Code Extension
Cài đặt extension “Live Sass Compiler” trong VS Code → click “Watch Sass” → tự động compile.
Variables – Biến trong SCSS
Variables lưu giữ giá trị để tái sử dụng. Khác với CSS custom properties, SCSS variables compile-time (thay thế khi compile).
/* ========== VARIABLES ========== */
// Colors
$color-primary: #3498db;
$color-secondary: #2c3e50;
$color-error: #e74c3c;
$color-success: #27ae60;
// Spacing
$spacing-xs: 8px;
$spacing-sm: 12px;
$spacing-md: 20px;
$spacing-lg: 40px;
// Font sizes
$font-size-sm: 14px;
$font-size-base: 16px;
$font-size-lg: 20px;
$font-size-xl: 28px;
// Border radius
$border-radius: 8px;
// Shadows
$shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1);
$shadow-md: 0 4px 12px rgba(0, 0, 0, 0.15);
$shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.2);
/* ========== USAGE ========== */
body {
color: $color-primary;
font-size: $font-size-base;
}
.button {
background-color: $color-primary;
padding: $spacing-sm $spacing-md;
border-radius: $border-radius;
box-shadow: $shadow-sm;
}
.button--error {
background-color: $color-error;
}
.container {
padding: $spacing-lg;
}
h1 {
font-size: $font-size-xl;
margin-bottom: $spacing-md;
}Lợi ích Variables
- ✅ DRY: Giá trị lưu một nơi, dùng ở nhiều nơi
- ✅ Dễ bảo trì: Đổi 1 variable = đổi tất cả
- ✅ Dễ đọc: $color-primary dễ hiểu hơn #3498db
- ✅ Consistent: Đảm bảo tính nhất quán design
Nesting – Lồng selectors
Nesting cho phép bạn lồng CSS selectors, giảm lặp lại parent selector.
/* ❌ CSS (lặp lại .card nhiều lần) */
.card {
background: white;
border-radius: 8px;
}
.card:hover {
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
}
.card__title {
font-size: 20px;
margin-bottom: 10px;
}
.card__description {
color: #666;
}
/* ✅ SCSS (clean, không lặp lại) */
.card {
background: white;
border-radius: 8px;
&:hover {
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
}
&__title {
font-size: 20px;
margin-bottom: 10px;
}
&__description {
color: #666;
}
}Ampersand (&) – Tham chiếu parent selector
.button {
padding: 10px 20px;
background-color: $color-primary;
/* &:hover = .button:hover */
&:hover {
background-color: darken($color-primary, 10%);
}
/* &:focus = .button:focus */
&:focus {
outline: 2px solid $color-primary;
}
/* &--secondary = .button--secondary */
&--secondary {
background-color: $color-secondary;
}
/* &__text = .button__text */
&__text {
font-weight: bold;
}
/* .icon & = .icon .button */
.icon & {
padding: 8px 16px;
}
}Compile result (CSS)
/* Kết quả sau khi compile từ SCSS trên */
.button {
padding: 10px 20px;
background-color: #3498db;
}
.button:hover {
background-color: #2980b9;
}
.button:focus {
outline: 2px solid #3498db;
}
.button--secondary {
background-color: #2c3e50;
}
.button__text {
font-weight: bold;
}
.icon .button {
padding: 8px 16px;
}Mixins – Tái sử dụng code
Mixins là CSS blocks mà bạn có thể tái sử dụng. Giống như functions.
/* ========== DEFINE MIXIN ========== */
// Flexbox centering
@mixin flex-center {
display: flex;
justify-content: center;
align-items: center;
}
// Responsive text (clamp)
@mixin responsive-text($min, $max) {
font-size: clamp($min, 5vw, $max);
}
// Button styles
@mixin button-base {
padding: 10px 20px;
border: none;
border-radius: $border-radius;
cursor: pointer;
font-weight: bold;
transition: all 0.3s;
}
// Button variant
@mixin button-variant($bg-color) {
background-color: $bg-color;
color: white;
&:hover {
background-color: darken($bg-color, 10%);
}
}
// Truncate text
@mixin truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
// Shadow
@mixin shadow($level: 'md') {
@if $level == 'sm' {
box-shadow: $shadow-sm;
} @else if $level == 'md' {
box-shadow: $shadow-md;
} @else {
box-shadow: $shadow-lg;
}
}
/* ========== USE MIXIN ========== */
.flex-container {
@include flex-center;
height: 200px;
}
.heading {
@include responsive-text(24px, 48px);
}
.button {
@include button-base;
@include button-variant($color-primary);
}
.button--secondary {
@include button-base;
@include button-variant($color-secondary);
}
.button--error {
@include button-base;
@include button-variant($color-error);
}
.card-title {
@include truncate;
}
.card {
@include shadow('md');
}Ví dụ hoàn chỉnh – Component Button with SCSS
/* ========== VARIABLES ========== */
$colors: (
'primary': #3498db,
'secondary': #2c3e50,
'error': #e74c3c,
'success': #27ae60,
);
$sizes: (
'sm': (padding: 8px 16px, font-size: 14px),
'md': (padding: 12px 20px, font-size: 16px),
'lg': (padding: 16px 28px, font-size: 18px),
);
$spacing-base: 20px;
$border-radius: 8px;
/* ========== MIXINS ========== */
@mixin button-base {
border: none;
border-radius: $border-radius;
cursor: pointer;
font-weight: 600;
transition: all 0.3s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
&:active {
transform: translateY(0);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
}
@mixin button-color($color) {
background-color: $color;
color: white;
&:hover:not(:disabled) {
background-color: darken($color, 10%);
}
}
@mixin button-size($size) {
$padding: map-get($size, 'padding');
$font-size: map-get($size, 'font-size');
padding: $padding;
font-size: $font-size;
}
/* ========== COMPONENT ========== */
.button {
@include button-base;
@include button-color(map-get($colors, 'primary'));
@include button-size(map-get($sizes, 'md'));
// Variants
&--secondary {
@include button-color(map-get($colors, 'secondary'));
}
&--error {
@include button-color(map-get($colors, 'error'));
}
&--success {
@include button-color(map-get($colors, 'success'));
}
// Sizes
&--sm {
@include button-size(map-get($sizes, 'sm'));
}
&--lg {
@include button-size(map-get($sizes, 'lg'));
}
// Outline style
&--outline {
background-color: transparent;
border: 2px solid map-get($colors, 'primary');
color: map-get($colors, 'primary');
&:hover:not(:disabled) {
background-color: map-get($colors, 'primary');
color: white;
}
}
}Functions – Hàm trong SCSS
/* ========== FUNCTIONS ========== */
// Calculate REM from PX
@function rem($pixels) {
@return $pixels / 16 * 1rem;
}
// Lighten color
@function lighter($color, $amount: 10%) {
@return lighten($color, $amount);
}
// Get color from map
@function color($name) {
@return map-get($colors, $name);
}
/* ========== USAGE ========== */
body {
font-size: rem(16);
margin: rem(20);
}
.heading {
color: color('primary');
font-size: rem(28);
}
.text-light {
color: lighter(color('secondary'), 20%);
}Import & Organize – Chia file SCSS
/* ========== main.scss ========== */
// 1. Variables & Functions
@import 'variables';
@import 'functions';
// 2. Mixins
@import 'mixins';
// 3. Base/Reset
@import 'base';
@import 'typography';
// 4. Layout
@import 'layout/header';
@import 'layout/footer';
@import 'layout/container';
// 5. Components
@import 'components/button';
@import 'components/card';
@import 'components/form';
// 6. Utilities
@import 'utilities';File structure:
scss/
├── main.scss # Main file (imports all)
├── _variables.scss # Colors, spacing, fonts
├── _functions.scss # Reusable functions
├── _mixins.scss # Reusable mixins
├── _base.scss # Reset, global styles
├── _typography.scss # Font, heading styles
├── layout/
│ ├── _header.scss
│ ├── _footer.scss
│ └── _container.scss
├── components/
│ ├── _button.scss
│ ├── _card.scss
│ └── _form.scss
└── _utilities.scss # Utility classes💡 Naming convention: Tên file SCSS bắt đầu với underscore (_) khi import (ví dụ: _variables.scss). File chính không có underscore (main.scss).
Advanced Features
1. For Loop
/* Generate margin utilities */
@for $i from 1 through 5 {
.m-#{$i} {
margin: $i * 10px;
}
}
/* Generates: .m-1, .m-2, .m-3, ... */2. Each Loop (Iterate over map)
$colors: (
'primary': #3498db,
'secondary': #2c3e50,
'error': #e74c3c,
);
@each $name, $color in $colors {
.bg-#{$name} {
background-color: $color;
}
}
/* Generates: .bg-primary, .bg-secondary, .bg-error */3. If/Else Logic
@mixin responsive-padding($size) {
@if $size == 'mobile' {
padding: 10px;
} @else if $size == 'tablet' {
padding: 20px;
} @else {
padding: 40px;
}
}SCSS vs CSS Custom Properties
| Tính năng | SCSS Variables | CSS Custom Properties |
|---|---|---|
| Syntax | $color-primary | var(--color-primary) |
| Compile time | Thay thế khi compile | Runtime (trong browser) |
| Scope | Lexical scope | CSS scope (cascade) |
| Dynamic | Static | Có thể thay đổi via JS |
| Math | Hỗ trợ | Cần calc() |
| Khi dùng | Build-time configuration | Dynamic theming |
/* Dùng cả hai kết hợp */
// SCSS variables (static)
$spacing-unit: 8px;
:root {
// CSS variables (dynamic)
--color-primary: #3498db;
--color-secondary: #2c3e50;
}
.button {
// SCSS variable cho spacing
padding: $spacing-unit * 2;
// CSS variable cho color
color: var(--color-primary);
}Tóm tắt
- ✅ SCSS/SASS: CSS Preprocessor với variables, mixins, functions
- ✅ Variables: $color-primary, $spacing-md…
- ✅ Nesting: Lồng selectors, dùng & để tham chiếu parent
- ✅ Mixins: @mixin + @include để tái sử dụng code
- ✅ Functions: Hàm tính toán, lighten, darken…
- ✅ Loops: @for, @each để generate code
- ✅ Organization: Import files theo phần: variables, mixins, layout, components…
- ✅ DRY: Viết ít hơn, maintain dễ hơn
- ✅ Compile: SCSS → CSS (trình duyệt dùng CSS)
🎯 Bài tập thực hành
Bài tập 1: Chuyển đổi CSS vanilla sang SCSS:
- Lấy file CSS từ dự án cũ
- Tạo variables cho colors, spacing, font-sizes
- Chuyển CSS thành SCSS với nesting
- Compile SCSS → CSS
- Kiểm tra output CSS đúng không
Bài tập 2: Tạo component library với SCSS:
- Tạo _variables.scss (colors, spacing, sizes)
- Tạo _mixins.scss (button-base, button-color…)
- Tạo components/_button.scss (tất cả button variants)
- Tạo components/_card.scss (card styles)
- Tạo main.scss import tất cả
- Test: compile SCSS, kiểm tra output CSS
Bài tập 3: Tạo responsive utility classes với SCSS loops:
- Dùng @for loop generate spacing utilities (.m-1, .m-2…)
- Dùng @each loop generate color utilities (.bg-primary, .bg-secondary…)
- Dùng @for + media query generate responsive padding
- Compile → CSS → test trên HTML
Chúc bạn thành công! 🚀