Modern CSS Features
Checking access...
CSS has evolved dramatically. Modern CSS features reduce the need for preprocessors (Sass, Less) and JavaScript for layout and theming.
Custom Properties (CSS Variables)
Custom properties store reusable values:
:root { /* Color palette */ --color-primary: #0066cc; --color-secondary: #6c757d; --color-success: #28a745; --color-danger: #dc3545; --color-bg: #ffffff; --color-text: #333333;
/* Spacing */ --spacing-xs: 4px; --spacing-sm: 8px; --spacing-md: 16px; --spacing-lg: 24px; --spacing-xl: 48px;
/* Typography */ --font-family: "Roboto", system-ui, sans-serif; --font-size-base: 16px; --line-height: 1.6;
/* Border radius */ --radius-sm: 4px; --radius-md: 8px; --radius-lg: 16px;}Using Variables
.button { background: var(--color-primary); color: white; padding: var(--spacing-sm) var(--spacing-md); border-radius: var(--radius-sm); font-family: var(--font-family);}
.card { background: var(--color-bg); color: var(--color-text); padding: var(--spacing-lg); border-radius: var(--radius-md);}Fallback Values
.element { color: var(--color-brand, #0066cc); /* If --color-brand is not defined, use #0066cc */ padding: var(--spacing-md, 16px); /* Fallback to 16px */}Scoped Variables
Variables respect the cascade — redefine them in any scope:
/* Global values in :root */:root { --bg: white; --text: black; }
/* Dark mode override */.dark-mode { --bg: #1a1a2e; --text: #e0e0e0;}
/* Component-level override */.alert-danger { --color-primary: var(--color-danger);}
/* Usage */body { background: var(--bg); color: var(--text);}Changing Variables with JavaScript
// Set a variabledocument.documentElement.style.setProperty('--color-primary', '#ff6600');
// Get a variablegetComputedStyle(document.documentElement).getPropertyValue('--color-primary');
// Toggle dark modedocument.body.classList.toggle('dark-mode');Container Queries
Container queries let you style elements based on their container’s size, not the viewport:
/* Define a container */.card-container { container-type: inline-size; container-name: cards;}
/* Query the container */@container cards (min-width: 400px) { .card { display: grid; grid-template-columns: 200px 1fr; gap: 16px; }}
@container cards (max-width: 399px) { .card { display: flex; flex-direction: column; }}Container queries are revolutionary for component libraries — a component can adapt to its container width without knowing where it’s placed.
Container Query Units
.card { font-size: clamp(0.875rem, 4cqi, 1.25rem); /* cqi = 1% of container's inline size */ /* cqw = 1% of container's width */ /* cqh = 1% of container's height */ padding: 2cqi;}CSS Nesting
Nesting mirrors HTML structure within your CSS — no more repeating selectors:
/* Before nesting */.card { ... }.card .header { ... }.card .body { ... }.card .body p { ... }.card.is-active { ... }
/* After nesting */.card { padding: 16px;
& .header { font-weight: bold; }
& .body { & p { line-height: 1.6; } }
&.is-active { border-color: blue; }}You can also nest media queries:
.card { display: grid; grid-template-columns: 1fr;
@media (min-width: 768px) { grid-template-columns: 200px 1fr; }}Cascade Layers: @layer
Layers let you control the cascade order explicitly — third-party CSS, framework CSS, and your CSS no longer fight:
/* Define layer order (first = lowest priority) */@layer reset, framework, components, utilities;
/* Add styles to layers */@layer reset { *, *::before, *::after { box-sizing: border-box; margin: 0; }}
@layer framework { .btn { padding: 8px 16px; border-radius: 4px; }}
@layer components { .btn-primary { background: blue; color: white; }}
/* Unlayered styles go ON TOP of all layers (highest priority) */.urgent { background: red !important; } /* Avoid this */Layer order: reset < framework < components < utilities < unlayered styles.
The :has() Selector
:has() selects an element based on its children — previously impossible with CSS alone:
/* Style a card that contains an image */.card:has(img) { display: grid; grid-template-columns: 200px 1fr;}
/* Style a form group that has an error */.form-group:has(.error) { border-color: red;}
/* Style a parent that contains a checked checkbox */.fieldset:has(input:checked) { background: #f0fff0;}
/* Style a section that contains a heading */section:has(h2) { padding-top: 40px;}
/* Multiple conditions */.card:has(img):has(.badge) { position: relative;}Additional Modern Features
accent-color
Style form controls with a single property:
input[type="checkbox"],input[type="radio"],input[type="range"],progress { accent-color: #0066cc;}scroll-behavior
html { scroll-behavior: smooth; /* Smooth anchor scrolling */}/* For individual elements */.element { scroll-behavior: smooth; scroll-margin-top: 80px; /* Offset for fixed headers */}text-wrap
h1 { text-wrap: balance; /* Balanced line wrapping for headings */}p { text-wrap: pretty; /* Prevents orphaned words */}color-mix()
.button { --brand: #0066cc; background: var(--brand);}.button:hover { background: color-mix(in srgb, var(--brand), black 20%); /* Darkens the brand color without knowing what it is */}
.card { background: color-mix(in srgb, var(--brand), white 90%); /* Tints the brand color for a subtle background */}Browser Support Notes
| Feature | Support | Safe to Use? |
|---|---|---|
| Custom Properties | All modern browsers | Yes |
| Container Queries | Chrome/Edge 105+, Safari 16+, Firefox 110+ | Yes |
| CSS Nesting | Chrome 120+, Safari 17.2+, Firefox 117+ | Yes (2023+) |
| @layer | All modern browsers | Yes |
| :has() | Chrome 105+, Safari 15.4+, Firefox 121+ | Yes |
| accent-color | All modern browsers | Yes |
| color-mix() | Chrome 111+, Safari 16.2+, Firefox 113+ | Yes |
Most features are safe for modern development. Use @supports for progressive enhancement:
@supports (container-type: inline-size) { /* Container query styles */}@supports selector(:has(*)) { /* :has() styles */}Try It Yourself
- Create a design system with custom properties (colors, spacing, typography, radii)
- Build a dark mode toggle that switches variable values
- Use
@containerto make a card component adapt to its parent width - Refactor a nested selector structure using CSS nesting
- Use
:has()to highlight cards that contain images or badges - Use
color-mix()for hover states instead of hardcoded colors
Key Takeaways
- Custom properties enable design tokens, theming, and runtime color changes
- Container queries make components responsive to their parent, not just the viewport
- CSS nesting reduces repetition and mirrors HTML structure
@layerresolves specificity wars between frameworks and your styles:has()is the “parent selector” — it styles elements based on their childrencolor-mix()creates hover/active states without knowing the base color- Most modern CSS features are safe to use in 2026