Layout comparisons
25 old vs modern layout CSS techniques, side by side.
Browser compatibility:
Path shapes without SVG clip paths
Limited availability Old
/* 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 layouts without JavaScript charts
Widely available Old
.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);} Scaling elements without transform hacks
Newly available Old
.thumbnail { transform: scale(0.5); transform-origin: top left; margin-bottom: -50%; margin-right: -50%;} Modern
.thumbnail { zoom: 0.5;} Preventing layout shift from scrollbar appearance
Newly available Old
/* 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;} Preventing scroll chaining without JavaScript
Widely available Old
.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;} Scrollbar styling without -webkit- pseudo-elements
Newly available Old
/* 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;} Mobile viewport height without the 100vh hack
Widely available Old
.hero { height: 100vh;} Modern
.hero { height: 100dvh;} Media query ranges without min-width and max-width
Widely available Old
@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 without the background-image hack
Widely available Old
<!-- 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-growing textarea without JavaScript
Limited availability Old
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;} Corner shapes beyond rounded borders
Limited availability Old
.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;} Filling available space without calc workarounds
Limited availability Old
.full { width: 100%; width: calc(100% - 40px);} Modern
.full { width: stretch;} Carousel navigation without a JavaScript library
Limited availability Old
/* 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%;} Hover tooltips without JavaScript events
Limited availability Old
/* 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> Positioning shorthand without four properties
Widely available Old
.overlay { position: absolute; top: 0; right: 0; bottom: 0; left: 0;} Modern
.overlay { position: absolute; inset: 0;} Tooltip positioning without JavaScript
Limited availability Old
/* 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);} Scroll snapping without a carousel library
Widely available Old
// 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;} Direction-aware layouts without left and right
Widely available Old
.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;} Naming grid areas without line numbers
Widely available Old
.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;} Aligning nested grids without duplicating tracks
Newly available Old
.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;} Spacing elements without margin hacks
Widely available Old
.grid { display: flex;}.grid > * { margin-right: 16px;}.grid > *:last-child { margin-right: 0;} Modern
.grid { display: flex; gap: 16px;} Aspect ratios without the padding hack
Widely available Old
.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;} Sticky headers without JavaScript scroll listeners
Widely available Old
// 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 without media queries
Widely available Old
.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; }} Centering elements without the transform hack
Widely available Old
.parent { position: relative;}.child { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);} Modern
.parent { display: grid; place-items: center;}