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