Customize

Search 155 classes, tokens & guides from the framework metadata.

Graffiti

github

Elements

Single-purpose UI elements.

These are common, non-complex UI elements that just need a class or two. Forms, cards, buttons, and more.

Buttons

Button styling for `<button>` elements and `.button` class for links.

When to use: Buttons and links styled as actions.

Classes: .button, .primary, .success, .warning, .error, .ghost, .minimal, .dark, .light, .contrast, .mini

Reference Notes
Surface Variants

.dark, .light, and .contrast keep a fixed surface regardless of theme. Use them on buttons that need to sit on a particular surface (e.g., a .dark hero) and stay legible.

Reset Utility

.reset is a general-purpose utility (not a button variant) for stripping native chrome from any element. It removes background, border, radius, shadow, and padding while inheriting typography and color — useful when you want a <button> to behave like a bare interactive surface.

Styling Details
  • All buttons have consistent padding and border-radius
  • Hover, focus, and active states included
  • Disabled state reduces opacity and prevents interaction

Icon Button

Compact square button for an icon-only action, with auto glyph sizing and an aria-label requirement.

When to use: Toolbar controls, close affordances, overflow menus, and any action carried by an icon alone.

Classes: .icon-button, button[aria-label]

Reference Notes

.icon-button is a square button for an icon-only action. It builds on the base <button> styling and standardizes the things that were previously re-derived by hand: glyph size, square padding, and the focus ring. Compose it with any button variant (.primary, .ghost, .minimal, .mini).

Icon-only controls must be named for assistive tech, so always supply an aria-label (the same convention .tip relies on for its label text).

Variants

.icon-button composes with the button variants. Use .mini for a tighter footprint and .ghost / .minimal for low-emphasis toolbar controls.

Classless Auto-Square

A labelled <button> whose only child is an <svg> collapses to a square footprint with no class at all. This keeps one-off icon triggers terse while still requiring an accessible name.

Glyph Sizing
  • Any SVG inside a <button> or .button is sized from --button-icon-size (default 1.15em), so a label+icon button and an icon-only button share one optical glyph size.
  • Square padding comes from --icon-button-pad; .mini tightens it.
  • The footprint stays square via aspect-ratio: 1, so it never drifts wider than it is tall regardless of the icon.
Accessibility
  • Always pair an icon-only control with aria-label — the auto-square rule only triggers when the label is present, nudging you toward an accessible name.
  • The inner <svg> should be aria-hidden="true" so the label is the single accessible name.

Floating Action Button

Circular, fixed-position primary action pinned above the safe area.

When to use: A single, high-emphasis action that should stay reachable while content scrolls (compose, add, new message).

Classes: .fab, .button, .circle

Reference Notes

The .fab is a floating action button: a circular, elevated control pinned to the bottom-inline-end corner of the viewport, clear of the device safe area. It is composition-first — .button supplies the surface, color variant, hover lift, and focus ring, .circle supplies the round footprint, and .fab adds only the fixed positioning and a stronger elevation shadow.

Basic Usage
  • It carries a single icon, so it must always have an aria-label — the same icon-only naming convention .tip and .icon-button rely on.
  • The inner <svg> should be aria-hidden="true" so the label is the one accessible name.
Positioning and Safe Areas
  • position: fixed, pinned with inset-block-end: calc(var(--safe-bottom) + var(--pad-l)) and inset-inline-end: calc(var(--safe-right) + var(--pad-l)) so it clears the home indicator and rounded display corners.
  • Layered at var(--z-sticky) to sit above scrolling content, matching the .bottom-nav stacking convention.
  • Elevation is var(--shadow-4) — one step stronger than .bottom-nav since the FAB floats higher in the stack.
  • Default footprint is --size: 56px (the Material-standard FAB size); override --size for a mini or extended variant.
Composing with Bottom Nav

When a .bottom-nav is present, lift the FAB so it clears the tab bar by overriding its bottom inset:

