Skip to content
Vitops
GuidesComponentsPackagesThemeReferenceChangelog

Start here

VitopsInstallationYour design systemYour config

Live preview

Theme previewAnimation libraryIconsPatterns

Components

OverviewCSS classesWeb componentsAstro componentsBricks elements

Packages

@getvitops/cli@getvitops/astro@getvitops/vite@getvitops/generator@getvitops/core@getvitops/utils@getvitops/emdash@getvitops/create

Reference

Config referenceOutput formatsColour systemType & space scalesComponent patternsIconsConsent gateConversion trackingSearchLegal documentsCSS class vocabularyBricks elements

Releases

Changelog

Patterns

Every component pattern, live and interactive — the same markup the framework ships, not a picture of it.

ControlsContentDisclosureOverlaysNavigationLayoutMotion & textProgressive enhancementFeedback

Every demo below is the real pattern. The overlays in particular are worth opening and closing: they are driven by the top-layer animation driver, and this page is where that behaviour is checked by hand.

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.

.btn on a span.cta on a link

CTA roles

ui-primaryui-secondaryui-accentbrand-primarybrand-secondaryinfosuccesswarningdanger

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-auto and --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.

ui-primaryui-secondaryui-accentbrand-primarybrand-secondaryinfosuccesswarningdanger
  • Design
  • System

Media object and tile

fig

Media object

Figure beside body, the oldest layout primitive there is.

tile

Clickable tile

The whole surface is the target.

Comment thread

  • AG
    Alex2 days ago

    A threaded comment, laid out on a grid so the avatar column stays aligned.

  • VT
    Vitops

    The compact variant.

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

PatternTierNeeds JS
cardCSSNo
drawerCSS + Popover APINo
theme editorWeb componentYes — 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.

94%

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

Dialog

Entry and exit both animate now — the old animation: on [open] only ever played once.

Scrim via --scrim-filter.

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.

HomeDocsLogin

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.

  • Home
  • Products
    ▾
    • Toolchain
      ▾
      • Generator
      • CLI
    • Components
  • Resources
    ▾
    • Guides
    • Changelog
  • Pricing

The same tree with sitenav--drawer-end. Only the drawer's resting edge differs; at width both must reach the identical navbar.

  • Home
  • Products
    ▾
    • Toolchain
    • Components
  • Pricing

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.

OverviewDetails

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

startmiddleend

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

  • A wide placeholder
  • A tall placeholder
  • A square placeholder

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

A square placeholder
A wide placeholder
A tall placeholder

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

94%

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

BeforeAfter

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

Copy the generate command

Dismissable

Dismiss me — the element removes the node and fires a cancelable event.

Marquee

csstailwindbricksdesignastrowordpress

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.

Form controls

Helper text.

Built with the design system it documents.

@getvitops on npm