Modern CSS Cheatsheet

Old patterns replaced with native CSS. 76 comparisons across 6 categories: Layout, Animation, Color, Selectors, Typography, and Workflow. Each row shows what you used to write and what replaces it.

Layout Animation Color Selectors Typography Workflow
Topic Then Now
Centering position: absolute; top: 50%; transform: translate(-50%, -50%) display: grid; place-items: center
Aspect ratio padding-bottom: 56.25%; height: 0 aspect-ratio: 16 / 9
Element spacing margin on children + :not(:last-child) gap: 1rem
Full-bleed positioning top: 0; right: 0; bottom: 0; left: 0 inset: 0
Logical properties margin-left, padding-top margin-inline-start, padding-block
Nested grid alignment Duplicate track definitions on child element grid-template-columns: subgrid
Grid area naming grid-column: 1 / 3; grid-row: 1 grid-area: header + grid-template-areas
Responsive components @media (min-width: 600px) @container (min-width: 400px)
Sticky elements JS scroll listener + class toggle position: sticky; top: 0
Mobile viewport height height: 100vh (breaks on mobile) height: 100svh or dvh
Viewport units vw / vh only svh, dvh, lvh, svw, dvw
Scroll chaining JS wheel/touchmove preventDefault overscroll-behavior: contain
Scrollbar layout shift overflow-y: scroll or hardcoded padding scrollbar-gutter: stable
Lazy rendering IntersectionObserver + visibility toggle content-visibility: auto
Element scaling transform: scale(0.5) + negative margins zoom: 0.5
Clip-path shapes clip-path: path("M 0 0...") — px only, not responsive clip-path: shape(from 0% 0%, line to 100% 0%, ...)
Responsive clip paths SVG <clipPath> + clipPathUnits markup clip-path: polygon(0 0, 100% 0, ...)
Non-round corners clip-path polygon() hacks corner-shape: squircle / notch / scoop
Image fit background-image + background-size: cover object-fit: cover
Fill available space width: calc(100% - 48px) width: stretch
Tooltip positioning Popper.js / JS getBoundingClientRect anchor-name + position-anchor
Frosted glass Pseudo-element blurred background copy backdrop-filter: blur(12px)
Topic Then Now
Scroll animations IntersectionObserver + class toggle animation-timeline: view()
Scroll snapping Swiper.js / custom carousel JS scroll-snap-type: x mandatory
Page transitions JS framework (GSAP / Barba.js) view-transition-name + @view-transition
Entry animations requestAnimationFrame / setTimeout delay @starting-style { opacity: 0 }
Animating display: none JS class + visibility workaround transition-behavior: allow-discrete
Independent transforms transform: rotate(45deg) scale(1.2) rotate: 45deg; scale: 1.2; translate
Height auto animation JS scrollHeight measurement interpolate-size: allow-keywords; height: auto
Custom easing cubic-bezier() trial and error linear(0, 0.5 50%, 1)
Staggered animations nth-child + hardcoded delay per item animation-delay: calc(sibling-index() * 100ms)
Scroll spy IntersectionObserver + JS active class :target-current { color: active }
Carousel navigation Swiper.js prev/next button handlers ::scroll-button(next); ::scroll-marker
Topic Then Now
Color mixing Sass mix() / darken() color-mix(in oklch, blue 60%, white)
Perceptually uniform colors #hex / hsl() — non-uniform steps oklch(0.55 0.2 264)
Dark mode colors Duplicate rules in @media prefers-color-scheme color: light-dark(#111, #eee)
Dark mode defaults @media (prefers-color-scheme: dark) color-scheme: light dark
Color variants Sass lighten() / darken() oklch(from var(--brand) calc(l + 0.2) c h)
Wide gamut colors sRGB hex only oklch() / color(display-p3 ...)
Auto readable text color: white / black hardcoded per background color: contrast-color(var(--bg))
Topic Then Now
Parent selection JS classList on parent element .form:has(input:invalid) { border: red }
Selector grouping h1, h2, h3 { } repeated selectors :is(h1, h2, h3) { }
Zero-specificity resets Complex specificity tricks :where(ul, ol) { margin: 0 }
Focus styles :focus { outline } — all input types :focus-visible { outline }
Form validation styles JS blur event + .touched class input:user-valid / :user-invalid
CSS nesting Sass / Less nesting Native CSS & nesting
Scoped styles BEM / CSS Modules @scope (.card) { .title { } }
Text highlighting innerHTML.replace() wrapping <mark> CSS.highlights.set() + ::highlight(search)
Sticky/snapped state JS scroll position detection @container scroll-state(stuck: top) { }
Range style queries Multiple separate @container blocks @container style(--n > 50) { }
Form control styling appearance: none + full custom rebuild accent-color: purple
Hover tooltips JS mouseenter / mouseleave events popover=hint + interestfor
Topic Then Now
Fluid typography calc() + @media breakpoints per size font-size: clamp(1rem, 2.5vw, 2rem)
Balanced headlines Manual <br> or max-width trial and error text-wrap: balance
Body text orphans Default wrapping text-wrap: pretty
Multiline truncation JS height measurement + appended "..." line-clamp: 3; overflow: hidden
Drop caps float: left + manual font-size adjustment ::first-letter { initial-letter: 3 }
Variable fonts Separate @font-face per weight font-weight: 100 900 (variable font)
Font loading FOIT — flash of invisible text font-display: swap
Vertical text centering padding-top/bottom trial and error text-box: trim-both cap alphabetic
Topic Then Now
Theme variables Sass $variables --custom-property + var()
Specificity control !important overrides @layer base, components, utilities
Typed custom properties Untyped --var (no animation support) @property --hue { syntax: "<angle>" }
CSS feature detection JS feature sniffing @supports (display: grid) { }
Media query ranges @media (min-width: 600px) and (max-width: 900px) @media (600px <= width <= 900px)
Reusable CSS logic Sass @mixin @function --space($n) { result: calc($n * 8px) }
Inline conditionals JS class toggling per variant color: if(style(--size: lg): 2rem; else: 1rem)
Typed attribute values JS dataset access + parseInt() width: attr(data-size type(<length>))
Auto-growing textarea JS oninput + scrollHeight resize field-sizing: content
Scrollbar styling ::-webkit-scrollbar (non-standard) scrollbar-color + scrollbar-width
Modal dialogs Custom div + JS focus trap <dialog> element
Dropdown menus JS click toggle + aria-expanded popover API
Light dismiss JS click-outside detection closedby="any" attribute
Exclusive accordions JS accordion open/close logic <details name="group">
Modal controls onclick handlers + getElementById commandfor + command attributes
Custom select Select2 / Choices.js libraries appearance: base-select

New CSS drops.

Join 500+ readers who've survived clearfix hacks.

ESC