Motion and Accessibility
  • The hover lift comes from .button and degrades automatically under the global prefers-reduced-motion guard — .fab introduces no animation of its own.
  • Keyboard focus shows the shared focus ring inherited from .button.
  • Because it is a real <button>, it works with zero JavaScript and is fully keyboard and screen-reader operable.

Chips

Interactive pill-shaped elements for filters, categories, and selections.

When to use: Selectable pills for filters and segmented choices.

Classes: .chip, .selected, .mini

Reference Notes

Use for multi-select interfaces, tag filters, or skill selectors.

With Icons

Icons are automatically sized to 1em.

Styling Details
  • Pill shape with border-radius: var(--br-xxl)
  • Border: var(--border-1)
  • Selected state: primary color background
  • Hover/focus/active states included
  • Fluid typography at --fl: -1 (smaller text)
Chips vs Tags
  • Chips are interactive (clickable, selectable)
  • Tags are for display (category labels, metadata)

Tags

Subtle category labels with customizable colors.

When to use: Status or category labels.

Classes: .tag, .success, .warning, .error, .info, .muted

Reference Notes

Use for metadata, categories, or status indicators.

Custom Colors

For custom categories, use --tag-color as a fallback when semantic variants are not a fit:

Available colors: --red, --orange, --yellow, --green, --teal, --blue, --indigo, --purple, --pink, --gray, --slate

With Icons

Icons are automatically sized to 1em.

Styling Details
  • Soft tinted background derived from --tag-color via OKLCH lightness/chroma adjustments (theme-aware in light and dark)
  • Text color auto-adjusts for light/dark themes
  • Subtle border derived from --tag-color
  • Fluid typography at --fl: -1 (smaller text)
  • Pill shape with border-radius: var(--br-xxl)
  • Interactive tags (<a>/<button>) lift 1px on hover (translate: 0 -1px)
Tags vs Chips
  • Tags are for display (category labels, metadata)
  • Chips are interactive (clickable, selectable)

Rating

Five-star rating in two zero-JS forms — a read-only display meter and a native-radio input.

When to use: Showing an average score or collecting a star rating without JavaScript.

Classes: .rating

Reference Notes

.rating ships two forms that share the same star visuals. Pick by whether the value is read-only or editable. Both work with native HTML + CSS only and need no JavaScript.

Display Form (Read-Only)

Put a --rating value (0–5, decimals allowed) on a <span class="rating">. The filled portion is clipped to --rating / 5, so half- and quarter-stars render faithfully. Because the stars are decorative paint, give the element an accessible name with role="img" + aria-label so the score is announced.

  • Filled stars use --warning (the semantic amber token); the empty track uses --fg-2. Override --rating-on / --rating-off to retint.
  • Adjust scale with --rating-size (defaults to 1.25em).
Input Form (Interactive)

Wrap five radio + label pairs in a <fieldset class="rating">. List them in reverse order (5 down to 1) so the general-sibling combinator can fill the checked star and every star before it. It collects a real form value, supports keyboard selection, shows a hover preview, and degrades to plain native radio buttons if the styling is unavailable.

  • Each <label> carries an aria-label ("3 stars") so the choice is announced.
  • The radios stay focusable (visually hidden, not display: none); the active label gets the standard focus ring on :focus-visible.
  • Hover preview and the checked transition are short-circuited automatically under prefers-reduced-motion via the global motion guard.
Accessibility Notes
  • Display form: role="img" + aria-label is the single source of truth for the value — keep the label in sync with --rating.
  • Input form: the fieldset + legend group the radios; submit the chosen value like any other radio group.

Reactions

Emoji reaction picker on a native select, degrades to a normal dropdown.

When to use: Letting people react to a post, comment, or message with a single emoji.

Classes: .reactions

Reference Notes

.reactions turns a native <select> into a horizontal emoji reaction bar using the customizable select API (appearance: base-select). The whole control is one class on the <select> — the trigger shows the current reaction, and opening it reveals the bar of emoji options. No JavaScript.

