Day 12: CSS Variables, calc(), clamp() & Dark Mode
✅ Today you will learn
- Custom properties (CSS variables) and var()
- Design tokens: colours, spacing, radius, shadows
- calc(), min(), max() and clamp() for fluid sizes
- Dark mode with prefers-color-scheme
- A theme switch with data-theme
🏢 In the real world
Two days before launch the client says: “Our new logo is maroon, not orange. Change the colour everywhere.” In a site without variables that means hunting 140 hex codes across 6 files. With variables it is one line. Agencies that build many client sites from one template rely completely on this.
Custom properties
<style>
:root { /* global "design tokens" */
--brand: #e34f26;
--brand-dark: #9a3412;
--radius: 12px;
--space: 16px;
--shadow: 0 8px 24px rgb(0 0 0 / 10%);
}
.card { background: white; border-radius: var(--radius); padding: var(--space); box-shadow: var(--shadow); margin-bottom: var(--space); border-top: 4px solid var(--brand); }
.card h3 { color: var(--brand-dark); margin: 0 0 6px; }
.btn { background: var(--brand); color: white; border: 0; padding: 8px 14px; border-radius: calc(var(--radius) / 2); }
.maroon { --brand: #7f1d1d; --brand-dark: #450a0a; } /* re-theme a section */
</style>
<div class="card"><h3>Diwali Box</h3><p>Uses the global orange brand.</p><button class="btn">Order</button></div>
<div class="card maroon"><h3>Wedding Box</h3><p>Same CSS, maroon theme – only two variables changed.</p><button class="btn">Order</button></div>| Concept | Example | Note |
|---|---|---|
| Declare | --brand: #e34f26; | Name starts with two dashes; case-sensitive |
| Use | color: var(--brand); | Read the value |
| Fallback | var(--accent, gold) | Used if --accent is not defined |
| Scope | :root = global, .card = local | Variables inherit like color does |
| Live updates | element.style.setProperty("--brand", "#7f1d1d") | JavaScript can change themes instantly |
Call it --brand, --text, --surface – not --orange. When the brand turns maroon, a variable named --orange holding maroon becomes confusing.
calc(), min(), max()
<style>
.bar { height: 28px; background: #264de4; color: #fff; padding: 4px 8px; margin: 6px 0; border-radius: 6px; font: 600 12px system-ui; }
.a { width: calc(100% - 120px); } /* full width minus a 120px sidebar */
.b { width: min(100%, 360px); } /* never wider than 360px */
.c { width: max(50%, 220px); } /* at least 220px */
.d { padding-left: calc(var(--i, 0) * 20px); background: #0891b2; }
</style>
<div class="bar a">calc(100% - 120px)</div>
<div class="bar b">min(100%, 360px)</div>
<div class="bar c">max(50%, 220px)</div>
<div class="bar d" style="--i:1">indent level 1</div>
<div class="bar d" style="--i:3">indent level 3 (calc with a variable)</div>clamp(): fluid typography in one line
<style>
h1 {
/* clamp(MIN, PREFERRED, MAX) */
font-size: clamp(1.6rem, 1rem + 3vw, 3rem);
line-height: 1.1;
margin: 0 0 8px;
}
.section { padding: clamp(16px, 4vw, 48px); background: #eef2ff; border-radius: 12px; }
</style>
<div class="section">
<h1>Fresh mithai since 1985</h1>
<p>Resize the window: the heading and padding grow smoothly between their limits – no media queries.</p>
</div>Pure vw ignores the user’s zoom and font settings (accessibility issue). Adding a rem part keeps the text responsive to zoom while still scaling with the screen.
Dark mode – automatic and with a switch
<style>
:root {
--bg: #ffffff; --surface: #f1f5f9; --text: #111827; --muted: #6b7280; --brand: #264de4;
}
@media (prefers-color-scheme: dark) { /* follows the phone setting */
:root:not([data-theme="light"]) { --bg: #0b1120; --surface: #1e293b; --text: #e5e7eb; --muted: #94a3b8; --brand: #60a5fa; }
}
:root[data-theme="dark"] { --bg: #0b1120; --surface: #1e293b; --text: #e5e7eb; --muted: #94a3b8; --brand: #60a5fa; }
body { background: var(--bg); color: var(--text); transition: background .3s, color .3s; }
.card { background: var(--surface); padding: 14px; border-radius: 12px; }
.card small { color: var(--muted); }
.card a { color: var(--brand); }
.switch { float: right; background: var(--surface); color: var(--text); border: 1px solid var(--muted); border-radius: 99px; padding: 4px 12px; }
</style>
<button class="switch" onclick="var r=document.documentElement; r.dataset.theme = r.dataset.theme==='dark' ? 'light' : 'dark'">🌓 Theme</button>
<div class="card">
<h3>Order #1042</h3>
<small>Placed 25 Oct, 10:42</small>
<p>2 kg kaju katli · ₹1,800 · <a href="#">Track order</a></p>
</div>- 1Define every colour as a variable on :root
- 2Use only variables inside components (no raw hex codes)
- 3Override the variables in @media (prefers-color-scheme: dark)
- 4Also override them for [data-theme="dark"] so a button can force it
- 5Save the user’s choice in localStorage (JavaScript course)
🏅 Job skills unlocked today
Practice tasks
🏋️ Practice – do it yourself
Show solution
Quick quiz
🧠 Quick Quiz – test yourself
Interview corner
💼 Interview Corner
CSS variables vs Sass variables?
Sass variables are replaced at build time and disappear in the output. CSS custom properties exist at runtime: they cascade, inherit, can be scoped to elements, changed in media queries and updated by JavaScript – which is what makes theming and dark mode easy.
How would you implement dark mode?
Define semantic colour tokens on :root, use them everywhere, override them under prefers-color-scheme: dark, allow a manual override via a data-theme attribute set by JS and stored in localStorage, and check contrast in both themes.
Summary
- --name: value on :root; use with var(--name, fallback).
- Override variables to re-theme sections or the whole site.
- calc/min/max/clamp make sizes fluid without media queries.
- Dark mode = swap token values via prefers-color-scheme and data-theme.
Day 13: transitions and transforms – smooth hover effects, scaling cards and flipping cards.
