10 Modern CSS Tricks Every Developer Should Use in 2026
CSS has evolved dramatically over the past three years. Features that once required complex JavaScript hacks or heavy libraries are now one-liners in pure CSS. Here are ten of the most impactful modern CSS techniques that every web developer should have in their toolkit right now.
Media queries respond to the viewport width. Container queries respond to the width of a parent element. This means components can style themselves based on how much space they actually have, making them truly reusable regardless of where they live on the page — a massive improvement over the viewport-only approach we were forced to use for years.
/* Step 1: Mark the parent as a containment context */
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* Step 2: Style the child based on the parent's width */
.card {
display: flex;
flex-direction: column;
padding: 16px;
}
/* When the CARD WRAPPER is at least 400px wide */
@container card (min-width: 400px) {
.card {
flex-direction: row;
align-items: center;
padding: 24px;
}
.card-image { width: 140px; flex-shrink: 0; }
}
For decades, CSS could only style children and siblings — never parents. The :has() pseudo-class finally changes this, enabling you to style a parent element based on what it contains. This eliminates an enormous number of JavaScript class-toggle hacks.
/* Style a form-group differently when its input has content */
.form-group:has(input:not(:placeholder-shown)) label {
font-weight: 700;
color: var(--primary);
transform: translateY(-20px) scale(0.85);
}
/* Add a visual indicator to any nav item that has a sub-menu */
.nav-item:has(.dropdown-menu) > .nav-link::after {
content: " ▾";
opacity: 0.6;
}
/* Style a card grid differently when it contains an image */
.article-card:has(img) {
grid-template-rows: 200px 1fr;
}
Specificity conflicts in large CSS codebases are notoriously difficult to debug. Cascade layers let you explicitly define the order in which groups of styles are applied, making specificity predictable and eliminating the need for !important overrides:
/* Define layer order — last wins for same-specificity conflicts */
@layer reset, base, components, utilities;
@layer reset {
*, *::before, *::after { box-sizing: border-box; margin: 0; }
}
@layer base {
body { font-family: 'Inter', sans-serif; line-height: 1.6; }
a { color: var(--primary); }
}
@layer components {
.btn { padding: 10px 20px; border-radius: 8px; }
}
/* Utilities always win, even with low specificity */
@layer utilities {
.hidden { display: none !important; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; }
}
Parallax effects, scroll-progress indicators, and reveal-on-scroll animations traditionally required JavaScript IntersectionObserver with dozens of lines of code. Now they are pure CSS:
/* Reading progress bar at top of article — no JS required! */
@keyframes progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
.reading-progress {
position: fixed;
top: 0; left: 0;
width: 100%; height: 4px;
background: var(--primary);
transform-origin: left;
animation: progress linear;
animation-timeline: scroll(root block);
}
/* Fade-in cards as they enter the viewport */
@keyframes fade-up {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
.card {
animation: fade-up linear both;
animation-timeline: view();
animation-range: entry 0% entry 30%;
}
CSS custom properties become extraordinarily powerful when combined with calc() to create dynamic, mathematically-derived design tokens:
:root {
--space-unit: 8px;
--space-xs: calc(var(--space-unit) * 0.5); /* 4px */
--space-sm: calc(var(--space-unit) * 1); /* 8px */
--space-md: calc(var(--space-unit) * 2); /* 16px */
--space-lg: calc(var(--space-unit) * 3); /* 24px */
--space-xl: calc(var(--space-unit) * 5); /* 40px */
--color-hue: 220;
--color-primary: hsl(var(--color-hue), 86%, 61%);
--color-primary-dk: hsl(var(--color-hue), 86%, 45%);
--color-primary-lt: hsl(var(--color-hue), 86%, 95%);
}
/* Change the whole theme by altering ONE variable */
.theme-purple { --color-hue: 260; }
.theme-green { --color-hue: 140; }
Instead of margin-left and padding-right, use logical properties that automatically flip direction for right-to-left languages like Arabic, Hebrew, and Urdu:
/* Instead of: margin-left / margin-right */
/* Use: margin-inline (both) or margin-inline-start / end */
.sidebar { margin-inline-start: 24px; } /* right language ← left language */
.article { padding-block: 40px; } /* top + bottom */
p { text-align: start; } /* left in LTR, right in RTL */
/* Without :is() — repetitive */
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { color: inherit; }
/* With :is() — clean (takes the highest specificity of its args) */
:is(h1, h2, h3, h4, h5, h6) a { color: inherit; }
/* :where() is identical but has ZERO specificity — great for resets */
:where(h1, h2, h3, h4) { margin-top: 1.5em; }
/* Combine for powerful selectors */
.card :is(h2, h3):not(:first-child) { margin-top: 24px; }
Styling checkboxes, radio buttons, and range sliders used to require complex custom implementations with hidden inputs and pseudo-elements. The accent-color property does it in one line:
:root { accent-color: #4f8ef7; }
/* Or target specific input types individually */
input[type="checkbox"] { accent-color: #4f8ef7; width: 18px; height: 18px; }
input[type="radio"] { accent-color: #a259ff; }
input[type="range"] { accent-color: #39ff14; }
subgrid allows a grid child to inherit its parent's grid tracks — solving the classic problem where items inside nested elements could not align with the parent grid:
.article-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
/* Cards inherit the parent's column tracks */
.article-card {
display: grid;
grid-row: span 3;
grid-template-rows: subgrid; /* magic! */
}
/* Now all cards' image, title, and footer align perfectly */
.card-image { grid-row: 1; }
.card-body { grid-row: 2; }
.card-footer { grid-row: 3; align-self: end; }
The color-mix() function lets you blend colours directly in CSS, enabling automatic dark-mode-friendly colour palettes from a single base colour:
:root {
--brand: #4f8ef7;
--brand-hover: color-mix(in srgb, var(--brand) 80%, black);
--brand-light: color-mix(in srgb, var(--brand) 15%, white);
--brand-glass: color-mix(in srgb, var(--brand) 12%, transparent);
}
.btn-primary {
background: var(--brand);
}
.btn-primary:hover {
background: var(--brand-hover); /* automatically 20% darker */
}
.highlight-box {
background: var(--brand-glass);
border: 1px solid color-mix(in srgb, var(--brand) 30%, transparent);
}