Stop writing CSS
like it's 2015.
Modern CSS code snippets, side by side with the old hacks they replace. Every technique you still Google has a clean, native replacement now.
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
display: grid;
place-items: center;
}
/* child needs nothing. */
All comparisons
75 snippetsText highlighting without DOM manipulation
.replace(/term/g, '<mark>$&</mark>');
background: yellow;
}
Path shapes without SVG clip paths
/* px values — not responsive */
from 0% 0%, line to 100% 0%,
curve to 0% 80% via 50% 105%);
Scaling elements without transform hacks
margin-bottom: -50%; /* hack */
Readable text without manual contrast checks
color: white; /* hardcoded */
Reduced motion without JavaScript detection
if (mq.matches) el.style.animation = 'none';
* { animation-duration: 0.01ms !important; }
}
Perceptually uniform colors with oklch
--brand-light: #818cf8;
--brand-dark: #3730a3;
/* guess-and-check each shade */
--brand-light: oklch(0.75 0.2 264);
--brand-dark: oklch(0.35 0.2 264);
/* only L changes, same perceived hue */
CSS feature detection without JavaScript
if (CSS.supports('display', 'grid')) {
el.classList.add('grid');
}
.layout { display: grid; }
}
/* no JS required */
Frosted glass effect without opacity hacks
background-image: url(bg.jpg);
filter: blur(12px);
z-index: -1; }
backdrop-filter: blur(12px);
background: rgba(255,255,255,.1);
}
Exclusive accordions without JavaScript
details.forEach(d => d.open = false);
this.open = true;
<details name="faq">...</details>
/* browser closes others */
Custom easing curves without cubic-bezier guessing
anime({ targets: el,
easing: 'easeOutBounce' });
linear(0, 1.2, 0.9, 1.05, 1);
/* bounce — no JS needed */
Preventing layout shift from scrollbar appearance
/* or hardcode the scrollbar width */
body { padding-right: 17px; }
scrollbar-gutter: stable;
}
/* scrollbar space always reserved */
Media query ranges without min-width and max-width
and (max-width: 1200px) {
/* styles */
}
/* styles */
}
Preventing scroll chaining without JavaScript
modal.addEventListener('wheel', e =>
e.preventDefault(), { passive: false })
overflow-y: auto;
overscroll-behavior: contain;
}
/* page stays still */
Responsive images without the background-image hack
background-image: url(...);
background-size: cover;
background-position: center;
}
object-fit: cover;
width: 100%;
height: 200px;
}
Scrollbar styling without -webkit- pseudo-elements
::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-thumb { background: #888; }
scrollbar-width: thin;
scrollbar-color: #888 transparent;
}
Mobile viewport height without the 100vh hack
height: 100vh;
}
/* overflows on mobile */
height: 100dvh;
}
/* adapts to browser chrome */
Form validation styles without JavaScript
el.addEventListener('blur', () =>
el.classList.add('touched'))
/* .touched:invalid { color: red } */
border-color: red;
}
input:user-valid {
border-color: green;
}
Auto-growing textarea without JavaScript
el.addEventListener('input', () => {
el.style.height = 'auto';
el.style.height = el.scrollHeight + 'px'; })
field-sizing: content;
min-height: 3lh;
}
/* grows with content, no JS */
Smooth height auto animations without JavaScript
el.style.height = el.scrollHeight + 'px';
el.addEventListener('transitionend', ...)
.accordion { height: 0; overflow: hidden;
transition: height .3s ease; }
.accordion.open { height: auto; }
Range style queries without multiple blocks
@container style(--p: 51%) {}
@container style(--p: 52%) {}
/* ...for each value */
--progress > 50%
) {
.bar { ... }
}
Sticky & snapped element styling without JavaScript
'scroll', () => {
/* check position */
});
stuck: top
) {
.header { ... }
}
Typed attribute values without JavaScript
el.style.width =
el.dataset.pct + '%';
width: attr(
data-pct type(<percentage>)
);
}
Inline conditional styles without JavaScript
el.classList.toggle(
'primary', isPrimary
);
background: if(
style(--variant: primary):
blue; else: gray
);
}
Reusable CSS logic without Sass mixins
@function fluid($min, $max) {
@return clamp(...);
}
--min, --max
) {
@return clamp(...);
}
Corner shapes beyond rounded borders
clip-path: polygon(
... /* 20+ points */
);
}
border-radius: 2em;
corner-shape: squircle;
}
Responsive clip paths without SVG
clip-path: path(
'M0 200 L100 0...'
);
}
clip-path: shape(
from 0% 100%, ...
);
}
Scroll spy without IntersectionObserver
IntersectionObserver(cb);
/* 15+ lines of JS */
color: var(--accent);
}
Filling available space without calc workarounds
width: calc(100% - 40px);
/* or width: 100% and overflow */
}
width: stretch;
}
/* fills container, keeps margins */
Staggered animations without nth-child hacks
li:nth-child(2) { --i: 1; }
li:nth-child(3) { --i: 2; }
/* repeat for every item… */
transition-delay:
calc(0.1s * (sibling-index() - 1));
}
Carousel navigation without a JavaScript library
new Swiper('.carousel', {
navigation: { /* … */ },
pagination: { /* … */ },
});
content: "➡";
}
.carousel li::scroll-marker {
content: '';
}
Vertical text centering without padding hacks
padding: 10px 20px;
/* looks off-center, tweak top/bottom */
padding-top: 8px; /* hack */
}
text-box: trim-both cap alphabetic;
}
/* true optical centering */
Hover tooltips without JavaScript events
btn.addEventListener('mouseenter',
() => showTooltip())
/* + focus, blur, positioning */
<div id="tip" popover=hint>
Tooltip content
</div>
Modal controls without onclick handlers
document.querySelector('#dlg')
.showModal()">Open</button>
command="show-modal">Open</button>
<dialog id="dlg">...</dialog>
Dialog light dismiss without click-outside listeners
dialog.addEventListener('click',
(e) => { /* check bounds */ })
Click outside to close
</dialog>
/* no JS listeners */
Customizable selects without a JavaScript library
new Choices('#my-select');
/* rebuilds entire DOM */
select ::picker(select) {
appearance: base-select;
}
Vivid colors beyond sRGB
color: rgb(200, 80, 50);
}
/* sRGB only, washed on P3 */
color: oklch(0.7 0.25 29);
}
/* or color(display-p3 1 0.2 0.1) */
Color variants without Sass functions
.btn { background: #e0e0e0; }
background: oklch(from var(--brand) calc(l + 0.2) c h);
}
Multiline text truncation without JavaScript
.card-title { overflow: hidden; }
display: -webkit-box;
-webkit-line-clamp: 3;
line-clamp: 3;
}
Drop caps without float hacks
float: left;
font-size: 3em; line-height: 1;
}
-webkit-initial-letter: 3;
initial-letter: 3;
}
Positioning shorthand without four properties
top: 0; right: 0;
bottom: 0; left: 0;
}
position: absolute;
inset: 0;
}
Lazy rendering without IntersectionObserver
new IntersectionObserver(
(entries) => { /* render */ }
).observe(el);
content-visibility: auto;
contain-intrinsic-size: auto 500px;
}
Dropdown menus without JavaScript toggles
.menu.open { display: block; }
/* + JS: click, clickOutside, ESC, aria */
#menu[popover] {
position: absolute;
}
Tooltip positioning without JavaScript
position: fixed, update on scroll */
.tooltip { position: fixed; }
.tooltip {
position-anchor: --tip;
top: anchor(bottom);
}
Scoped styles without BEM naming
.card__title { … }
.card__body { … }
// or CSS Modules / styled-components */
.title { font-size: 1.25rem; }
.body { color: #444; }
}
/* .title only inside .card */
Typed custom properties without JavaScript
:root { --hue: 0; }
hsl(var(--hue), …) /* no interpolation */
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
/* animatable, validated */
Independent transforms without the shorthand
/* change one = rewrite all */
translate: 10px 0;
rotate: 45deg;
scale: 1.2;
}
/* animate any one without touching the rest */
Animating display none without workarounds
el.addEventListener('transitionend', …)
visibility + opacity + pointer-events
transition-behavior: allow-discrete; }
.panel.hidden { opacity: 0; display: none; }
/* no JS wait or visibility hack */
Entry animations without JavaScript timing
requestAnimationFrame(() => {
el.classList.add('visible');
});
.card { @starting-style { opacity: 0; transform: translateY(10px); } }
/* no rAF/setTimeout */
Page transitions without a framework
Barba.init({ … })
transition hooks + duration state
.hero { view-transition-name: hero; }
/* no Barba, no React TG */
Scroll snapping without a carousel library
$('.carousel').slick({ … })
touchstart / scroll handlers
.carousel > * { scroll-snap-align: start; }
/* no lib, no touch handlers */
Balanced headlines without manual line breaks
h1 { text-align: center; }
.balance-text /* JS lib */
text-wrap: balance;
}
/* no br or JS */
Font loading without invisible text
/* Default: invisible text until load */
font-family: "MyFont";
font-display: swap;
}
Multiple font weights without multiple files
@font-face { font-weight: 700; }
/* 4+ files */
font-family: "MyVar";
src: url("MyVar.woff2");
font-weight: 100 900;
}
Dark mode defaults without extra CSS
input, select, textarea { ... }
}
color-scheme: light dark;
}
Dark mode colors without duplicating values
color: #eee;
}
color-scheme: light dark;
Low-specificity resets without complicated selectors
/* or (0,0,1) specificity, still wins */
margin: 0;
padding-inline-start: 1.5rem;
}
Direction-aware layouts without left and right
padding-right: 1rem;
[dir="rtl"] .box { margin-right: ... }
padding-inline-end: 1rem;
border-block-start: 1px solid;
Naming grid areas without line numbers
grid-column: 1 / 3;
grid-row: 2;
display: grid;
grid-template-areas: "header header" "sidebar main" "footer footer";
}
Aligning nested grids without duplicating tracks
grid-template-columns: 1fr 1fr 1fr;
/* duplicate parent tracks */
}
display: grid;
grid-template-columns: subgrid;
}
Modal dialogs without a JavaScript library
/* + JS: open/close, ESC, focus trap */
padding: 1rem;
}
dialog::backdrop { background: rgb(0 0 0 / .5); }
Styling form controls without rebuilding them
// + 20+ lines of custom box/border/background
input[type="radio"] {
accent-color: #7c3aed;
}
Grouping selectors without repetition
margin-bottom: 0.5em;
}
margin-bottom: 0.5em;
}
Focus styles without annoying mouse users
// Shows on mouse click too, or people remove it (a11y fail)
outline: 2px solid var(--focus-color);
}
Controlling specificity without !important
.page .card .title { ... }
.page .card .title.special { color: red !important; }
@layer utilities { .mt-4 { margin-top: 1rem; } }
Theme variables without a preprocessor
// Compiles to static #7c3aed
.btn { background: $primary; }
--primary: #7c3aed;
}
.btn { background: var(--primary); }
Fluid typography without media queries
@media (min-width: 600px) { h1 { font-size: 1.5rem; } }
@media (min-width: 900px) { h1 { font-size: 2rem; } }
font-size: clamp(1rem, 2.5vw, 2rem);
}
Spacing elements without margin hacks
.grid > *:last-child { margin-right: 0; }
display: flex;
gap: 16px;
}
Aspect ratios without the padding hack
.inner { position: absolute; inset: 0; }
aspect-ratio: 16 / 9;
}
Sticky headers without JavaScript scroll listeners
// then add/remove .fixed class
.header.fixed { position: fixed; }
position: sticky;
top: 0;
}
Scroll-linked animations without a library
observer.observe(el)
el.style.opacity = …
animation-range: entry;
/* pure CSS, GPU-accelerated */
Nesting selectors without Sass or Less
.nav {
& a { color: #888; }
}
& a { color: #888; }
}
/* plain .css, no build */
Responsive components without media queries
.card { … }
}
/* viewport, not container */
.card { flex-direction: column; }
}
Mixing colors without a preprocessor
$blend: mix(
$blue, $pink, 60%);
in oklch, #3b82f6,
#ec4899);
Selecting parent elements without JavaScript
el.closest('.parent')
.classList.add(…)
grid-template: auto 1fr;
}
Centering elements without the transform hack
top: 50%; left: 50%;
transform: translate(-50%,-50%);
display: grid;
place-items: center;
}
Frequently asked
What is modern-css.com?
A reference site for web developers showing side-by-side comparisons of outdated CSS techniques and their modern native replacements. It covers 70+ CSS properties and features including Grid, custom properties, CSS nesting, container queries, scroll-driven animations, anchor positioning, and more.
Do I still need CSS preprocessors like Sass or Less?
Many features once exclusive to CSS preprocessors are now built into native CSS. CSS custom properties
replace Sass variables, native CSS nesting replaces Sass nesting syntax, color-mix() and
relative color syntax replace Sass color functions, and @layer replaces manual specificity
management. Most common Sass use cases can now be handled with native CSS.
What browsers support modern CSS features?
Browser support varies by feature. Core properties like CSS Grid, Custom Properties, Flexbox gap, and
aspect-ratio are widely available with 95%+ global support. Newer features like CSS nesting,
container queries, :has(), and anchor positioning are newly available across all major
browsers. Each snippet shows a Baseline status and a live browser support percentage sourced from caniuse.
What is CSS Baseline?
CSS Baseline is a classification system from the Web Platform project that describes how broadly a web feature is supported. Widely available means all major browsers have supported it for at least 2.5 years. Newly available means it recently shipped in all major browsers. Limited availability means it works in some browsers but not all. Every snippet on this site shows its Baseline status.
Is this accordion built with JavaScript?
No. This FAQ uses the native HTML <details> and <summary> elements.
The browser handles open and close natively — no JavaScript required. The + toggle indicator is
a CSS ::after pseudo-element. A fitting way to answer questions about modern web features.