Layout comparisons
26 old vs modern layout CSS techniques, side by side.
Browser compatibility:
Name-only @container queries without size conditions
Newly available Old way
.sidebar { container-name: sidebar;}.card { display: grid;}/* size condition required even when you only care about the name */@container sidebar (width > 0) { .card { grid-auto-flow: column; }} Modern
.sidebar { container-name: sidebar;}.card { display: grid;}@container sidebar { .card { grid-auto-flow: column; }}/* no dummy size condition */ CSS shape() Function: Responsive clip-path Shapes
Limited availability Old way
/* path() uses absolute px — not responsive */.hero { clip-path: path( "M 0 0 L 800 0 L 800 360 /* */ Q 400 420 0 360 Z" /* */ ); /* Breaks on resize */} Modern
.hero { clip-path: shape( from 0% 0%, line to 100% 0%, line to 100% 80%, curve to 0% 80% via 50% 105%, close );} Data-Driven CSS Layouts with Typed attr()
Widely available Old way
.bar { block-size: 1.5rem; inline-size: calc(1% * var(--pct)); background: var(--accent);}/* --pct set in JS per element */bars.forEach(bar => { bar.style.setProperty('--pct', bar.dataset.pct);}); Modern
.bar { block-size: 1.5rem; background: var(--accent); inline-size: attr(data-pct percentage);} CSS zoom Property vs transform: scale()
Newly available Old way
.thumbnail { transform: scale(0.5); transform-origin: top left; margin-bottom: -50%; margin-right: -50%;} Modern
.thumbnail { zoom: 0.5;} Prevent Scrollbar Layout Shift: scrollbar-gutter: stable
Newly available Old way
/* Option 1: always show scrollbar (ugly on short pages) */body { overflow-y: scroll;}/* Option 2: hardcode scrollbar width (fragile) */body { padding-right: 17px;}/* scrollbar width varies by OS and browser */ Modern
body { scrollbar-gutter: stable;} CSS overscroll-behavior: contain (No JavaScript)
Widely available Old way
.modal-content { overflow-y: auto;}// JS: prevent scroll chaining on wheel and touch modal.addEventListener('wheel', (e) => { e.preventDefault(); }, { passive: false }); // also needed for touch: touchmove listener Modern
.modal-content { overflow-y: auto; overscroll-behavior: contain;} Style Scrollbars in CSS: scrollbar-color, scrollbar-width
Newly available Old way
/* Chrome + Safari only — Firefox ignores all of this */::-webkit-scrollbar { width: 8px;}::-webkit-scrollbar-track { background: #f1f1f1;}::-webkit-scrollbar-thumb { background: #888;}::-webkit-scrollbar-thumb:hover { background: #555;} Modern
* { scrollbar-width: thin; scrollbar-color: #888 transparent;} CSS dvh, svh, lvh: Mobile Viewport Height Fix
Widely available Old way
.hero { height: 100vh;} Modern
.hero { height: 100dvh;} CSS Media Query Range Syntax (width < 768px)
Widely available Old way
@media (min-width: 600px) and (max-width: 1200px) { .card { grid-template-columns: 1fr 1fr; }} Modern
@media (600px <= width <= 1200px) { .card { grid-template-columns: 1fr 1fr; }} Responsive Images in CSS with object-fit: cover
Widely available Old way
<!-- div instead of img: no alt, not semantic --><div class="card-image"></div>.card-image { background-image: url(photo.jpg); background-size: cover; background-position: center;} Modern
<img src="photo.jpg" alt="..." loading="lazy">img { object-fit: cover; width: 100%; height: 200px;} Auto-Resize Textarea in CSS: field-sizing: content
Limited availability Old way
textarea { overflow: hidden;}// JS: reset height then set to scrollHeight on every inputel.addEventListener('input', () => { el.style.height = 'auto'; el.style.height = el.scrollHeight + 'px'; }); Modern
textarea { field-sizing: content; min-height: 3lh;} CSS corner-shape: Squircle, Scoop, and Notch Corners
Limited availability Old way
.card { clip-path: polygon( 0% 10%, 2% 4%, 4% 2%, 10% 0%, /* ...16 more points */ );}/* Or use an SVG mask image */ Modern
.card { border-radius: 2em; corner-shape: squircle;} Fill Available Space in CSS with the stretch Keyword
Limited availability Old way
.full { width: 100%; width: calc(100% - 40px);} Modern
.full { width: stretch;} CSS-Only Carousel Navigation with scroll-marker
Limited availability Old way
/* JS: Swiper.js carousel with nav + dots */import Swiper from 'swiper';new Swiper('.carousel', { navigation: { nextEl: '.next', prevEl: '.prev', } , pagination: { el: '.dots' } ,});/* + custom CSS for buttons, dots, states */ Modern
.carousel::scroll-button(left) { content: "⬅" / "Scroll left";}.carousel::scroll-button(right) { content: "➡" / "Scroll right";}.carousel { scroll-marker-group: after;}.carousel li::scroll-marker { content: ''; width: 10px; height: 10px; border-radius: 50%;} CSS Hover Tooltips with popover=hint and interestfor
Limited availability Old way
/* JS: manage hover, focus, and position */btn.addEventListener('mouseenter', () => { tip.hidden = false; positionTooltip(btn, tip);});btn.addEventListener('mouseleave', () => { tip.hidden = true;});btn.addEventListener('focus', /* … */);btn.addEventListener('blur', /* … */); Modern
<button interestfor="tip"> Hover me</button><div id="tip" popover=hint> Tooltip text</div> CSS inset Shorthand for top, right, bottom, left
Widely available Old way
.overlay { position: absolute; top: 0; right: 0; bottom: 0; left: 0;} Modern
.overlay { position: absolute; inset: 0;} CSS Anchor Positioning for Tooltips
Limited availability Old way
/* JS: getBoundingClientRect for trigger + tooltip, compute top/left, handle scroll/resize */.tooltip { position: fixed; top: var(--computed-top); left: var(--computed-left);} Modern
.trigger { anchor-name: --tip;}.tooltip { position-anchor: --tip; top: anchor(bottom);} CSS Scroll Snap: scroll-snap-type and scroll-snap-align
Widely available Old way
// carousel.js + Slick/Swiperimport Swiper from 'swiper';new Swiper('.carousel', { slidesPerView: 1, loop: true}); Modern
.carousel { scroll-snap-type: x mandatory; overflow-x: auto; display: flex; gap: 1rem;}.carousel > * { scroll-snap-align: start;} CSS Logical Properties: RTL Layouts Without left and right
Widely available Old way
.box { margin-left: 1rem; padding-right: 1rem;}[dir="rtl"] .box { margin-left: 0; margin-right: 1rem;} Modern
.box { margin-inline-start: 1rem; padding-inline-end: 1rem; border-block-start: 1px solid;} CSS grid-template-areas: Named Grid Areas
Widely available Old way
.header { grid-column: 1 / -1;}.sidebar { grid-column: 1; grid-row: 2;}.main { grid-column: 2; grid-row: 2;}.footer { grid-column: 1 / -1; grid-row: 3;} Modern
.layout { display: grid; grid-template-areas: "header header" "sidebar main" "footer footer";}.header { grid-area: header;}.sidebar { grid-area: sidebar;} CSS Subgrid: Align Nested Grids Without Duplicating Tracks
Newly available Old way
.parent { display: grid; grid-template-columns: 1fr 1fr 1fr;}.child-grid { display: grid; grid-template-columns: 1fr 1fr 1fr;} Modern
.parent { display: grid; grid-template-columns: 1fr 1fr 1fr;}.child-grid { display: grid; grid-template-columns: subgrid;} CSS gap Property for Flex and Grid Spacing
Widely available Old way
.grid { display: flex;}.grid > * { margin-right: 16px;}.grid > *:last-child { margin-right: 0;} Modern
.grid { display: flex; gap: 16px;} CSS aspect-ratio Property (No Padding Hack)
Widely available Old way
.video-wrapper { position: relative; padding-top: 56.25%;}.video-wrapper > * { position: absolute; top: 0; left: 0; width: 100%; height: 100%;} Modern
.video-wrapper { aspect-ratio: 16 / 9;} CSS Sticky Header with position: sticky
Widely available Old way
// JavaScript: scroll listenerwindow.addEventListener('scroll', () => { const rect = header.getBoundingClientRect(); if (rect.top <= 0) header.classList.add('fixed'); else header.classList.remove('fixed');});.header.fixed { position: fixed; top: 0;} Modern
.header { position: sticky; top: 0;} Responsive Components with CSS Container Queries
Widely available Old way
.card { display: grid; grid-template-columns: 1fr;}@media (min-width: 768px) { .card { grid-template-columns: auto 1fr; }} Modern
.wrapper { container-type: inline-size;}.card { grid-template-columns: 1fr;}@container (width > 400px) { .card { grid-template-columns: auto 1fr; }} Center a Div in CSS with place-items: center
Widely available Old way
.parent { position: relative;}.child { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);} Modern
.parent { display: grid; place-items: center;}