Patterns
Every component pattern, live and interactive — the same markup the framework ships, not a picture of it.
Controls
Buttons
.cta is persuasion — filled and larger. btn is affordance: it pairs element: "button" with class: "btn", so a bare <button> gets it with no class at all, at zero specificity.
CTA roles
Links
A plain link, an external one, a mailto and a tel— each gets its own affordance fromanchor-link.css with no extra class.
Icon
.icon sizes in em and inherits currentColor. Because.cta and btn are already inline-flex with a gap, a start or end icon is child order, not a modifier class.
Content
Card
Card
A surface with padding, radius and a border, all from the pattern's token chain.
With a utility
A colour utility overrides the pattern because utilities sit in a later cascade layer.
Third
Sized by
.grid-autoand--grid-min.
Badge and tag
Split by behaviour, not by looks: a badge is a static label; a tagis editable or dismissable. Their shared group is named label — deliberately not tag, because a pattern whose key equals its group name collides in the token namespace.
- Design
- System
Media object and tile
Media object
Figure beside body, the oldest layout primitive there is.
Clickable tile
The whole surface is the target.
Comment thread
Pull quote
A pattern whose responsive behaviour lives in a container query keyed on its own classes survives the tailwind strip; one keyed on breakpoint prefixes does not.
Table
| Pattern | Tier | Needs JS |
|---|---|---|
| card | CSS | No |
| drawer | CSS + Popover API | No |
| theme editor | Web component | Yes — quarantined |
Separator
Counter, hand-authored
Hand-written — no <Counter />, no <wc-counter>. The finished figure is the only markup; see enhanced below for the same figure counting up to it.
Disclosure
Native <details>. Note the pattern deliberately ships no::details-content transition: it deadlocks in Chrome 149, becausecontent-visibility: hidden means no layout, so auto resolves to 0 and the disclosure never opens at all.
What the framework guarantees
Fallback markup is accessible and usable with no JavaScript.
Open by default
State lives in the DOM, so it survives with scripting off.
Reveal
The class-driven disclosure, for when the trigger isn't a <summary>..reveal animates on .is-open via the grid 0fr → 1frtrick, so it works where ::details-content can't reach.
.reveal.is-open — shown open here; the framework animates the flip.
Revealed content.
Tree
packages
core
- css/
- src/
- generator
Overlays
All four edges, plus a dialog. Each states only where it starts(--translate-x-from); the transition, the @starting-style and the scrim come from the top-layer driver and popover.css. They animateout as well as in — which the keyframe versions never did.
drawer--left
Esc or click outside to dismiss.
drawer--right
Esc or click outside to dismiss.
drawer--top
Esc or click outside to dismiss.
drawer--bottom
Esc or click outside to dismiss.
Composed
drawer--right open-fade-in
Popover and dropdown
Positioned with the Anchor Positioning API.
Revealed on hover, no scrim.
Gallery, hand-authored
Hand-written — no <Gallery />, no <wc-gallery>. Each thumbnail is a command="show-modal" commandfor="…" invoker for its own<dialog closedby="any">, so open and close both work with no JavaScript at all. What's missing this way is prev/next — acommand="show-modal" button inside an open dialog would stack a second modal on top rather than replace the first, so that control only exists once<wc-gallery> upgrades it. See enhanced below.
Navigation
Navbar
A horizontal row of links, with alignment. The primitive sitenav's wide state is shaped like.
Sitenav
One markup tree, two presentations, switched purely by container width: a toggle + drawer below the breakpoint, an inline navbar with dropdowns above it. These demos use sitenav--bp-sm (30rem) rather than themd default, because the measured container here is this prose column — not the window. A bp-md sitenav in a column this narrow would stay a drawer at every window size, which is the pattern working, not failing.
The same tree with sitenav--drawer-end. Only the drawer's resting edge differs; at width both must reach the identical navbar.
Navshell
A nav aside beside content, collapsing to a toggle + drawer. This whole page is already inside two of them — the site nav and the on-this-page nav — so the demo below is a third, nested one level deeper, which is the point: it promotes independently of both.
Its own --_navshell-wide flag is reset at this root, and the content column it sits in is a container — so it measures the space it actually has, not the viewport, and stays stacked here regardless of the two shells around it.
Layout
Cluster
Split
class="split flex-col md-split-1-2" — stacked below 48rem, 1:2 above. .flex-col beats .split by layer; .md-split-1-2beats .flex-col by sitting later in the same file.
1
2
Subgrid
Short
One line.
Footer
Longer heading that wraps
Two lines of body copy so the tranches have something to align against.
Footer
Third
Body.
Footer
Headings, bodies and footers sit on shared row lines because each card's rows are subgrid — not because anything was measured.
Masonry
Item 1
▪ ▪ ▪ ▪ ▪ ▪
Item 2
▪ ▪ ▪
Item 3
▪ ▪ ▪ ▪ ▪
Item 4
▪ ▪
Item 5
▪ ▪ ▪ ▪
Item 6
▪ ▪ ▪
Grouped
.group collapses the borders between children into one shared edge. It reads--_flex-direction from whatever flex utility is on it via a style query, so it knows which corners to round without being told.
Motion & text
Typographic effects from text-effects.css. All CSS-only — the animation engine drives them, so they respect prefers-reduced-motion without a script.
- A typewriter line.
- Animated gradient
- Highlighted phrase
- Staggered letters
Progressive enhancement
The tier-2 elements. Every one of these renders something usable before its bundle loads — that is the bar for existing at all. Turn JavaScript off and the carousel is a scroll-snap strip, the compare slider is two images, the entries are headings and definition lists.
Carousel
One
Two
Three
Scroll for more
.carousel is the shell and .carousel__track the scroller, so the hint and the controls sit outside the scrollport. Scroll-snap, the scrollbar and the hint are pure CSS; where the browser has ::scroll-button() and::scroll-marker (Chromium today) so are the arrows and dots, and where it doesn't the element builds matching real ones.
Carousel — looping
One
Two
Three
loop is opt-in: it clones the slides at both ends, which triples the markup and duplicates every image, so the default is an honest finite strip whose scrollbar reflects real content. autoplay implies it.
Carousel — the Astro wrapper
Scroll for more
Use the left and right arrow keys, or scroll sideways, to move between slides.
Three images of three different aspect ratios, all the same height —--carousel-slide-aspect gives each slide's media box a known ratio and the image covers it. The first is eager/fetchpriority="high" as the LCP candidate; the rest are lazy, which a horizontal scroll container honours, so they defer with no JavaScript involved.
Gallery — the Astro wrapper
Same fallback as the hand-authored gallery above — <Gallery /> just emits it for you, inside its own <wc-gallery>. Once that loads: prev/next, arrow keys, swipe to change images or swipe down to dismiss, an announced image counter, the next/previous image warming in the background, and the thumbnail morphing into the full image viadocument.startViewTransition where supported.
Counter — the Astro wrapper
Same fallback as the hand-authored counter above — <Counter /> just emits it for you, inside its own <wc-counter>. Once that loads and this figure scrolls into view, it counts up from 0. Reload to see it again — it only counts once.
Split panel
Start
Drag the divider.
End
Resizable panes.
Image compare
Two images and a drag handle; with no JS you simply see the "after" image.
Entries
The exemplar for the whole tier: semantic <h3> + <dl>pairs with no JS, parsed into a table when the container is wide enough.
css
- Emits
- styles.css
- Tokens
- Yes
tailwind
- Emits
- tailwind.css
- Tokens
- @theme
bricks
- Emits
- full payload
- Tokens
- JSON import
Copy button
Dismissable
Marquee
Hover to pause (marquee--pause-hover).
Multi field
Feedback
Banner
An announcement bar. Add .sticky to pin it.
Notification
Saved
Toast shell — entry, exit and an auto-dismiss progress bar.
A threaded comment, laid out on a grid so the avatar column stays aligned.
The compact variant.