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.

TopicThenNow
Centeringposition: absolute; top: 50%; transform: translate(-50%, -50%)display: grid; place-items: center
Aspect ratiopadding-bottom: 56.25%; height: 0aspect-ratio: 16 / 9
Element spacingmargin on children + :not(:last-child)gap: 1rem
Full-bleed positioningtop: 0; right: 0; bottom: 0; left: 0inset: 0
Logical propertiesmargin-left, padding-topmargin-inline-start, padding-block
Nested grid alignmentDuplicate track definitions on child elementgrid-template-columns: subgrid
Grid area naminggrid-column: 1 / 3; grid-row: 1grid-area: header + grid-template-areas
Responsive components@media (min-width: 600px)@container (min-width: 400px)
Sticky elementsJS scroll listener + class toggleposition: sticky; top: 0
Mobile viewport heightheight: 100vh (breaks on mobile)height: 100svh or dvh
Viewport unitsvw / vh onlysvh, dvh, lvh, svw, dvw
Scroll chainingJS wheel/touchmove preventDefaultoverscroll-behavior: contain
Scrollbar layout shiftoverflow-y: scroll or hardcoded paddingscrollbar-gutter: stable
Lazy renderingIntersectionObserver + visibility togglecontent-visibility: auto
Element scalingtransform: scale(0.5) + negative marginszoom: 0.5
Clip-path shapesclip-path: path("M 0 0...") — px only, not responsiveclip-path: shape(from 0% 0%, line to 100% 0%, ...)
Responsive clip pathsSVG <clipPath> + clipPathUnits markupclip-path: polygon(0 0, 100% 0, ...)
Non-round cornersclip-path polygon() hackscorner-shape: squircle / notch / scoop
Image fitbackground-image + background-size: coverobject-fit: cover
Fill available spacewidth: calc(100% - 48px)width: stretch
Tooltip positioningPopper.js / JS getBoundingClientRectanchor-name + position-anchor
Frosted glassPseudo-element blurred background copybackdrop-filter: blur(12px)
Multi-column layoutFloat columns or JS masonrycolumns: 3
Touch scroll interferenceJS touchmove preventDefault()touch-action: none
TopicThenNow
Scroll animationsIntersectionObserver + class toggleanimation-timeline: view()
Scroll snappingSwiper.js / custom carousel JSscroll-snap-type: x mandatory
Page transitionsJS framework (GSAP / Barba.js)view-transition-name + @view-transition
Entry animationsrequestAnimationFrame / setTimeout delay@starting-style { opacity: 0 }
Animating display: noneJS class + visibility workaroundtransition-behavior: allow-discrete
Independent transformstransform: rotate(45deg) scale(1.2)rotate: 45deg; scale: 1.2; translate
Height auto animationJS scrollHeight measurementinterpolate-size: allow-keywords; height: auto
Custom easingcubic-bezier() trial and errorlinear(0, 0.5 50%, 1)
Staggered animationsnth-child + hardcoded delay per itemanimation-delay: calc(sibling-index() * 100ms)
Scroll spyIntersectionObserver + JS active class:target-current { color: active }
Carousel navigationSwiper.js prev/next button handlers::scroll-button(next); ::scroll-marker
TopicThenNow
Color mixingSass mix() / darken()color-mix(in oklch, blue 60%, white)
Perceptually uniform colors#hex / hsl() — non-uniform stepsoklch(0.55 0.2 264)
Dark mode colorsDuplicate rules in @media prefers-color-schemecolor: light-dark(#111, #eee)
Dark mode defaults@media (prefers-color-scheme: dark)color-scheme: light dark
Color variantsSass lighten() / darken()oklch(from var(--brand) calc(l + 0.2) c h)
Wide gamut colorssRGB hex onlyoklch() / color(display-p3 ...)
Auto readable textcolor: white / black hardcoded per backgroundcolor: contrast-color(var(--bg))
Blend modesJS canvas or Photoshop exportsmix-blend-mode: multiply
TopicThenNow
Parent selectionJS classList on parent element.form:has(input:invalid) { border: red }
Selector groupingh1, h2, h3 { } repeated selectors:is(h1, h2, h3) { }
Zero-specificity resetsComplex specificity tricks:where(ul, ol) { margin: 0 }
Focus styles:focus { outline } — all input types:focus-visible { outline }
Form validation stylesJS blur event + .touched classinput:user-valid / :user-invalid
CSS nestingSass / Less nestingNative CSS & nesting
Scoped stylesBEM / CSS Modules@scope (.card) { .title { } }
Text highlightinginnerHTML.replace() wrapping <mark>CSS.highlights.set() + ::highlight(search)
Sticky/snapped stateJS scroll position detection@container scroll-state(stuck: top) { }
Range style queriesMultiple separate @container blocks@container style(--n > 50) { }
Form control stylingappearance: none + full custom rebuildaccent-color: purple
Hover tooltipsJS mouseenter / mouseleave eventspopover=hint + interestfor
TopicThenNow
Fluid typographycalc() + @media breakpoints per sizefont-size: clamp(1rem, 2.5vw, 2rem)
Balanced headlinesManual <br> or max-width trial and errortext-wrap: balance
Body text orphansDefault wrappingtext-wrap: pretty
Multiline truncationJS height measurement + appended "..."line-clamp: 3; overflow: hidden
Drop capsfloat: left + manual font-size adjustment::first-letter { initial-letter: 3 }
Variable fontsSeparate @font-face per weightfont-weight: 100 900 (variable font)
Font loadingFOIT — flash of invisible textfont-display: swap
Vertical text centeringpadding-top/bottom trial and errortext-box: trim-both cap alphabetic
Rotated texttransform: rotate(-90deg) + margin hackswriting-mode: vertical-lr
Decorative underlinesborder-bottom or box-shadow hackstext-decoration-thickness + text-underline-offset
TopicThenNow
Theme variablesSass $variables--custom-property + var()
Specificity control!important overrides@layer base, components, utilities
Typed custom propertiesUntyped --var (no animation support)@property --hue { syntax: "<angle>" }
CSS feature detectionJS feature sniffing@supports (display: grid) { }
Media query ranges@media (min-width: 600px) and (max-width: 900px)@media (600px <= width <= 900px)
Reusable CSS logicSass @mixin@function --space($n) { result: calc($n * 8px) }
Inline conditionalsJS class toggling per variantcolor: if(style(--size: lg): 2rem; else: 1rem)
Typed attribute valuesJS dataset access + parseInt()width: attr(data-size type(<length>))
Auto-growing textareaJS oninput + scrollHeight resizefield-sizing: content
Scrollbar styling::-webkit-scrollbar (non-standard)scrollbar-color + scrollbar-width
Modal dialogsCustom div + JS focus trap<dialog> element
Dropdown menusJS click toggle + aria-expandedpopover API
Light dismissJS click-outside detectionclosedby="any" attribute
Exclusive accordionsJS accordion open/close logic<details name="group">
Modal controlsonclick handlers + getElementByIdcommandfor + command attributes
Custom selectSelect2 / Choices.js librariesappearance: base-select
Stacking contextopacity: 0.99 hackisolation: isolate
ESC