CSS Variables & Modern Features
Custom properties, calc(), and modern CSS
What are CSS Variables?
CSS Custom Properties (commonly called "CSS variables") let you define reusable values that you can reference throughout your stylesheet. They make your CSS more maintainable, enable dynamic theming, and can even be manipulated with JavaScript.
Unlike preprocessor variables (like SASS variables), CSS variables are live—they cascade, inherit, and can be changed at runtime.
Basic CSS Variables
Define and Use
:root {
--color-primary: #3b82f6;
--color-success: #10b981;
--color-danger: #ef4444;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--radius: 8px;
--shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
.button {
background: var(--color-primary);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius);
box-shadow: var(--shadow);
}
/* Fallback value if variable undefined */
.element {
color: var(--text-color, #333);
}
Scoped Variables
Variables can be scoped to specific elements, creating component-level customization:
.card {
--card-padding: 24px;
--card-border: 4px;
padding: var(--card-padding);
border-width: var(--card-border);
}
/* Override for specific instances */
.card.compact {
--card-padding: 12px;
--card-border: 2px;
}
/* Children inherit variables */
.card-header {
padding: var(--card-padding);
}
Dark Mode with Variables
CSS variables make implementing themes incredibly easy:
Light Theme
This is light mode with dynamic variables
Dark Theme
Same markup, different variables
:root {
--bg: #ffffff;
--text: #1f2937;
--border: #e5e7eb;
}
/* Automatic dark mode */
@media (prefers-color-scheme: dark) {
:root {
--bg: #111827;
--text: #f9fafb;
--border: #374151;
}
}
/* Or manual toggle with class */
.dark {
--bg: #111827;
--text: #f9fafb;
--border: #374151;
}
body {
background: var(--bg);
color: var(--text);
border-color: var(--border);
}
calc() Function
Perform calculations with mixed units:
Basic Calculations
/* Mixed units */
.sidebar {
width: calc(100% - 250px);
}
.element {
padding: calc(1rem + 10px);
margin: calc(var(--spacing) * 2);
/* Nested calc */
width: calc(100% - calc(var(--sidebar) + 20px));
}
/* Responsive scaling */
h1 {
font-size: calc(1.5rem + 2vw);
}
Modern CSS Functions: min(), max(), clamp()
Responsive without media queries:
clamp(): Fluid Typography
This heading scales fluidly
Min: 1.5rem, Preferred: 4vw, Max: 3rem
h1 {
font-size: clamp(1.5rem, 4vw, 3rem);
/* min: 1.5rem, preferred: 4vw, max: 3rem */
}
.container {
width: clamp(300px, 80%, 1200px);
padding: clamp(1rem, 5vw, 3rem);
}
min() and max()
/* min(): Pick the smallest */
.container {
width: min(100%, 1200px);
/* Same as: width: 100%; max-width: 1200px; */
}
/* max(): Pick the largest */
.sidebar {
padding: max(20px, 5vw);
/* At least 20px, grows with viewport */
}
/* Nested */
.element {
width: min(100%, max(300px, 50vw));
}
:has() - The Parent Selector
Style elements based on what they contain (revolutionary!):
This card has a valid input
This card has an invalid input
/* Style parent based on children */
.card:has(img) {
padding-top: 0; /* card contains image */
}
.form:has(:invalid) {
border-color: red; /* form has invalid inputs */
}
.form:has(:valid) {
border-color: green;
}
/* Previous sibling */
h2:has(+ p) {
margin-bottom: 0.5rem;
}
/* Complex selections */
.nav:has(.dropdown:hover) {
background: rgba(0,0,0,0.5);
}
Container Queries
Responsive design based on container size, not viewport:
Container queries let you style elements based on their parent's size instead of the viewport. Perfect for component-based design!
/* Define container */
.card-container {
container-type: inline-size;
container-name: card;
}
/* Query the container */
@container card (min-width: 400px) {
.card {
display: flex;
gap: 20px;
}
}
@container (min-width: 600px) {
.card-title {
font-size: 1.5rem;
}
.card-image {
width: 200px;
}
}
✓ Supported in all modern browsers (2023+)
:is() and :where() Selectors
Simplify complex selectors:
:is() - Grouped Selectors
/* Instead of repeating */
.card h2, .card h3, .modal h2, .modal h3 {
color: var(--heading-color);
}
/* Use :is() */
:is(.card, .modal) :is(h2, h3) {
color: var(--heading-color);
}
/* More examples */
:is(h1, h2, h3, h4) {
font-weight: bold;
}
article :is(h1, h2, h3) {
margin-top: 2rem;
}
:where() - Zero Specificity
/* Same as :is() but zero specificity */
:where(h1, h2, h3) {
margin-top: 2rem;
}
/* Easy to override */
.special-h2 {
margin-top: 1rem; /* wins even though it's less specific */
}
/* Great for resets */
:where(ul, ol) {
list-style: none;
margin: 0;
padding: 0;
}
Logical Properties
Write direction-agnostic CSS that works for LTR and RTL languages:
/* Physical (doesn't adapt to text direction) */
margin-left: 20px;
padding-right: 10px;
border-top: 1px solid;
/* Logical (adapts to writing mode & direction) */
margin-inline-start: 20px; /* left in LTR, right in RTL */
padding-inline-end: 10px; /* right in LTR, left in RTL */
border-block-start: 1px; /* top in horizontal mode */
/* Block = vertical axis (top/bottom) */
/* Inline = horizontal axis (left/right) */
margin-block: 20px; /* top and bottom */
margin-inline: 20px; /* left and right */
padding-block-start: 10px; /* padding-top */
padding-inline-end: 10px; /* padding-right in LTR */
/* Shorthand */
inset: 0; /* top, right, bottom, left: 0 */
inset-inline: 0; /* left, right: 0 */
inset-block: 0; /* top, bottom: 0 */
⚠️ Browser Support & Progressive Enhancement
Most modern CSS features have excellent browser support, but always check caniuse.com for specific features.
/* Feature detection with @supports */
@supports (container-type: inline-size) {
.card {
/* Container query styles */
}
}
@supports not (container-type: inline-size) {
.card {
/* Fallback styles */
}
}
/* CSS variables are widely supported */
@supports (--css: variables) {
.element {
color: var(--text-color);
}
}
✓ Modern CSS Best Practices
- • Use CSS variables for colors, spacing, and any repeated values
- • Prefer
clamp()over media queries for fluid typography - • Use logical properties for internationalization (LTR/RTL support)
- • Leverage
:has()for parent/sibling styling instead of JavaScript - • Use container queries for truly reusable components
- • Keep variables organized (colors, spacing, typography, etc.)
- • Use
:where()for base styles that should be easy to override