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;}

Other categories

ESC