Updated for 2026

Stop writing CSS
like it's 2015.

Every old CSS hack has a clean, native replacement now. Here they are, side by side: the old way and the modern way.

Old
.center {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
}
Modern
.center {
  display: grid;
  place-items: center;
}

/* that's it. */

All comparisons

40 snippets
Color
Intermediate

Vivid colors beyond sRGB

Modern .hero {
  color: oklch(0.7 0.25 29);
}
/* or color(display-p3 1 0.2 0.1) */
Old .hero {
  color: rgb(200, 80, 50);
}
/* sRGB only, washed on P3 */
hover to see old →
Color
Advanced

Color variants without Sass functions

Modern .btn {
  background: oklch(from var(--brand) calc(l + 0.2) c h);
}
Old /* Sass: lighten($brand, 20%), darken($brand, 10%) */
.btn { background: #e0e0e0; }
hover to see old →
Typography
Beginner

Multiline text truncation without JavaScript

Modern .card-title {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  line-clamp: 3;
}
Old /* JS: slice text by chars/words, add "..." */
.card-title { overflow: hidden; }
hover to see old →
Typography
Beginner

Drop caps without float hacks

Modern .drop-cap::first-letter {
  initial-letter: 3;
}
Old .drop-cap::first-letter {
  float: left;
  font-size: 3em; line-height: 1;
}
hover to see old →
Layout
Beginner

Positioning shorthand without four properties

Modern .overlay {
  position: absolute;
  inset: 0;
}
Old .overlay {
  top: 0; right: 0;
  bottom: 0; left: 0;
}
hover to see old →
Workflow
Intermediate

Lazy rendering without IntersectionObserver

Modern .section {
  content-visibility: auto;
  contain-intrinsic-size: auto 500px;
}
Old // JS IntersectionObserver
new IntersectionObserver(
  (entries) => { /* render */ }
).observe(el);
hover to see old →
Layout
Beginner

Dropdown menus without JavaScript toggles

Modern button[popovertarget=menu] { }
#menu[popover] {
  position: absolute;
}
Old .menu { display: none; }
.menu.open { display: block; }
/* + JS: click, clickOutside, ESC, aria */
hover to see old →
Layout
Advanced

Tooltip positioning without JavaScript

Modern .trigger { anchor-name: --tip; }
.tooltip {
  position-anchor: --tip;
  top: anchor(bottom);
}
Old /* Popper.js / Floating UI: compute rect,
position: fixed, update on scroll */
.tooltip { position: fixed; }
hover to see old →
Workflow
Advanced

Scoped styles without BEM naming

