Chapter 31: Custom Properties
Custom Properties (also called CSS Variables)
Before custom properties existed (pre-2016), CSS was very rigid:
- You had to repeat the same color / spacing / font-size value dozens of times
- Changing the primary color? → Find & replace in 50 places
- Want a dark mode? → Duplicate almost the entire stylesheet
- Want to make spacing consistent? → Memorize magic numbers like 16px, 24px, 32px everywhere
Custom properties solved all of these problems in one elegant way.
They are basically variables you define once → use everywhere → change in one place → everything updates instantly.
Let’s learn it properly — like I’m sitting next to you with VS Code open, explaining line by line.
1. Basic Syntax – How to Create & Use Custom Properties
Custom properties always start with two dashes — and are defined inside any selector (most commonly :root).
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
/* Define variables on :root → they are global & inherited everywhere */ :root { --primary-color: #3b82f6; /* blue */ --secondary-color: #10b981; /* green */ --text-color: #1e293b; --bg-color: #f8fafc; --card-bg: white; --spacing-sm: 1rem; --spacing-md: 2rem; --spacing-lg: 4rem; --border-radius: 12px; --shadow-sm: 0 4px 12px rgba(0,0,0,0.08); } /* Use them with var() function */ body { background-color: var(--bg-color); color: var(--text-color); font-family: system-ui, sans-serif; } .btn { background-color: var(--primary-color); color: white; padding: var(--spacing-md) var(--spacing-lg); border-radius: var(--border-radius); box-shadow: var(--shadow-sm); } .card { background-color: var(--card-bg); padding: var(--spacing-lg); border-radius: var(--border-radius); box-shadow: var(--shadow-sm); } |
Key points:
- You define them with –variable-name: value;
- You use them with var(–variable-name)
- They inherit like normal CSS properties (so :root makes them global)
- You can define them anywhere (on .dark-mode, on .card, on button, etc.) → they override higher up the cascade
2. Real Power – Change Theme in One Place
Want dark mode? Just override variables:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@media (prefers-color-scheme: dark) { :root { --bg-color: #0f172a; --text-color: #e2e8f0; --card-bg: #1e293b; --primary-color:#60a5fa; } } /* Or manual toggle class */ .dark-mode { --bg-color: #0f172a; --text-color: #e2e8f0; --card-bg: #1e293b; --primary-color:#60a5fa; } |
→ One change → entire site updates colors automatically.
3. Very Practical Real-World Example (Copy-Paste & Play)
index.html
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>CSS Custom Properties – Webliance</title> <link rel="stylesheet" href="style.css"> </head> <body class="light-theme"> <!-- toggle class: light-theme / dark-theme --> <div class="container"> <header> <h1>CSS Variables in Action</h1> <button class="theme-toggle">Toggle Theme</button> </header> <section class="hero"> <h2>Welcome to Modern CSS</h2> <p>Change one value → entire site updates instantly</p> <a href="#" class="btn primary">Primary Button</a> <a href="#" class="btn secondary">Secondary Button</a> </section> <section class="cards"> <div class="card"> <h3>Card One</h3> <p>Uses --card-bg, --spacing-md, --shadow-sm</p> </div> <div class="card featured"> <h3>Featured Card</h3> <p>Overrides --card-bg locally</p> </div> </section> </div> <script> // Simple theme toggle (for demo) document.querySelector('.theme-toggle').addEventListener('click', () => { document.body.classList.toggle('dark-theme'); document.body.classList.toggle('light-theme'); }); </script> </body> </html> |
style.css (all power comes from variables)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
/* Global variables – light theme default */ :root { --bg-color: #f8fafc; --text-color: #1e293b; --surface: white; --primary: #3b82f6; --secondary: #10b981; --accent: #f59e0b; --border: #e2e8f0; --shadow-sm: 0 4px 12px rgba(0,0,0,0.08); --radius: 12px; --spacing-xs: 0.8rem; --spacing-sm: 1.2rem; --spacing-md: 2rem; --spacing-lg: 4rem; } /* Dark theme override */ .dark-theme { --bg-color: #0f172a; --text-color: #e2e8f0; --surface: #1e293b; --primary: #60a5fa; --secondary: #34d399; --accent: #fbbf24; --border: #334155; --shadow-sm: 0 4px 12px rgba(0,0,0,0.4); } /* Apply variables everywhere */ body { background: var(--bg-color); color: var(--text-color); font-family: system-ui, sans-serif; line-height: 1.65; min-height: 100vh; } .container { max-width: 1100px; margin: 0 auto; padding: 0 var(--spacing-md); } header { padding: var(--spacing-lg) 0; text-align: center; } h1 { font-size: clamp(3rem, 7vw, 5rem); margin-bottom: var(--spacing-md); } .theme-toggle { padding: var(--spacing-sm) var(--spacing-lg); background: var(--primary); color: white; border: none; border-radius: var(--radius); cursor: pointer; font-weight: 600; } /* Hero */ .hero { text-align: center; padding: var(--spacing-lg) 0; } .hero h2 { font-size: clamp(2.2rem, 5vw, 4rem); margin-bottom: var(--spacing-sm); } .btn { display: inline-block; padding: var(--spacing-sm) var(--spacing-lg); border-radius: var(--radius); font-weight: 600; text-decoration: none; transition: all 0.25s ease; } .btn.primary { background: var(--primary); color: white; } .btn.secondary { background: var(--secondary); color: white; } .btn:hover { transform: translateY(-2px); box-shadow: var(--shadow-sm); } /* Cards */ .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: var(--spacing-lg); margin-top: var(--spacing-lg); } .card { background: var(--surface); border-radius: var(--radius); padding: var(--spacing-lg); box-shadow: var(--shadow-sm); transition: all 0.25s ease; } .card:hover { transform: translateY(-6px); box-shadow: 0 12px 32px rgba(0,0,0,0.12); } .card.featured { --surface: #fefce8; /* local override – only this card */ border: 2px solid var(--accent); } |
What You Should Do Right Now
- Open the file → see light theme
- Click “Toggle Theme” → watch dark mode instantly apply everywhere
- Change –primary value in :root → entire site primary color changes
- Change –spacing-md: 3rem; → all medium spacing updates
- Look at .card.featured → see how it overrides –surface locally
Why Custom Properties Are a Game-Changer (2026 Perspective)
- Single source of truth — change brand color once
- Dark mode becomes trivial
- Theming (company blue vs client red vs seasonal themes)
- Consistency — no more 16px vs 1rem vs 1.2em confusion
- Local overrides — one card can have different background without duplicating CSS
- Calculations — calc(var(–spacing-md) * 1.5)
- Works with JS — change variables dynamically (document.documentElement.style.setProperty(‘–primary’, ‘#ef4444’))
How does it feel when you change one color and everything updates? That “wow” moment is exactly why custom properties are now standard in every serious CSS codebase.
Next possible lessons — tell me:
- “real-world theme switcher with localStorage + JS”
- “using custom properties inside calc() & hsl()”
- “advanced theming (brand colors, seasonal themes)”
- “container queries + custom properties”
- “common mistakes people make with CSS variables”
You’ve just unlocked one of the biggest productivity boosters in modern CSS. Chai khatam? Fresh cup lao — let’s keep enhancing! 🚀 😄
