Modern CSS Tips & Tricks
A curated collection of modern CSS features every frontend developer should know. Container queries, cascade layers, :has(), and more — each with a real code example.
20
Tips
6
Categories
Free
Always
Container Queries
.wrapper {
container-type: inline-size;
}
@container (min-width: 400px) {
.card { display: grid; }
}:has() Parent Selector
form:has(input:invalid) .btn {
opacity: 0.5;
pointer-events: none;
}Fluid Typography
h1 {
font-size: clamp(2rem, 5vw + 1rem, 4.5rem);
text-wrap: balance;
}Container Queries
Respond to a container's own size instead of the viewport. Build truly portable components that adapt wherever they're placed.
.wrapper {
container-type: inline-size;
container-name: card;
}
@container card (min-width: 480px) {
.card {
display: grid;
grid-template-columns: 200px 1fr;
gap: 1rem;
}
}CSS Grid Subgrid
Let nested grid items snap to the parent's tracks. Solves the classic 'cards with misaligned footers' problem without JavaScript.
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: auto 1fr auto;
gap: 1rem;
}
.card {
display: grid;
grid-row: span 3;
/* Inherit parent row sizes */
grid-template-rows: subgrid;
}CSS Grid auto-fit vs auto-fill
auto-fit collapses empty tracks so columns stretch to fill space. auto-fill keeps them. Use auto-fit for responsive grids with zero media queries.
/* Columns stretch to fill — no media queries needed */
.responsive-grid {
display: grid;
grid-template-columns:
repeat(auto-fit, minmax(260px, 1fr));
gap: 1.5rem;
}
/* Empty slots remain (useful for fixed layouts) */
.fixed-slots {
grid-template-columns:
repeat(auto-fill, minmax(260px, 1fr));
}aspect-ratio
Lock element proportions in one line. Replaces the old padding-top percentage hack entirely.
.embed { aspect-ratio: 16 / 9; width: 100%; }
.avatar { aspect-ratio: 1; width: 3rem; }
.card-img { aspect-ratio: 4 / 3; }
.golden { aspect-ratio: 1.618; }
/* Works with object-fit too */
img.cover {
aspect-ratio: 16 / 9;
object-fit: cover;
width: 100%;
}CSS Logical Properties
Use flow-relative props instead of directional ones. Layouts mirror automatically for RTL languages — no extra CSS needed.
/* Logical — works in any writing mode */
.card {
margin-inline-start: 1rem;
padding-block-start: 1.5rem;
border-inline-end: 4px solid;
}
/* No [dir="rtl"] override needed — ever */
/* Shorthand */
.box {
margin-inline: auto; /* left & right */
padding-block: 2rem; /* top & bottom */
}:has() — The Parent Selector
Select an element based on its descendants. The most requested CSS feature in history — now universally supported.
/* Card layout shifts when it has an image */
.card:has(img) {
display: grid;
grid-template-columns: 120px 1fr;
}
/* Disable submit when form has invalid input */
form:has(input:invalid) .btn-submit {
opacity: 0.5;
pointer-events: none;
}
/* Dark nav when hero is visible */
:has(.hero:in-viewport) nav {
color: white;
}CSS Nesting
Write nested rules natively — no Sass or PostCSS needed. Use & to reference the parent and keep related styles together.
.button {
padding: .5rem 1rem;
background: oklch(55% 0.18 85);
&:hover {
background: oklch(45% 0.18 85);
translate: 0 -2px;
}
&.large {
padding: .75rem 1.5rem;
font-size: 1.125rem;
}
& .icon {
margin-inline-end: .5rem;
}
}:is() and :where()
:is() groups selectors and keeps the highest specificity of the list. :where() does the same but with zero specificity — ideal for resets.
/* Clean with :is() */
h1 a, h2 a, h3 a, h4 a { color: inherit; }
/* Clean with :is() */
:is(h1, h2, h3, h4) a { color: inherit; }
/* :where() = zero specificity, easy to override */
:where(h1, h2, h3, h4) {
line-height: 1.2;
margin-block-end: .5em;
}
/* Combine both */
:is(article, section) :where(h2, h3) {
font-size: clamp(1.25rem, 3vw, 2rem);
}Cascade Layers (@layer)
Control specificity at the layer level. Layers declared earlier always lose — no more specificity wars with third-party CSS.
/* Declare order upfront — first = lowest priority */
@layer reset, base, components, utilities;
@layer reset {
*, *::before, *::after { box-sizing: border-box; }
}
@layer components {
.btn { padding: .5rem 1rem; background: blue; }
}
/* Utilities always win regardless of specificity */
@layer utilities {
.mt-4 { margin-top: 1rem !important; }
}@property — Typed Custom Properties
Register CSS variables with a type and initial value. Unlocks smooth animation of properties that were previously unanimatable.
@property --hue {
syntax: "<number>";
initial-value: 0;
inherits: false;
}
.swatch {
background: hsl(var(--hue), 70%, 55%);
transition: --hue 0.6s ease;
}
.swatch:hover { --hue: 220; }
/* Also works for gradients! */
@property --stop {
syntax: "<percentage>";
initial-value: 0%;
inherits: false;
}CSS Custom Properties + calc()
Custom properties are more than static variables. Combine them with calc() to build a single-source-of-truth design system.
:root {
--space: 0.25rem;
--cols: 12;
--gap: calc(var(--space) * 4);
--radius-base: 0.5rem;
--radius-lg: calc(var(--radius-base) * 2);
}
.grid {
display: grid;
grid-template-columns: repeat(var(--cols), 1fr);
gap: var(--gap);
}
@media (max-width: 768px) {
:root { --cols: 4; }
}clamp() — Fluid Typography
Fluid font sizes and spacing that scale smoothly between a min and max — no breakpoints, no JavaScript.
/* clamp(minimum, preferred, maximum) */
h1 {
/* 2rem on mobile → 4.5rem on wide screens */
font-size: clamp(2rem, 5vw + 1rem, 4.5rem);
line-height: clamp(1.1, 1.2 + 0.3vw, 1.35);
}
section {
padding: clamp(2rem, 8vw, 6rem);
}
.container {
width: min(90%, 1280px);
margin-inline: auto;
}text-wrap: balance & pretty
balance distributes words evenly across lines for headings. pretty prevents orphaned last words in paragraphs. Both with one property.
/* Even line lengths for headings */
h1, h2, h3 {
text-wrap: balance;
/* Browser limit: max 4 lines */
}
/* No orphaned words at end of paragraphs */
p, li {
text-wrap: pretty;
}
/* Before: needed ugly hacks like this */
/* h1 { max-width: 28ch; } */backdrop-filter
Apply blur, brightness, and saturation to whatever is behind an element. The foundation of modern glassmorphism UI.
.glass {
background: rgb(255 255 255 / 0.1);
backdrop-filter: blur(16px) saturate(180%);
-webkit-backdrop-filter: blur(16px) saturate(180%);
border: 1px solid rgb(255 255 255 / 0.2);
border-radius: 1rem;
}
.frosted-nav {
backdrop-filter: blur(24px) brightness(0.85);
background: rgb(0 0 0 / 0.25);
}color-mix()
Mix two colors in any color space directly in CSS. Generate tints, shades, and transparent variants from a single token.
:root {
--brand: #ca8a04;
/* 20% white = tint */
--brand-light:
color-mix(in oklch, var(--brand) 80%, white);
/* 30% black = shade */
--brand-dark:
color-mix(in oklch, var(--brand) 70%, black);
/* Transparent variant */
--brand-alpha:
color-mix(in srgb, var(--brand) 15%, transparent);
}accent-color
Theme all native form controls — checkboxes, radios, range sliders, progress bars — with one line. No custom component needed.
/* Global brand accent on all controls */
:root {
accent-color: #ca8a04;
}
/* Or target specific elements */
input[type="checkbox"],
input[type="radio"] {
accent-color: #ca8a04;
width: 1.2rem;
height: 1.2rem;
}
input[type="range"],
progress { accent-color: #ca8a04; }Scroll Snap
Smooth scroll-stop points with zero JavaScript. Perfect for carousels, image galleries, and full-screen sections.
/* Horizontal carousel */
.carousel {
overflow-x: auto;
scroll-snap-type: x mandatory;
display: flex;
gap: 1rem;
scrollbar-width: none;
}
.slide {
scroll-snap-align: start;
flex: 0 0 300px;
}
/* Full-screen vertical sections */
.page {
scroll-snap-type: y mandatory;
height: 100vh;
overflow-y: scroll;
}overscroll-behavior
Stop scroll chaining — the UX bug where scrolling inside a modal also scrolls the page. One line, no JS needed.
/* Modal: don't scroll the page when it ends */
.modal {
overflow-y: auto;
overscroll-behavior-y: contain;
}
/* PWA: disable pull-to-refresh */
body {
overscroll-behavior-y: none;
}
/* Carousel: no horizontal bounce */
.carousel {
overscroll-behavior-x: contain;
}content-visibility: auto
Tell the browser to skip rendering off-screen content entirely. Can cut initial render time by 50%+ on content-heavy pages.
.article-section {
content-visibility: auto;
/* Hint at rendered height to
prevent layout shifts */
contain-intrinsic-size: 0 500px;
}
/* Works great for long lists too */
.list-item {
content-visibility: auto;
contain-intrinsic-size: 0 80px;
}grid-template-areas
Name your grid regions for visual, readable layout code. Rearranging the layout for different breakpoints is as easy as editing a string.
.page {
display: grid;
grid-template-areas:
"header header"
"sidebar main "
"footer footer ";
grid-template-columns: 240px 1fr;
grid-template-rows: auto 1fr auto;
min-height: 100vh;
}
header { grid-area: header; }
aside { grid-area: sidebar; }
main { grid-area: main; }
footer { grid-area: footer; }Want all 20 tips in one file?
Download the full PDF — great for offline reference.