Modern @scope (.card) {
  .title { font-size: 1.25rem; }
  .body { color: #444; }
}
/* .title only inside .card */
Old // BEM: .card__title, .card__body
.card__title { … }
.card__body { … }
// or CSS Modules / styled-components */
hover to see old →
Workflow
Advanced

Typed custom properties without JavaScript

Modern @property --hue {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}
/* animatable, validated */
Old // --hue was a string, no animation
:root { --hue: 0; }
hsl(var(--hue), …) /* no interpolation */
hover to see old →
Animation
Beginner

Independent transforms without the shorthand

Modern .icon {
  translate: 10px 0;
  rotate: 45deg;
  scale: 1.2;
}
/* animate any one without touching the rest */
Old .icon { transform: translateX(10px) rotate(45deg) scale(1.2); }
/* change one = rewrite all */
hover to see old →
Animation
Intermediate

Animating display none without workarounds

Modern .panel { transition: opacity .2s, overlay .2s;
  transition-behavior: allow-discrete; }
.panel.hidden { opacity: 0; display: none; }
/* no JS wait or visibility hack */
Old // wait for transitionend then display:none
el.addEventListener('transitionend', …)
visibility + opacity + pointer-events
hover to see old →
Animation
Intermediate

Entry animations without JavaScript timing

Modern .card { transition: opacity .3s, transform .3s; }
.card { @starting-style { opacity: 0; transform: translateY(10px); } }
/* no rAF/setTimeout */
Old // add class after paint
requestAnimationFrame(() => {
  el.classList.add('visible');
});
hover to see old →
Animation
Advanced

Page transitions without a framework

Modern document.startViewTransition(() => updateDOM());
.hero { view-transition-name: hero; }
/* no Barba, no React TG */
Old // Barba.js or React Transition Group
Barba.init({ … })
transition hooks + duration state
hover to see old →
Layout
Intermediate

Scroll snapping without a carousel library

Modern .carousel { scroll-snap-type: x mandatory; }
.carousel > * { scroll-snap-align: start; }
/* no lib, no touch handlers */
Old // Slick, Swiper, or scroll/touch JS
$('.carousel').slick({ … })
touchstart / scroll handlers
hover to see old →
Typography
Beginner

Balanced headlines without manual line breaks

Modern h1, h2 {
  text-wrap: balance;
}
/* no br or JS */
Old // manual <br> or Balance-Text.js
h1 { text-align: center; }
.balance-text /* JS lib */
hover to see old →
Typography
Beginner

Font loading without invisible text

Modern @font-face {
  font-family: "MyFont";
  font-display: swap;
}
Old @font-face { ... }
/* Default: invisible text until load */
hover to see old →
Typography
Intermediate

Multiple font weights without multiple files

Modern @font-face {
  font-family: "MyVar";
  src: url("MyVar.woff2");
  font-weight: 100 900;
}
Old @font-face { font-weight: 400; }
@font-face { font-weight: 700; }
/* 4+ files */
hover to see old →
Workflow
Beginner

Dark mode defaults without extra CSS

Modern :root {
  color-scheme: light dark;
}
Old @media (prefers-color-scheme: dark) {
  input, select, textarea { ... }
}
hover to see old →
Color
Intermediate

Dark mode colors without duplicating values

Modern color: light-dark(#111, #eee);
color-scheme: light dark;
Old @media (prefers-color-scheme: dark) {
  color: #eee;
}
hover to see old →
Selector
Intermediate

Low-specificity resets without complicated selectors

Modern :where(ul, ol) {
  margin: 0;
  padding-inline-start: 1.5rem;
}
Old .reset ul, .reset ol { ... }
/* or (0,0,1) specificity, still wins */
hover to see old →
Layout
Intermediate

Direction-aware layouts without left and right

Modern margin-inline-start: 1rem;
padding-inline-end: 1rem;
border-block-start: 1px solid;
Old margin-left: 1rem;
padding-right: 1rem;
[dir="rtl"] .box { margin-right: ... }
hover to see old →
Layout
Beginner

Naming grid areas without line numbers

Modern .layout {
  display: grid;
  grid-template-areas: "header header" "sidebar main" "footer footer";
}
Old float: left; /* clearfix, margins */
grid-column: 1 / 3;
grid-row: 2;
hover to see old →
Layout
Advanced

Aligning nested grids without duplicating tracks

Modern .child-grid {
  display: grid;
  grid-template-columns: subgrid;
}
Old .child-grid {
  grid-template-columns: 1fr 1fr 1fr;
/* duplicate parent tracks */
}
hover to see old →
Layout
Intermediate

Modal dialogs without a JavaScript library

Modern dialog {
  padding: 1rem;
}
dialog::backdrop { background: rgb(0 0 0 / .5); }
Old .overlay { position: fixed; z-index: 999; }
/* + JS: open/close, ESC, focus trap */
hover to see old →
Color
Beginner

Styling form controls without rebuilding them

Modern input[type="checkbox"],
input[type="radio"] {
  accent-color: #7c3aed;
}
Old appearance: none;
// + 20+ lines of custom box/border/background
hover to see old →
Selector
Beginner

Grouping selectors without repetition

Modern .card :is(h1, h2, h3, h4) {
  margin-bottom: 0.5em;
}
Old .card h1, .card h2, .card h3, .card h4 {
  margin-bottom: 0.5em;
}
hover to see old →
Selector
Beginner

Focus styles without annoying mouse users

Modern :focus-visible {
  outline: 2px solid var(--focus-color);
}
Old :focus { outline: 2px solid blue; }
// Shows on mouse click too, or people remove it (a11y fail)
hover to see old →
Workflow
Intermediate

Controlling specificity without !important

Modern @layer base, components, utilities;
@layer utilities { .mt-4 { margin-top: 1rem; } }
Old .card .title { ... }
.page .card .title { ... }
.page .card .title.special { color: red !important; }
hover to see old →
Workflow
Beginner

Theme variables without a preprocessor

Modern :root {
  --primary: #7c3aed;
}
.btn { background: var(--primary); }
Old // Sass: $primary: #7c3aed;
// Compiles to static #7c3aed
.btn { background: $primary; }
hover to see old →
Typography
Intermediate

Fluid typography without media queries

Modern h1 {
  font-size: clamp(1rem, 2.5vw, 2rem);
}
Old h1 { font-size: 1rem; }
@media (min-width: 600px) { h1 { font-size: 1.5rem; } }
@media (min-width: 900px) { h1 { font-size: 2rem; } }
hover to see old →
Layout
Beginner

Spacing elements without margin hacks

Modern .grid {
  display: flex;
  gap: 16px;
}
Old .grid > * { margin-right: 16px; }
.grid > *:last-child { margin-right: 0; }
hover to see old →
Layout
Beginner

Aspect ratios without the padding hack

Modern .video-wrapper {
  aspect-ratio: 16 / 9;
}
Old .wrapper { padding-top: 56.25%; position: relative; }
.inner { position: absolute; inset: 0; }
hover to see old →
Layout
Beginner

Sticky headers without JavaScript scroll listeners

Modern .header {
  position: sticky;
  top: 0;
}
Old // JS: scroll listener + getBoundingClientRect
// then add/remove .fixed class
.header.fixed { position: fixed; }
hover to see old →
Animation
Advanced

Scroll-linked animations without a library

Modern animation-timeline: view();
animation-range: entry;
/* pure CSS, GPU-accelerated */
Old // JS + IntersectionObserver
observer.observe(el)
el.style.opacity = …
hover to see old →
Workflow
Beginner

Nesting selectors without Sass or Less

Modern .nav {
  & a { color: #888; }
}
/* plain .css, no build */
Old // requires Sass compiler
.nav {
  & a { color: #888; }
}
hover to see old →
Layout
Intermediate

Responsive components without media queries

Modern @container (width < 400px) {
  .card { flex-direction: column; }
}
Old @media (max-width: 768px) {
  .card { … }
}
/* viewport, not container */
hover to see old →
Colors
Intermediate

Mixing colors without a preprocessor

Modern background: color-mix(
  in oklch, #3b82f6,
  #ec4899);
Old // Sass required
$blend: mix(
  $blue, $pink, 60%);
hover to see old →
Selectors
Intermediate

Selecting parent elements without JavaScript

Modern .card:has(img) {
  grid-template: auto 1fr;
}
Old // JavaScript required
el.closest('.parent')
  .classList.add(…)
hover to see old →
Layout
Beginner

Centering elements without the transform hack

Modern .parent {
  display: grid;
  place-items: center;
}
Old position: absolute;
top: 50%; left: 50%;
transform: translate(-50%,-50%);
hover to see old →
40
Comparisons
8
Categories
2026
Up to date
0
Dependencies needed

New CSS drops every month.

Get one old → modern comparison in your inbox every week.

ESC