A growing collection of recipes done with pure CSS or a single delegated listener. Scroll down — everything you see below runs on the platform.
recipe 01
fit-text
The font scales to its container with container-query units — no measuring, no layout shift. Drag the handle and watch it rescale live (shadcn Resizable).
recipe 02
parallax
Each frame holds a layer that drifts at its own rate as you scroll — depth rendered on the compositor (animation-timeline: view()), no scroll listener.
depth from scroll
Nine layers, nine speeds
Every blob, ring and dot rides its own view() timeline. Nothing here listens to scroll.
stagger — the same recipe over a group; each child's entrance is offset from the last.
recipe 03
reveal
Each card fades and rises into place as it enters the viewport — a view() timeline, no IntersectionObserver.
Zero client JS
Recipes lean on CSS and the platform, not hooks.
Correct on first paint
No measure-then-resize, no post-mount jump.
RSC-safe
Components stay server HTML — no "use client".
Graceful fallback
Unsupported browsers just render at rest.
Reduced-motion aware
Animations bow out when the user asks.
Tiny
Each recipe is a few lines of CSS + a thin wrapper.
recipe 04
accordion
Native <details> with a smooth interpolate-size open/close. Exclusive (only one open) via a shared name — no state.
Does this ship JavaScript?
Almost none. fit-text, parallax, reveal and accordion are pure CSS; analytics is a single delegated listener for the whole app.
Do the components need "use client"?
No. They only render markup and set CSS variables / data attributes, so they work as Server Components.
What about old browsers?
Every recipe is gated behind @supports or native fallbacks — unsupported engines render the content at rest, never broken.
Can I use one recipe only?
Yes — each is its own subpath import, so you ship only what you use.
recipe 05
analytics
These buttons are plain server HTML with data-track attributes. One delegated listener (in instrumentation-client.ts) catches every click — a toast pops up for each event.
recipe 06
scroll-progress
The emerald bar pinned to the very top of this page is the scroll-progress recipe — one fixed element whose scaleX rides the document's scroll() timeline. Scroll and watch it fill; no listener.
recipe 07
sticky-shrink
Scroll inside the box — the header shrinks from 96 to 56px over the first 180px, driven by one registered progress var. The logo reads that same var and scales in step. Zero JS.
Row 1 — keep scrolling to shrink the header above.
Row 2 — keep scrolling to shrink the header above.
Row 3 — keep scrolling to shrink the header above.
Row 4 — keep scrolling to shrink the header above.
Row 5 — keep scrolling to shrink the header above.
Row 6 — keep scrolling to shrink the header above.
Row 7 — keep scrolling to shrink the header above.
Row 8 — keep scrolling to shrink the header above.
Row 9 — keep scrolling to shrink the header above.
Row 10 — keep scrolling to shrink the header above.
Row 11 — keep scrolling to shrink the header above.
Row 12 — keep scrolling to shrink the header above.
Row 13 — keep scrolling to shrink the header above.
Row 14 — keep scrolling to shrink the header above.
recipe 08
slider
A CSS scroll-snap track — swipe or trackpad works everywhere. Dots and prev/next buttons are CSS Carousel pseudo-elements, so they need no JS. One slide at a time with a peek of the next; the arrow advances exactly one. (For a multi-slide row, use a smaller itemSize — the arrows then page the group, which is how scroll-buttons work. Controls want Chrome 135+.)
recipe 09
dialog
A native <dialog> opened by the HTML invoker commands (command="show-modal") — modal, backdrop, Esc, and backdrop-click to close (closedby="any") all native. Zero JS, no onClick. Needs Chrome 135+.
recipe 10
popover
A native popover (menu / dropdown) via the Popover API — toggle, click-outside dismiss and the top layer are native, and it auto-anchors to the button with CSS anchor positioning. Zero JS. Needs Chrome 125+.