CSS recipes
Short answers to problems that usually need a script. Each one is a few lines of CSS once CSS Post Processor is on the page.
Navigation and headers#
Highlight the active navigation link#
nav a:current { font-weight: 700; border-bottom: 2px solid currentColor; }Also matches parent sections: "Docs" stays active on every /docs/... page. More
Shrink the header after scrolling#
header { height: 80px; transition: height .2s; }
html:scrolled(80px) header { height: 56px; }Hide the header on scroll down, show it on scroll up#
header { position: sticky; top: 0; transition: translate .25s; }
html:scrolling-down header { translate: 0 -100%; }Shadow only while a sticky header is stuck#
header { position: sticky; top: 0; }
header:stuck { box-shadow: 0 2px 12px rgb(0 0 0 / .15); }Solid header once the hero has scrolled away#
body:has(.hero:not(:in-view)) header { background: #fff; }Highlight the current section in a table of contents#
.toc a:target-in-view { color: var(--accent); }Switch to a hamburger menu when links stop fitting#
nav ul { display: flex; flex-wrap: wrap; }
nav:has(li:wrapped) ul { display: none; }
nav:has(li:wrapped) .menu-button { display: block; }Scrolling and reveal effects#
Fade elements in the first time they scroll into view#
.reveal { transition: opacity .6s, translate .6s; }
.reveal:not(:seen) { opacity: 0; translate: 0 2rem; }Lazy-load a background image just before it's visible#
.hero:seen(300px) { background-image: url(/img/hero-large.jpg); }The image starts downloading when the element is within 300px of the viewport.
Scroll shadows on a scrollable list#
.list { overflow: auto; max-height: 20rem; }
.list:not(:scroll-end) { box-shadow: inset 0 -16px 12px -12px #0003; }Disable carousel arrows at the ends#
.carousel:scroll-start .prev, .carousel:scroll-end .next { opacity: .3; pointer-events: none; }Stagger an entrance animation#
.list > li { animation: rise .5s both; animation-delay: calc($[index] * 80ms); }Images#
Fade images in when they finish loading#
img { transition: opacity .4s; }
img:not(:loaded) { opacity: 0; }Hide broken images, or show a fallback#
img:broken { visibility: hidden; }
.avatar:has(img:broken) { background: url(/img/default-avatar.svg) center / cover; }A different background for every slide#
.slider > div { background-image: url(/img/slide-$[index].jpg); }Background image from a data attribute (CMS-friendly)#
<div class="card" data-bg="/uploads/photo-17.jpg"></div>.card { background: url($[attr(data-bg)]) center / cover; }A random hero image on each visit#
.hero { background-image: url(/img/hero-$[random(1,5)].jpg); }Layout and text#
Equal-height cards across rows#
.card h3 { height: same; }Every matched element gets the height of the tallest one, updated on resize.
Make a headline fill its box#
.hero h1 { font-size: fit(24px, 120px); }Split a row evenly however many columns there are#
.cols > div { width: calc(100% / $[count]); }Show "Read more" only when text is truncated#
.excerpt { -webkit-line-clamp: 3; display: -webkit-box; -webkit-box-orient: vertical; overflow: hidden; }
.post .more { display: none; }
.post:has(.excerpt:overflowing) .more { display: inline; }Current year in the footer#
.copyright::after { content: "© $[year] Your Company"; }Pseudo-elements#
Make an ::after "×" close a chip#
.chip::after { content: "×"; margin-left: .5em; cursor: pointer; }
.chip::after:hover { color: #dc2626; }css.on('click', '.chip::after', function () { this.remove(); });Update a badge drawn with ::after#
css.pseudo(document.querySelector('.inbox'), '::after').text = unread + ' new';Close a modal when the backdrop is clicked#
css.on('click', 'dialog::backdrop', function () { this.close(); });Highlight an icon drawn with ::before on hover#
.link::before { content: "→ "; transition: color .15s; }
.link::before:hover { color: var(--accent); }Forms and content#
Floating labels#
.field:has(input:filled, input:focus) label { translate: 0 -1.2em; font-size: .8em; }Warn about unsaved changes#
form .unsaved { display: none; }
form:has(:dirty) .unsaved { display: block; }Dim sold-out products#
.product:has(.badge:contains("sold out" i)) { opacity: .5; }Low-stock badge#
.product .low-stock { display: none; }
.product:has([data-stock<5]) .low-stock { display: inline-block; }Offline banner#
html:offline .offline-banner { display: block; }Hide video controls after a few idle seconds#
html:idle(3s) .player-controls { opacity: 0; }