SMACSS (Scalable and Modular Architecture for CSS)
SMACSS was created by Jonathan Snook in 2011, born out of his work at Yahoo Mail. Unlike BEM (a naming convention) or OOCSS (two principles), SMACSS is a complete CSS organization system — it classifies every CSS rule into one of five categories, each with specific rules about where it lives, how it's named, and how it interacts with other categories.
"SMACSS is more style guide than rigid framework." — Jonathan Snook
1. The Five SMACSS Categories
┌──────────────────────────────────────────────────────────┐
│ Category │ Purpose │ Prefix │
├──────────────────────────────────────────────────────────┤
│ 1. Base │ Element defaults │ (none) │
│ 2. Layout │ Page skeleton structure │ l- or # │
│ 3. Module │ Reusable UI components │ (none) │
│ 4. State │ JS-driven state changes │ is- has- │
│ 5. Theme │ Visual skin/color layer │ theme- │
└──────────────────────────────────────────────────────────┘
2. Category 1: Base Rules
Base rules are the default styling for HTML elements — what the page looks like before any class is applied. Think: CSS reset + sensible defaults.
- What goes here: Element selectors, attribute selectors, pseudo-classes on elements.
- What NEVER goes here: Class selectors, ID selectors, or
!important.
/* ── Base Rules ── */
/* Reset */
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* Root tokens */
:root {
--font-body: 'Inter', system-ui, sans-serif;
--font-heading: 'Playfair Display', Georgia, serif;
--color-text: #f0f0f0;
--color-bg: #0f0f0f;
--color-primary: hsl(245, 80%, 62%);
}
/* Global element defaults */
html { scroll-behavior: smooth; }
body {
font-family: var(--font-body);
font-size: 1rem;
line-height: 1.6;
color: var(--color-text);
background: var(--color-bg);
-webkit-font-smoothing: antialiased;
}
/* Typography defaults */
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-heading);
font-weight: 700;
line-height: 1.2;
color: var(--color-text);
}
h1 { font-size: clamp(2rem, 5vw, 4rem); }
h2 { font-size: clamp(1.5rem, 3vw, 2.5rem); }
h3 { font-size: clamp(1.25rem, 2vw, 1.75rem); }
h4 { font-size: 1.25rem; }
p { line-height: 1.7; color: var(--color-text-muted, #888); }
a { color: var(--color-primary); text-decoration: none; transition: color 0.2s; }
a:hover { color: var(--color-primary-dark, #5048d4); }
img, video, svg { max-width: 100%; height: auto; display: block; }
ul, ol { list-style: none; }
button, input, textarea, select {
font-family: inherit;
font-size: inherit;
}
code, pre {
font-family: var(--font-mono, monospace);
font-size: 0.875em;
}
pre {
background: rgba(255,255,255,0.05);
border: 1px solid rgba(255,255,255,0.08);
border-radius: 8px;
padding: 1.5rem;
overflow-x: auto;
}
3. Category 2: Layout Rules
Layout rules define the major structural sections of the page — the skeleton that contains modules. They answer: "Where do the big sections go?"
- Prefix:
l-or use ID selectors (#header,#sidebar) for unique page elements. - Characteristics: Usually one instance per page, rarely change, purely structural.
/* ── Layout Rules ── */
/* Page wrapper */
.l-page {
min-height: 100dvh;
display: flex;
flex-direction: column;
}
/* Two-column layout */
.l-two-column {
display: grid;
grid-template-columns: 1fr 280px;
gap: 3rem;
max-width: 1200px;
margin: 0 auto;
padding: 0 clamp(1rem, 4vw, 2rem);
align-items: flex-start;
}
/* Three column layout */
.l-three-column {
display: grid;
grid-template-columns: 240px 1fr 200px;
gap: 2rem;
max-width: 1400px;
margin: 0 auto;
padding: 0 2rem;
}
/* Blog/article layout */
.l-content { min-width: 0; }
.l-sidebar { position: sticky; top: 80px; }
/* Centered container (most common layout rule) */
.l-container {
width: 100%;
max-width: 1200px;
margin-left: auto;
margin-right: auto;
padding-left: clamp(1rem, 4vw, 2rem);
padding-right: clamp(1rem, 4vw, 2rem);
}
.l-container--narrow { max-width: 720px; }
.l-container--wide { max-width: 1440px; }
/* Section spacing */
.l-section {
padding-block: clamp(3rem, 8vw, 8rem);
}
/* Flexbox row layout */
.l-row {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
}
/* Grid layouts */
.l-grid-2 { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1.5rem; }
.l-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem; }
.l-grid-4 { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1.5rem; }
.l-grid-auto { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1.5rem; }
/* Responsive collapse */
@media (max-width: 1024px) { .l-three-column { grid-template-columns: 240px 1fr; } }
@media (max-width: 768px) {
.l-two-column { grid-template-columns: 1fr; }
.l-three-column { grid-template-columns: 1fr; }
.l-grid-2, .l-grid-3, .l-grid-4 { grid-template-columns: 1fr; }
}
4. Category 3: Module Rules
Modules are the reusable, portable components — everything that isn't layout or base. They should work anywhere in the page regardless of their container.
- Naming:
.module-name— no prefix. - Sub-modules:
.module-name-sub— hyphen-separated. - Modifiers:
.module-name--variant(adapted from BEM for module variants).
/* ── Module: card ── */
.card {
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: var(--radius-lg);
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
/* Sub-modules */
.card-image { width: 100%; aspect-ratio: 16/9; object-fit: cover; display: block; }
.card-body { padding: 1.5rem; }
.card-title { font-size: 1.25rem; font-weight: 700; margin-bottom: 0.5rem; color: var(--color-text); }
.card-excerpt { color: var(--color-text-muted); font-size: 0.9rem; line-height: 1.6; }
.card-footer { padding: 1rem 1.5rem; border-top: 1px solid var(--color-border); display: flex; justify-content: space-between; align-items: center; }
/* Module variations (modifier-like) */
.card-featured { border-color: var(--color-primary); box-shadow: 0 0 0 1px var(--color-primary); }
.card-dark { background: var(--color-bg); }
.card-horizontal { display: flex; }
.card-horizontal .card-image { width: 160px; height: auto; aspect-ratio: auto; flex-shrink: 0; }
/* ── Module: btn ── */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.7rem 1.5rem;
border-radius: var(--radius-full, 9999px);
font-weight: 600;
font-size: 1rem;
cursor: pointer;
border: 2px solid transparent;
line-height: 1;
text-decoration: none;
transition: all 0.2s ease;
white-space: nowrap;
}
.btn:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 3px; }
.btn:disabled { opacity: 0.5; pointer-events: none; }
/* Sub-modules: variants */
.btn-primary { background: var(--color-primary); color: white; }
.btn-primary:hover { background: var(--color-primary-dark); }
.btn-outline { border-color: var(--color-primary); color: var(--color-primary); }
.btn-outline:hover { background: var(--color-primary); color: white; }
.btn-ghost { color: var(--color-text-muted); }
.btn-ghost:hover { background: rgba(255,255,255,0.05); color: var(--color-text); }
.btn-danger { background: var(--color-danger); color: white; }
/* Sub-modules: sizes */
.btn-sm { padding: 0.4rem 1rem; font-size: 0.875rem; }
.btn-lg { padding: 1rem 2rem; font-size: 1.125rem; }
.btn-xl { padding: 1.25rem 2.5rem; font-size: 1.25rem; }
/* ── Module: nav ── */
.nav {
position: sticky;
top: 0;
z-index: 200;
background: rgba(15, 15, 15, 0.85);
backdrop-filter: blur(20px);
border-bottom: 1px solid var(--color-border);
}
.nav-container { display: flex; align-items: center; justify-content: space-between; height: 70px; }
.nav-logo { font-size: 1.5rem; font-weight: 700; color: var(--color-text); }
.nav-list { display: flex; gap: 0.5rem; list-style: none; }
.nav-link { padding: 0.5rem 0.875rem; color: var(--color-text-muted); border-radius: 6px; transition: all 0.2s; font-size: 0.9rem; }
.nav-link:hover { color: var(--color-text); background: rgba(255,255,255,0.06); }
.nav-link-active { color: var(--color-primary); font-weight: 600; }
/* ── Module: hero ── */
.hero {
min-height: 100dvh;
display: flex;
align-items: center;
justify-content: center;
position: relative;
overflow: hidden;
text-align: center;
padding: 6rem 1.5rem;
}
.hero-title { font-size: clamp(3rem, 8vw, 6rem); font-weight: 700; line-height: 1.05; margin-bottom: 1.5rem; color: var(--color-text); }
.hero-subtitle { font-size: clamp(1rem, 2vw, 1.3rem); color: var(--color-text-muted); max-width: 640px; margin: 0 auto 2.5rem; line-height: 1.6; }
.hero-actions { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; }
5. Category 4: State Rules
State rules describe how a module or layout looks in a particular dynamic state — usually toggled by JavaScript interaction. They augment or override the module/layout styling.
- Prefix: Always
is-orhas-(SMACSS defines this strongly). - Scope: Applied by JS — added/removed dynamically.
- Rule: They can override module styles but should NEVER define structural properties from scratch.
/* ── State Rules ── */
/* Visibility states */
.is-hidden { display: none !important; }
.is-visible { display: block !important; }
.is-invisible { visibility: hidden; pointer-events: none; }
.is-transparent { opacity: 0; }
/* Active/selected states */
.is-active { color: var(--color-primary) !important; }
.is-selected { background: rgba(108, 99, 255, 0.12); border-color: var(--color-primary); }
.is-highlighted { background: rgba(108, 99, 255, 0.08); }
.is-current { font-weight: 700; color: var(--color-text) !important; }
/* Interactive states */
.is-loading {
pointer-events: none;
opacity: 0.6;
cursor: wait;
}
.is-loading::after {
content: '';
display: inline-block;
margin-left: 0.5rem;
width: 0.875rem; height: 0.875rem;
border: 2px solid rgba(255,255,255,0.2);
border-top-color: currentColor;
border-radius: 50%;
animation: spin 0.7s linear infinite;
vertical-align: middle;
}
.is-disabled { opacity: 0.4 !important; pointer-events: none !important; cursor: not-allowed !important; }
.is-collapsed { max-height: 0; overflow: hidden; }
.is-expanded { max-height: none; }
/* Form states */
.is-valid { border-color: var(--color-success) !important; }
.is-invalid { border-color: var(--color-danger) !important; }
.is-focused { border-color: var(--color-primary) !important; box-shadow: 0 0 0 3px rgba(108,99,255,0.2) !important; }
/* UI states */
.is-open { display: block; }
.is-closed { display: none; }
.is-sticky { position: fixed; top: 0; }
/* Error state ownership */
.has-error { }
.has-error .form-input { border-color: var(--color-danger); }
.has-error .form-label { color: var(--color-danger); }
.has-error .form-message { display: block; }
/* Success state */
.has-success { }
.has-success .form-input { border-color: var(--color-success); }
@keyframes spin { to { transform: rotate(360deg); } }
State rules in JavaScript:
// Toggle state classes — clean SMACSS pattern
const nav = document.querySelector('.nav');
const menuBtn = document.querySelector('.js-menu-toggle');
menuBtn.addEventListener('click', () => {
nav.classList.toggle('is-open');
menuBtn.classList.toggle('is-active');
});
// Loading state
const form = document.querySelector('form');
form.addEventListener('submit', () => {
form.classList.add('is-loading');
form.querySelector('[type=submit]').classList.add('is-loading');
});
6. Category 5: Theme Rules
Theme rules define the visual layer that changes between themes (dark/light, brand A/brand B). They override colors, fonts, and decorative properties — never structure or layout.
- Prefix:
theme-(or custom per project). - Scope: Applied at the root level to switch the entire visual appearance.
/* ── Theme Rules ── */
/* Default theme tokens (dark theme in this example) */
:root {
--color-bg: #0f0f0f;
--color-surface: #1a1a1a;
--color-border: rgba(255, 255, 255, 0.08);
--color-text: #f0f0f0;
--color-text-muted: #888888;
--color-primary: hsl(245, 80%, 62%);
}
/* Light theme override */
.theme-light {
--color-bg: #ffffff;
--color-surface: #f9f9f9;
--color-border: rgba(0, 0, 0, 0.08);
--color-text: #1a1a1a;
--color-text-muted: #666666;
}
/* High contrast theme */
.theme-contrast {
--color-bg: #000000;
--color-surface: #111111;
--color-border: #ffffff;
--color-text: #ffffff;
--color-text-muted: #cccccc;
--color-primary: #ffff00;
}
/* Blue brand theme */
.theme-blue {
--color-primary: hsl(210, 100%, 56%);
--color-primary-dark: hsl(210, 100%, 42%);
}
/* Orange brand theme */
.theme-orange {
--color-primary: hsl(30, 100%, 55%);
--color-primary-dark: hsl(30, 100%, 42%);
}
/* System auto-detect */
@media (prefers-color-scheme: light) {
:root:not([data-theme]) {
--color-bg: #ffffff;
--color-surface: #f9f9f9;
--color-text: #1a1a1a;
}
}
7. The Complete SMACSS File Structure
css/
├── base/
│ ├── _reset.css ← Universal reset
│ ├── _variables.css ← Custom property tokens
│ └── _defaults.css ← Typography + element defaults
│
├── layout/
│ ├── _container.css ← .l-container
│ ├── _grid.css ← .l-grid-*
│ ├── _sidebar.css ← .l-sidebar
│ └── _sections.css ← .l-section
│
├── modules/
│ ├── _btn.css ← .btn, .btn-primary, .btn-lg
│ ├── _card.css ← .card, .card-title, .card-featured
│ ├── _nav.css ← .nav, .nav-link, .nav-logo
│ ├── _hero.css ← .hero, .hero-title
│ ├── _form.css ← .form, .form-input, .form-group
│ ├── _modal.css ← .modal, .modal-body
│ ├── _badge.css ← .badge, .badge-sm
│ └── _alert.css ← .alert, .alert-success
│
├── state/
│ └── _states.css ← .is-*, .has-*, toggled by JS
│
├── themes/
│ ├── _dark.css ← Dark theme tokens
│ ├── _light.css ← Light theme tokens
│ └── _brand.css ← Brand color variants
│
└── main.css ← @import all files in order
8. SMACSS + WordPress Integration
/* SMACSS applied to WordPress builds */
/* BASE — override browser defaults for WP content */
.entry-content h2 { /* Use in base or module, not layout */ }
/* LAYOUT — GP page structure */
.l-site-wrapper { min-height: 100dvh; }
.l-content-area { max-width: 1200px; margin: 0 auto; }
.l-sidebar-area { width: 280px; position: sticky; top: 80px; }
/* MODULES — GP widget styling */
.widget { /* module */ }
.widget-title { /* sub-module */ }
.widget-meta-list { /* sub-module */ }
/* STATE — JS + PHP conditional classes */
.is-front-page .hero { min-height: 100dvh; } /* PHP: body_class adds is-front-page */
.is-sticky .site-header { box-shadow: var(--shadow-md); } /* JS: IntersectionObserver */
.is-menu-open .nav-list { display: flex; } /* JS: hamburger toggle */
/* THEME — WordPress customizer integration */
.theme-blue { --color-primary: #0066cc; }
.theme-green { --color-primary: #00a86b; }
9. SMACSS vs Other Methodologies
| Aspect | SMACSS | BEM | OOCSS |
|---|---|---|---|
| Core concept | Category system | Naming convention | Two principles |
| Focus | Organization | Naming | Reuse |
| State handling | ✅ First-class (is-) | Modifiers (partly) | Separate class |
| Layout concern | ✅ Dedicated category | Not addressed | Container principle |
| Theme system | ✅ Theme category | Not addressed | Skin principle |
| File structure | Defined by categories | Per component | By object type |
| WordPress fit | ✅ Excellent | ✅ Excellent | Good |
| Steepness | Medium | Low | Very low |
10. SMACSS Quick Reference
Category Prefix Examples
─────────────────────────────────────────────
Base (none) body, h1, a, ::selection
Layout l- .l-container .l-grid .l-sidebar
Module (none) .card .btn .nav .hero .modal
State is- / has- .is-active .is-loading .has-error
Theme theme- .theme-dark .theme-blue
Rules:
• Base: element selectors only — NO class/ID selectors
• Layout: structure and positioning ONLY — no colors
• Module: self-contained — no knowledge of parent/container
• State: always override a module — never standalone
• Theme: colors and typography ONLY — no structure