Anatomy

Three pieces of native markup make the customizable select work — none of them are extra Graffiti classes:

  • <button><selectedcontent></selectedcontent></button> — the trigger. The <selectedcontent> mirrors the chosen <option>, so the trigger always shows the current reaction.
  • <option> — one per reaction. The emoji is the visible glyph; a trailing .visually-hidden text label ("Like", "Love", …) gives each option a real accessible name so it isn't announced as "thumbs-up sign".
  • class="reactions" on the <select> — the only Graffiti class involved.
Behaviour & fallback

Because it's a real <select>, you get keyboard navigation, focus management, and a submittable form value for free. The chosen reaction posts under the control's name.

Where customizable select isn't supported, the <button> / <selectedcontent> children are ignored and the element renders as an ordinary <select> dropdown of the same emoji options — a fully working, accessible fallback with no JavaScript shim.

Accessibility
  • Always give the <select> an aria-label (or an associated <label>) — the reactions themselves are emoji, so the control needs its own name.
  • Keep a .visually-hidden text label inside every <option> so each reaction is announced by name in both the styled bar and the native fallback.
  • The trigger and options take the standard focus ring on :focus-visible; the open/close entrance is short-circuited under prefers-reduced-motion by the global motion guard.
Tuning
  • --reaction-size (default 1.35rem) sets the trigger glyph size; the picker scales its option emoji off the same value.
  • The bar opens centred above the trigger (position-area: block-start + justify-self: anchor-center) and flips below via position-try-fallbacks: flip-block when there isn't room.

Avatar

Circular avatar for user images or initials.

When to use: User photos or initials with size variants.

Classes: .avatar, .xs, .s, .l, .xl, .bordered

Reference Notes
Styling Details
  • Circular shape (border-radius: 50%)
  • Images use object-fit: cover
  • Initials have subtle background (--fg-1)
  • Text scales with avatar size

Boxes

Container styles for cards and content blocks.

When to use: Container surface styles and quick panel variants.

Classes: .box, .glow, .semi-gloss, .ghost, .invisible

Reference Notes
Styling Details
  • .box - Tint background, padding, border-radius, border
  • .box.glow - Applies var(--box) — a soft outer drop shadow plus two inset highlights for a subtle dimensional sheen
  • .box.semi-gloss - Gradient background, premium feel
  • .box.ghost - Transparent, border outline only
  • .box.invisible - No visual styling, just structure

Callouts

Informational boxes for tips, warnings, errors, and success messages.

When to use: Inline informational or status callout blocks.

Classes: .callout, .warning, .error, .success, .ghost, .fill, .callout.stack

Reference Notes
With Icon

The first direct <svg> child is pulled into the gutter and colored with --callout-accent:

Multiple Elements

Add .stack directly to the callout to align children to the start and stack them with consistent spacing:

A nested <div class="stack"> works too if you need finer control over which children stack.

CSS Variables
  • --callout-tint - Background color (applies to .fill only)
  • --callout-accent - Icon color

Card

A padded surface for grouped content. Reach for header, footer, or media only when you need a divided bar or edge-to-edge media.

When to use: Grouped content, linked previews, and pricing tiles.

Classes: .card, .card.linked, .card.featured

Reference Notes
Basic Card

The card itself is the padded surface — drop content directly inside. No wrapper, no body class.

Children stack vertically with a small built-in gap. Override it inline with --gap when you want more breathing room:

Add a direct <header> or <footer> only when you want a divided bar with a separator. They bleed edge-to-edge through the card's padding automatically.

With Media

Direct <img>, <picture>, or <figure> children bleed edge-to-edge too. At the top or bottom of the card they extend through that side's padding.

Notes
  • .card is the padded surface. Don't wrap content in an extra <div>.
  • Children stack with gap: var(--gap, var(--vs-s)). Override with style="--gap: var(--vs-m);".
  • <header>, <footer>, and <img>/<picture>/<figure> direct children bleed to the card edge — use them only when you actually want that treatment.
  • Skip headers and footers for simple cards. Most cards don't need them.

Bubble

Chat-friendly message container with configurable colors, width, and spacing.

When to use: Chat message presentation and conversation snippets.

Classes: .bubble, .bubble.thinking, .bubble.streaming, .chat-thread, .chat-thread.flowing, .chat-row, .chat-row.self, .chat-message, .chat-composer

Reference Notes

.bubble is a rounded chat container with configurable padding, max-width, and child flow spacing.

Chat Layout Helpers

Use these helpers to build complete chat threads:

  • .chat-thread - Vertical message stack with configurable spacing/padding
  • .chat-row - Left-aligned row
  • .chat-row.self - Right-aligned row
  • .chat-message - Width-constrained wrapper for each message
  • .chat-composer - Composer row where .input-group expands to fill space
In-Flight Variants

Two state modifiers for assistant turns:

  • .bubble.thinking — dashed border, italic, muted color. For reasoning / system thought.
  • .bubble.streaming — appends a blinking caret cursor. Pure CSS (@keyframes).
Flowing Thread (Bubble-less)

For long-form / editorial agents the conversation can read as one document rather than a stack of bubbles. Each turn becomes a row in a single readable column:

CSS Variables
  • --bubble-bg - Bubble background color
  • --bubble-border - Bubble border color
  • --bubble-max-inline - Max bubble width
  • --bubble-pad-block - Block-axis padding
  • --bubble-pad-inline - Inline-axis padding
  • --bubble-radius - Corner radius
  • --bubble-flow-space - Spacing between child elements

Icon Rail

Narrow vertical column of icon buttons with active state and optional status dot.

When to use: Workspace shells, agent switchers, tool palettes — any vertical nav sliver next to a wider sidebar.

Classes: .icon-rail, .icon-rail > .brand, .icon-rail > .status, .icon-rail > .spacer

Reference Notes

.icon-rail is a narrow vertical column of icon buttons. Drop in agent switchers, tool palettes, workspace shells. Pair with .layout-rail for a rail + sidebar + main app shell.

Anatomy
  • .icon-rail — vertical container (inline-size: --rail-size, default 4rem).
  • .brand — square top-of-rail mark on the primary color. Place a logo or sigil.
  • > a, > button — icon rows. Active row uses aria-current="page" or aria-pressed="true"; hover and focus pick up --fg-1 / --shadow-1.
  • > .status — optional 8px green dot in the bottom-trailing corner of a row, for "currently active" / "online" affordance.
  • > .spacerflex: 1 filler to push the next children to the bottom.
Composition

Sits left of a .layout-sidebar, sub-sidebar (.chat-list or similar), or inside .layout-rail:

CSS Variables
  • --rail-size — width of the rail (default 4rem)

Horizontal scrolling with CSS scroll-snap. No JavaScript required.

When to use: Horizontal scroll-snap for cards or media strips.

Classes: .carousel

Reference Notes
Styling Details
  • display: flex with horizontal overflow
  • scroll-snap-type: x mandatory for snapping
  • Children have scroll-snap-align: start
  • Thin scrollbar (scrollbar-width: thin)
  • Default gap of 1rem
How It Works
  • Drag, swipe, or scroll horizontally
  • Items snap to start position
  • Works with mouse, touch, trackpad, and keyboard
  • No JavaScript needed for basic functionality

Log Card

Compact card with mono label, status slot, and optional pre body — for tool calls, deploy logs, build steps.

When to use: AI tool calls, deploy / CI logs, build steps, activity feeds — any single-row transcript line that may expand to show payload.

Classes: .log-card, .log-card > header, .log-card > header > .label, .log-card > header > .status, .log-card > pre

Reference Notes

.log-card is a compact card with a mono label header, a status slot, and an optional <pre> body. Reads as a single row that may expand. General-purpose transcript line — not chat-specific.

Anatomy
  • > header — mono label row with an icon, a .label (monospace), and an optional .status pushed to the trailing end. Status is meant for short affordances like ✓ 412ms, … running, × failed.
  • > pre — optional payload. Wraps at the card edge; readable but contained.
  • Any other body content (paragraphs, summaries) also works — <pre> is just the most common.
Composition

Sits inside a .chat-message for AI tool calls, or anywhere a single-row-with-payload affordance is needed:

Reel

Vertical scrolling with CSS scroll-snap. Like carousel but vertical.

When to use: Vertical scroll-snap list or feed.

Classes: .reel

Reference Notes
CSS Variables
  • --reel-height - Container height (default: 80vh)
  • --gap - Gap between panels (default: 1rem)
Styling Details
  • display: flex with flex-direction: column
  • scroll-snap-type: y mandatory for snapping
  • Children have scroll-snap-align: start
  • Vertical overflow with thin scrollbar
How It Works
  • Scroll vertically to navigate panels
  • Panels snap to top
  • Works with mouse wheel, touch, and keyboard

Tables

Responsive table wrapper with clean data table styling.

When to use: Responsive table wrapper and default table styling.

Classes: .table, .table.zebra, .table.sticky

Reference Notes

Add .sticky to the wrapper to pin the header row while the body scrolls vertically. The wrapper becomes a vertical scroll context capped by --table-max-height (defaults to 70vh), and each <th> uses position: sticky so it stays visible on top of the scrolling cells:

  • Pure CSS, zero JavaScript — position: sticky does all the work.
  • The header <th> cells get an opaque var(--bg) so scrolling rows do not show through.
  • .sticky composes with .zebra on the same wrapper.
  • Override the scroll height with --table-max-height (e.g. --table-max-height: 24rem).

A future enhancement could add a scroll-shadow under the header that appears only while the body is scrolled, using scroll-state container queries (@container scroll-state(scrollable: top)); it is deferred until that feature has broad browser support.

Why the Wrapper?

The .table wrapper provides:

  • Horizontal scrolling on small screens
  • Border and border-radius on the container
  • Proper overflow handling
Styling Details
  • Tables are 100% width with collapsed borders
  • Headers have bottom border separator
  • Cells have consistent padding
  • Last row has no bottom border
  • Wrapper has overflow-x: auto for responsiveness
CSS Variables
  • --table-border - Custom border-radius for wrapper

Dialog

Native HTML `<dialog>` element with open/close animations.

When to use: Native modal flows and confirmations.

Classes: dialog, .close

Reference Notes

No JavaScript required when using HTML invokers.

How It Works
  • commandfor points to the dialog's id
  • command="show-modal" opens the dialog as a modal
  • command="close" closes the dialog
  • No JavaScript needed for basic open/close
Close Button

The .close class creates a circular red button. When it is a direct child of a <dialog>, an extra rule pins it to the top-right corner (slightly overlapping the dialog's top edge). Used outside a <dialog>, it's just the circular button — position it yourself.

Styling Details
  • max-width: 40ch - Character-based width for good proportions
  • Dark backdrop overlay
  • Smooth scale/opacity animation on open/close
  • Works in light and dark themes automatically

Tabs

Pure CSS tabs using `<details>` and `<summary>` with CSS Grid and Subgrid.

When to use: CSS-only tabbed content using details/summary.

Classes: .tabs, .tabs.boxed, .tabs.pill

Reference Notes

No JavaScript required.

How It Works
  • The name attribute on <details> ensures only one tab can be open at a time (native HTML behavior)
  • --n CSS variable positions each tab's summary in the correct grid column
  • --tab-count on the container sets the number of columns (default: 3)
Important Notes
  1. Unique names: Each tab group needs a unique name attribute
  2. Open one by default: Add open to the tab that should be visible initially
  3. Sequential --n values: Must match the visual order (1, 2, 3, etc.)
  4. Match --tab-count: If you have more than 3 tabs, set --tab-count to match
Styling Details
  • Uses CSS Grid with Subgrid for alignment
  • Smooth opacity transitions with @starting-style
  • Works in light and dark modes
  • Keyboard accessible (native details/summary behavior)
  • Active tab has underline indicator (default), card connection (boxed), or sliding thumb segment (pill)

Tooltip

Hover tooltips using CSS anchor positioning. No JavaScript required.

When to use: Popover-based tooltips with CSS anchor positioning.

Classes: .tip, .tooltip, .bottom, .left, .right

Reference Notes

Two patterns share the same visuals — pick by content shape.

Standalone .tip (Plain Text via aria-label)

Apply .tip directly to an icon button (or any focusable element) and put the label text on aria-label. The same string is the screen-reader accessible name and the visible tooltip — one source of truth, no duplication.

Position modifiers go on the same element:

This pattern is best for icon-only buttons where the tooltip text is the accessible name. The pseudo-element reads attr(aria-label), so it can only render plain text — no markup, links, or nested elements. For those, use the rich pattern below.

Don't combine .tip with visible text content. <button class="tip" aria-label="Save to draft">Save</button> would override the visible "Save" label with "Save to draft" for screen readers — that fails WCAG 2.5.3 (Label in Name). When the element already has a visible label and the tooltip is a supplemental description, use the rich pattern below with aria-describedby pointing at the tip.

Styling Details
  • Uses CSS anchor positioning (anchor-scope, position-anchor, position-area)
  • Background: var(--bg)
  • Border: var(--border-1)
  • Shadow: var(--shadow-3)
  • Max width: 30ch (wraps longer text)
  • Shows on :hover and :focus-visible / :focus-within
  • Smooth opacity transition (var(--d-fast) / var(--ease-smooth))
Picking a Pattern
Use casePattern
Icon-only button, tip text = accessible name<button class="tip" aria-label="…">
Element with visible label, tip is supplemental.tooltip wrapper + child .tip + aria-describedby
Tip body needs links, icons, or multi-line markup.tooltip wrapper + child .tip

List Navigation

Navigation list with clickable rows for settings pages, menus, and navigation indexes.

When to use: Grouped list-style navigation rows.

Classes: .list-nav

Reference Notes

Each item is a pill-shaped card with subtle shadow.

Use Cases
  • Settings pages
  • Mobile app menus
  • Feature indexes
  • Dashboard navigation
  • Account/profile menus
Item Structure

Items are direct <a> or <button> children of .list-nav. No additional classes needed.

Each item can contain:

  1. <svg> - Icon on the left (sized to 1.25em)
  2. Text - Title text
  3. <small> - Optional description (muted, smaller text)
Styling

Each item automatically gets:

  • Pill radius - var(--br-xxl) rounded corners
  • Subtle shadow - var(--shadow-2) for elevated card appearance
  • Background - Uses var(--bg) for proper theming
  • Gap - var(--pad-m) spacing between items
  • Dark mode - Items pick up a var(--border-1) border for definition
States
  • Default: Pill-shaped card with subtle shadow
  • Hover: Background highlight, icon brightens
  • Focus: Focus ring with inset offset
  • Active: Slightly darker background
  • Disabled: 65% opacity, no pointer events
Differences from Sidebar Nav
FeatureList NavSidebar Nav
Item styleIndividual cards with shadowFlat items in a list
DescriptionSupported via <small>Not supported
Use caseStandalone navigation rowsSticky sidebar menus
NestingNot supportedSupports details/summary
Visual weightHigher (cards with shadow)Lower (compact)
Accessibility
  • Use semantic <nav> container
  • Use <a> for navigation links, <button> for actions
  • Disabled buttons use disabled attribute
  • Links can use aria-disabled="true" when needed
  • Focus states are clearly visible

Toggle Switch

Accessible toggle/switch input using native checkbox.

When to use: Boolean settings with checkbox semantics.

Classes: .toggle, .compact

Reference Notes

<span id="forms">

CSS Variables
  • --toggle-color - Color when checked (default: var(--primary))
  • --toggle-width - Track width
  • --toggle-height - Track height
Accessibility

The toggle uses a native checkbox, so it:

  • Works with keyboard (Space to toggle)
  • Announces state to screen readers
  • Respects prefers-reduced-motion

Input Group

Input field with connected button.

When to use: Input plus attached action button patterns.

Classes: .input-group, .input-group > .affix, .input-group.stack-mobile

Reference Notes

Use for copy-to-clipboard, search with button, URL sharing, or any input that needs an action.

Static Affixes

Add an inert .affix child for a leading currency symbol or a trailing unit. The affix shares the input's fill, hairline border, and radius so the seam reads as one continuous control. It is non-interactive (pointer-events: none, user-select: none) — purely a visual adornment, with the real value living in the input.

  • Use a leading .affix as the first child for symbols like $ or .
  • Use a trailing .affix as the last child for units like kg, %, or USD.
  • You can combine both around a single input.
  • Pair with type="number" plus inputmode so mobile keyboards match the expected entry.
Stack on Mobile

Add .stack-mobile to break the group into a vertical stack below 640px. Each child reclaims a full border-radius so the input and button read as separate controls.

Styling Details
  • Input stretches to fill available space
  • Button stays sized to content
  • Connected with no gap, shared border-radius
  • Works with all button variants (.primary, .ghost, etc.)

Search input with icon positioned inside.

When to use: Search field with icon and compact action behavior.

Classes: .search

Reference Notes
Icon Source

Get icons from Phosphor Icons. The magnifying glass icon shown above is the "MagnifyingGlass" icon.

Styling Details
  • Icon is absolutely positioned, vertically centered
  • Input has left padding to accommodate the icon
  • Icon is muted (var(--fg-3)) so the user's typed text reads as the primary content

File Dropzone

Drag-and-drop file upload zone with click-to-upload fallback.

When to use: Drag-and-drop upload zones with native fallback.

Classes: .dropzone, .dragover

Reference Notes
Icon Source

Get icons from Phosphor Icons. The upload icon shown above is the "UploadSimple" icon.

Styling Details
  • Padding: var(--pad-xxxl), radius: var(--br-l) (hardcoded; theme by editing the source or wrapping)
  • Dashed border (2px dashed var(--fg-2))
  • Centered flex layout for icon and text
  • File input is hidden but covers entire area
  • Click anywhere to trigger file picker
  • .dragover state swaps border and icon colour to var(--accent)

Toolbar

Horizontal control bar — a wrapping flex row of buttons and controls with separator and spacer slots.

When to use: Editor control strips, card action rows, composer footers, and any cluster of related controls that should wrap gracefully when cramped.

Classes: .toolbar, .toolbar > .separator, .toolbar > .spacer

Reference Notes

.toolbar is a horizontal control bar: a wrapping flex row of buttons and controls. It is the public control-row pattern consumed by .composer and is equally at home wrapping a card's actions or an editor's formatting controls.

Anatomy
  • .toolbar — flex row with --vs-xs gap. flex-wrap: wrap is on by default, so controls reflow onto a new line when the container is narrow.
  • > .separator — a hairline vertical divider (--fg-2) for grouping related controls. Slot it as a <span class="separator"> between groups.
  • > .spacer — a flex: 1 push-to-end helper. Everything after it is pushed to the inline-end, e.g. to anchor a primary action on the trailing side.
Composition

The toolbar carries layout only — gap, wrap, and the two slots. Style its children with the existing button vocabulary (.button, .minimal, .ghost) and .icon-button for icon-only controls; icon-only controls must carry an aria-label. .composer consumes .toolbar directly, adding only its own inline padding as a context override.