Customize

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

Graffiti

github

UI Blocks

Composed multi-element patterns.

Larger composed patterns built from Graffiti primitives. Navigation, dialogs, forms, and app shells.

Accordion

Native HTML disclosure element with smooth animations using `<details>` and `<summary>`.

When to use: Expandable sections for FAQ and settings content.

Classes: details, .bordered, .right, .minimal

Reference Notes
Styling Details
  • Smooth open/close height animation: details::details-content transitions block-size from 0 to auto, unlocked by interpolate-size: allow-keywords at :root so the auto keyword is interpolable. The initial open of an [open] element animates in via @starting-style.
  • Custom arrow indicator that rotates on open
  • Proper focus-visible states
  • No JavaScript required
  • Respects prefers-reduced-motion: keyword interpolation is only enabled when motion is allowed, and the global reduced-motion guard short-circuits the transition duration, so the panel snaps open/closed instead of animating.
Exclusive Accordion (One Open at a Time)

Use the name attribute to group accordions:

Only one in the group can be open at a time.

Simple breadcrumb navigation with customizable separators.

When to use: Hierarchy path navigation inside app and docs views.

Classes: .breadcrumbs

Reference Notes
Custom Separator

Default separator is /. Change with --separator:

Other separator ideas: , , », |, ·

Current Page

Mark the current page with aria-current="page" on the <li>:

This removes the link styling and shows it as plain text.

Styling Details
  • Flexbox layout with wrapping
  • Separators via ::before pseudo-elements
  • Links are muted (--fg-5) and brighten on hover (--fg-7)
  • Proper focus-visible states
Accessibility
  • Use aria-label="Breadcrumb" on the <nav>
  • Use aria-current="page" on the current page <li>
  • Use semantic <nav>, <ul>, <li> structure

Pagination

Card footer style pagination with previous/next controls and current page state.

When to use: Paginated result navigation with previous and next controls.

Classes: .pagination

Reference Notes
Styling Details
  • .pagination handles layout only (flex, gap, border-top)
  • Buttons use existing .button.ghost styles
  • Page number buttons in ul get fixed 2rem square sizing
  • Current page uses primary border and stronger text
  • Border-top and padding give card footer feel
Accessibility
  • Use aria-label="Pagination" on the <nav>
  • Use aria-current="page" on the active page link
  • Use aria-disabled="true" for disabled controls

Native dropdown menu using HTML popover API and CSS anchor positioning.

When to use: Action menus using popover and anchor positioning.

Classes: .dropdown, .dropdown-menu, .dropdown-header, .end

Reference Notes

No JavaScript required for open/close. The trigger and menu must be children of the same .dropdown, and each menu still needs a document-unique id referenced by its trigger's popovertarget.

Anchor Setup (Automatic)

.dropdown supplies a locally scoped --dropdown anchor by default. The local scope lets several dropdowns reuse that internal name without a menu attaching to another instance. No inline custom property is required.

The legacy --anchor override remains available for integrations that already assign an explicit dashed-ident:

Normal dropdowns should use the automatic local anchor. If supplied, the override must start with --; local scoping still keeps it isolated to that dropdown.

How It Works
  • .dropdown scopes the default anchor name to its subtree
  • The direct [popovertarget] trigger declares the anchor used by its menu
  • popovertarget on the button points to the menu's id
  • popover attribute enables native popover behavior
  • .dropdown-menu provides styling and positioning
  • Clicking outside automatically closes the menu
Browser Behavior

Opening and dismissal require the HTML Popover API. Placement requires CSS anchor positioning, including anchor-scope. In supporting browsers, the default aligns the menu's inline-start edge to the trigger and .end aligns their inline-end edges. In browsers without all of those placement features, the native popover still opens and its actions remain usable, but it may render at an unanchored fallback position. Graffiti does not add a JavaScript positioning polyfill.

Vertical navigation for app sidebars with collapsible sections.

When to use: Sectioned app navigation with collapsible groups.

Classes: .sidebar-nav, .sidebar-nav.compact, .sidebar-nav.ghost, .sidebar-nav.minimal, .sidebar-nav.strong-active, .sidebar-nav.primary, .sidebar-nav.success, .sidebar-nav.warning, .sidebar-nav.error, .sidebar-nav.dark, .sidebar-nav.light, .sidebar-nav.contrast, .sub

Reference Notes

Uses native <details>/<summary> for expand/collapse.

With Icons

Icons are automatically sized to 20px (customizable via --sidebar-nav-icon-size).

CSS Variables
  • --sidebar-nav-icon-size - Icon size (default: 20px)
  • --sidebar-nav-indent - Indentation for nested items (default: 1.5rem)
Compact

Use .compact when a sidebar needs denser rows:

Compact mode keeps focus and hover behavior, while reducing row padding and icon size.

Ghost

.ghost swaps the row gradient for a transparent fill with a thin border that strengthens on hover and active. Use it when the sidebar should read as part of a surrounding panel rather than a filled control.

Minimal

.minimal drops the row chrome entirely — no border, no background, no shadow. Hierarchy comes from text color: inactive rows are --fg-6 (inherited from the base row rule), the active row sits at --fg-8, and hover lifts to full --fg.

Strong active

Compose .strong-active with .minimal or .ghost when the default active brightness isn't pulling enough hierarchy. The active row paints in full --fg and the hover state matches, so the selected row is clearly the brightest thing in the list.

Color variants

Add .primary, .success, .warning, or .error to re-skin the active row's gradient and text in a theme color. These are shorthands for --sn-color: var(--primary) etc.

You can also set --sn-color inline to any color in scope:

Surface variants

Use .dark, .light, or .contrast when the sidebar should sit on a fixed surface regardless of theme. .dark always renders on a near-black surface, .light on white, .contrast flips against the active color scheme.

Nested tag density

.tag placed inside a sidebar row gets tighter padding so badge-style accents (counts, keyboard hints) don't inflate the row height.

App Shell Pattern (Dashboard / Settings)

Combine with .layout-sidebar.fill for the canonical shell:

Notes:

  • .layout-sidebar.fill is app-shell-oriented by default (--layout-gap: 0, height: 100dvh)
  • Fill children auto-scroll when they are not .app-shell
  • If a fill child is .app-shell, overflow is not forced there, avoiding double-scroll
Fixed Sidebar Pattern

Use this when navigation should remain visible while main content scrolls.

Drawer

Popover-driven slide-in panel that anchors to any edge — left, right, top, or bottom.

When to use: Side navigation, mobile bottom sheets, filter panels, and any popover surface that should fly in from a screen edge.

Classes: .drawer, .left, .right, .end, .top, .bottom

Reference Notes

.drawer is a single primitive with four edge anchors. It rides on the native [popover] attribute, so opening and closing requires zero JavaScript — use popovertarget on a button.

Basic Example

By default, the drawer anchors to the inline start (the left edge in LTR).

Direction Modifiers

Add .right, .top, or .bottom to re-anchor the drawer to that edge. The slide-in animation, border placement, and sizing follow the edge.

.end is an alias for .right (matches the historical sidebar convention).

Direction Reference
ClassAnchors toSlide directionSizing
(none)LeftFrom left--drawer-inline-size wide, capped at 85vw
.rightRightFrom right--drawer-inline-size wide, capped at 85vw
.topTopFrom topFull width, up to 85dvh
.bottomBottomFrom bottomFull width, up to 85dvh

Left/right drawers are hard-capped at max-inline-size: 85vw — pushing --drawer-inline-size past that clamps to the cap.

Bottom Sheet Pattern

.drawer.bottom is the modern, accessible bottom sheet — full-width slide-up with a scrim and proper focus management courtesy of the popover API.

Notes
  • The drawer uses [popover], so it gets a backdrop, focus trapping, and ESC-to-close for free.
  • All directions animate via translate + @starting-style. No JavaScript required.
  • Top and bottom drawers cap at 85dvh so they never block the entire viewport.

Mobile Patterns

iOS and Android-friendly CSS patterns for PWAs and native-like web apps.

When to use: App shell, bottom nav, bottom sheet, safe areas.

Classes: .app-shell, .bottom-nav, .bottom-nav.blur, .bottom-sheet, .safe-top, .safe-bottom, .safe-x, .hide-scrollbar, .momentum-scroll

Reference Notes
Safe Area Variables

Graffiti provides CSS variables for iOS safe areas (notch, home indicator, status bar):

These inherited tokens use env() to return the user agent's safe area, or 0px on an ordinary rectangular viewport. For embedded app shells, override them on the shell's containing element; descendants such as .app-shell, .bottom-nav, and .bottom-sheet inherit the host-provided insets:

An override replaces the corresponding user-agent value. To add host spacing while preserving a device inset, include env() in the override, for example --safe-bottom: calc(env(safe-area-inset-bottom, 0px) + 1rem).

App Shell

Grid-based container that avoids the iOS URL bar 100vh bug:

Features:

  • Uses 100dvh (dynamic viewport height) to avoid iOS URL bar issues
  • Sticky header with blur backdrop
  • Main content scrolls independently
  • Respects safe areas automatically
  • Better nested scroll resilience (min-block-size: 0 on shell and direct regions)

Behavior:

  • .layout-sidebar.fill now acts as an app-shell frame by default (--layout-gap: 0, height: 100dvh)
  • Non-.app-shell first/second children auto-scroll on larger layouts
  • If a fill child is .app-shell, overflow is not forced on that child; its main handles scrolling
Bottom Navigation

Fixed tab bar for mobile apps:

Features:

  • Floating pill: position: fixed, inset from the bottom + sides, fully rounded (var(--br-xxl)) with var(--shadow-3)
  • Respects --safe-bottom for the home indicator
  • Active state via aria-current="page" or .active class (active color = var(--primary))
  • 24px icons + --fl: -1 label
  • Automatic light/dark theming

Add .blur for a translucent glass effect (70% bg + 20px backdrop blur):

Bottom Sheet

Drawer that slides up from bottom:

Features:

  • Rounded top corners
  • Automatic drag handle visual at top
  • Respects --safe-bottom
  • Max height 80dvh to allow dismissal

For interactive open/close, wrap in <dialog> or use with popover API.

Important: Viewport Meta Tag

For safe areas to work, include viewport-fit=cover:

Without this, safe area insets may not be reported correctly on iOS.

Full-width site header with navigation.

When to use: Page-level site/app top navigation bar.

Classes: .header, .header.border, .header.sticky, .header.readable

Reference Notes
Styling Details
  • Spans its parent — no inherent width constraint (add .readable to cap at 1400px)
  • Flexbox with space-between, --gap: 1rem between children
  • Nav <ul> styled as horizontal flex list
  • All direct children have margin reset
  • .sticky uses --z-overlay and a solid var(--bg) background

Site footer with navigation columns, copyright, and legal links.

When to use: Site footer with grouped navigation and legal links.

Classes: .footer

Reference Notes

Uses container queries for responsive behavior.

Required Classes
  • .footer - Container with container-type: inline-size, removes link underlines (shows on hover)
  • .grid.auto - Responsive auto-fit grid for nav columns
Key Features
  1. Container queries - .footer sets container-type: inline-size so child layouts respond to footer width, not viewport
  2. No underlines - Links have no text-decoration by default, underline appears on hover
  3. Responsive grid - .grid.auto uses auto-fit with --grid-min variable for responsive columns
  4. Layout stacking - .layout-sidebar and .split automatically stack in narrow containers
CSS Variables
  • --grid-min - Minimum column width for .grid.auto (default: 150px, recommended: 120px for footer)
Responsive Behavior

The footer uses container queries, not media queries:

  • When footer container is narrow, .layout-sidebar stacks vertically
  • .grid.auto columns wrap based on --grid-min
  • .split stacks when container is < 500px
Utility Classes Used
  • .box - Adds padding and border
  • .stack - Vertical spacing between children
  • .cluster - Horizontal wrapping layout
  • .split - Space-between horizontal layout
  • .layout-sidebar - Two-column layout (stacks in narrow containers)
  • .grid.auto - Auto-fit responsive grid

Swipe

Horizontal swipe-to-reveal component using CSS scroll-snap.

When to use: Swipe-to-reveal row actions for touch interactions.

Classes: .swipe, .stop

Reference Notes

Reveal action buttons by swiping left or right.

Structure
  • First child = left action
  • Second child = main visible content (sized to the swipe container's inline-size)
  • Third child = right action

Action children should be <button> elements — the 200px action width is applied via > button. Non-button actions (<a>, <div>) will not get that width.

Styling Details
  • CSS scroll-snap for smooth snapping
  • Three-column layout
  • Hidden scrollbar
  • Container query for center content width

User Menu

User account dropdown combining avatar trigger with dropdown menu.

When to use: Avatar trigger plus account actions dropdown.

Classes: .avatar, .dropdown

Reference Notes

Works with both image and initials avatars. Set a unique --anchor on each dropdown so the menu attaches to its avatar trigger.

Key Classes
  • .dropdown.end - Aligns menu to right edge of avatar
  • .avatar - Circular avatar styling on the button
  • .avatar.bordered - Adds a subtle border around the avatar
  • .dropdown-menu - Menu styling
  • .dropdown-header - User name display in menu
Anchor Setup

.dropdown requires a --anchor inline style (dashed-ident, unique per instance) so the menu can position-anchor to its trigger. See the Dropdown topic for details.

Confirm Dialog

A confirmation dialog pattern using native `<dialog>` with title, message, and action buttons.

When to use: Destructive or important confirmations using native dialog.

Classes: dialog, .close

Reference Notes
Key Classes Used
  • .close - Circular close button (auto-positions top-right only when a direct child of <dialog>)
  • .stack - Vertical layout for dialog content
  • .cluster - Horizontal layout for action buttons
  • .h4 - Heading style without using an actual heading element
  • .primary - Primary action button
How It Works
  • Uses HTML invokers (commandfor/command) - no JavaScript for open/close
  • .stack provides consistent vertical spacing between title, message, and buttons
  • .cluster with justify-content: flex-end aligns buttons to the right
  • Both Cancel and Confirm close the dialog; add your own JS for the confirm action

Popover

Generic interactive popover using the native Popover API and CSS anchor positioning.

When to use: Rich revealed content containing links, controls, or forms that would be invalid inside a tooltip.

Classes: .popover-anchor, .popover, .end, .top, .left, .right

Reference Notes

.popover-anchor creates a local anchor scope. Its direct popovertarget trigger names that anchor, and a sibling .popover[popover] attaches below it without per-instance positioning styles.

The default placement is block-end aligned to inline-start. Add .end, .top, .left, or .right to the popover. Keep popover="auto" for light dismissal; use manual only when Decks explicitly owns dismissal. A popover may contain interactive content. A tooltip may not.

Timeline

Activity feeds, step indicators, and progress tracking with status variants and glow effects.

When to use: Progress steps and chronological activity flows.

Classes: .timeline, .steps, .horizontal, .active, .completed, .success, .warning, .error, .info

Reference Notes
.timeline and .steps

.timeline and .steps are the same primitive — pick the name that matches intent. Use .timeline for chronological event lists (activity feeds, changelogs, history). Use .steps for ordered process flows (onboarding, multi-step forms, checkout).

State Classes
ClassUseVisual
.activeCurrent step in progressBold filled marker with ring
.completedFinished stepGreen filled marker with glow
.successSuccess statusGreen-tinted background with glow
.warningWarning statusYellow-tinted background with glow
.errorError statusRed-tinted background with glow
.infoInfo statusBlue-tinted background with glow
Marker Content

The .marker element can contain:

  • Text/numbers: <span class="marker">1</span>
  • SVG icons: <span class="marker"><svg>...</svg></span>
  • Emoji: <span class="marker">✓</span>

SVG icons are automatically sized to 1.125rem (18px).

Visual Features
  • Shadows: Multi-layered box-shadow for depth
  • Inner highlight: Subtle top highlight for 3D effect
  • Borders: Colored borders matching status variants
  • Glow rings: 0 0 0 3px spread shadow for colored halo effect
  • Gradients: Completed markers have gradient fill (lighter top, darker bottom)
Use Cases
  • Activity feeds: PR activity, commit history, user actions
  • Changelogs: Version history with status indicators
  • Steppers: Multi-step forms, checkout flow, onboarding
  • Build logs: CI/CD pipeline status
  • Notifications: Action history with status

Toast

Shallow notification viewport, item, toast, optional progress, tone, and placement contracts.

When to use: Brief asynchronous feedback that should not interrupt the current task.

Classes: .toast-viewport, .toast-item, .toast, .toast-progress

Reference Notes

Decks owns the queue, live-region urgency, timeouts, pause/resume, insertion, and removal. The canonical public authoring surface is .toast-viewport, .toast-item, .toast, and optional .toast-progress. New toast markup stays shallow and class-light.

A persistent toast has no progress indicator and remains visible until the consumer or dismiss control hides it:

The modern visual contract is a neutral, configurable surface with elevation. It has no default border or accent marker. Tone modifiers are .info, .success, .warning, and .error; each sets --toast-tone, which colors optional tracked progress by default without recoloring the surface. Use role="status" for info/success. Reserve role="alert" for urgent warning/error.

A timed semantic toast includes .toast-progress and sets --toast-duration to the same duration as its real auto-dismiss timer:

The progress indicator is decorative and tracks --toast-duration; it does not provide the timer. Add data-paused="true" to .toast-progress whenever the matching dismissal timer is paused. Under reduced motion, the countdown stops and toast visibility and movement transitions become effectively immediate. Omit .toast-progress for persistent or manually dismissed notifications.

Adding popover="manual" gives the viewport native top-layer visibility. Closed popover viewports stay hidden until opened. Placement modifiers are .top-start, .top-center, .top-end, .bottom-start, .bottom-center, and .bottom-end; they use logical insets and safe-area tokens. Omitting popover remains valid for application-controlled stacks.

Consuming component inputs map to Graffiti output rather than requiring a particular framework implementation:

Consumer concernGraffiti output
PlacementA viewport modifier, or data-inline="start|center|end" with data-block="start|end" on .toast-viewport
Tone.info, .success, .warning, or .error on .toast
ProgressPresence or absence of a direct .toast-progress child
Duration--toast-duration on .toast, synchronized with the consumer's dismissal timeout
DismissibleA direct native button[popovertargetaction="hide"] targeting the viewport's id; omit it when dismissal is unavailable
Surface/configPublic custom properties applied to the viewport or toast

Public controls are grouped by purpose:

  • Viewport geometry: --toast-width, --toast-offset.
  • Surface and spacing: --toast-bg, --toast-border, --toast-radius, --toast-shadow, --toast-padding-block, --toast-padding-inline, --toast-gap.
  • Typography: --toast-fg, --toast-muted, --toast-font-size, --toast-line-height, --toast-title-weight.
  • Tone and progress: --toast-tone, --toast-progress-color, --toast-progress-track, --toast-progress-size, --toast-duration.

An optional direct action composes the existing button system rather than introducing toast-owned styling. For example, add <button class="mini minimal" type="button">Undo</button>, or choose another documented button variant when the action needs different emphasis. Do not wrap or stretch the action to the toast width.

.toast-header, .toast-body, .toast-actions, and .toast-dismiss remain compatibility selectors for existing consumers, but new markup does not need them.

Login Form

A complete login form using Graffiti's base form styles.

When to use: Email/password auth form with native control styling.

Classes: form, .stack

Reference Notes

No custom CSS needed.

Key Classes Used
  • .stack - Vertical layout with consistent spacing
  • .split - Flexbox with space-between (checkbox left, link right)
  • .primary - Primary action button style
Notes
  • Form inputs are styled automatically (no classes needed on inputs)
  • Labels automatically get proper spacing
  • Use .error, .success, .warning classes on inputs for validation borders (or rely on :user-invalid / [aria-invalid="true"])
  • A <small> placed directly after an input is auto-styled as a caption — colour it inline (style="color: var(--red-6)") or use a .callout.error for prominent messages

Composer

Multi-line input with an inline toolbar — model, tool, attach controls alongside the text.

When to use: AI chat surfaces, comment and post composers, email drafts — anywhere a textarea has adjacent affordances. Use plain `.chat-composer` when only a single input + send button is needed.

Classes: .composer, .composer > textarea, .toolbar, .toolbar > .spacer

Reference Notes

.composer is a multi-line input with an inline toolbar. It supersedes .chat-composer when you need model, tool, or attach controls next to the text input. Also useful for comment and post surfaces — anywhere a textarea has adjacent affordances.

Anatomy
  • .composer — column container. Border, --br-l radius, --shadow-2 lift.
  • :focus-within — border switches to --primary, adds a brand-tinted ring. Pure CSS, no JS.
  • > textarea, > input[type="text"] — borderless inputs that inherit the composer's surface. resize: none; size with rows.
  • .toolbar — the public .toolbar control row, consumed directly. Children are typically icon buttons, chips, or a .spacer to push the submit to the trailing end; the composer adds only its own inline padding.
  • .toolbar > .spacerflex: 1 push-to-end helper.
Mobile Behavior

Inside .layout-rail, the toolbar wraps automatically at narrow container widths (<768px). Use the existing .chip density rather than introducing new mobile-specific classes.

Forms

All form inputs are styled automatically. No classes needed on inputs.

When to use: Field rows and form actions.

Classes: .row, .form-option-row, .form-actions

Reference Notes
Option Row Labels

Use .form-option-row on checkbox/radio labels to align controls and text with consistent spacing.

Validation States

Add .error, .success, or .warning to inputs. These classes set the input's border color; :user-invalid and [aria-invalid="true"] produce the same error styling automatically.

A <small> placed directly after an input is auto-styled as a caption (block display, spacing). Style its color inline (style="color: var(--red-6)") or use a .callout.error if you need a prominent validation message.

Field Rows

Use .row as a field wrapper to group label + control + help text.

Notes:

  • Outside forms, .row keeps its spacing utility behavior (margin-block).
  • Inside forms and fieldsets, .row becomes a compact field-group wrapper.
Form Actions

Use .form-actions for submit/cancel rows.

Behavior:

  • End-aligned action row on larger containers
  • Wrap-friendly spacing for longer labels
  • Stacks action controls full-width in narrow containers

Workbench Panel

Right-aligned pane with a tabbed body — artifact viewer, properties inspector, code preview.

When to use: IDE-shaped surfaces — agent artifacts, file previews, properties inspectors, anything that wants a "right rail" alongside the main reading column.

Classes: .workbench-panel, .workbench-panel > header, .workbench-panel > header > .tabs, .workbench-panel > .body

Reference Notes

.workbench-panel is a right-aligned content pane with a tabbed header and a scrolling body. Pairs with .layout-rail.with-workbench to build IDE-shaped surfaces.

Anatomy
  • > header — title row with a .tabs group pushed to the trailing end. Tab activation uses aria-pressed="true" (no aria-selected — there's no ARIA tab semantics implied; treat each as an independent toggle that the consumer wires up).
  • > .body — the scrolling content area (flex: 1; overflow: auto).
Layout

Pair with .layout-rail.with-workbench for a 4-column shell (rail / list / main / workbench). On mobile (<768px container width), the workbench collapses with the other rail children.

Kanban Board

Board, lane, task card, pointer drag, drop target, selected, and keyboard-drag visual states.

When to use: Moving ordered work items among named workflow stages.

Classes: .kanban-board, .kanban-column, .kanban-column-header, .kanban-card, .kanban-dropzone

Reference Notes

The board scrolls horizontally and keeps columns snap-aligned on narrow containers. A column contains .kanban-column-header, a card list, and one or more .kanban-dropzone destinations. Task cards compose .card kanban-card.

Decks toggles dragging / data-dragging, keyboard-dragging / data-keyboard-dragging, row selection, drag-over, and active/invalid dropzone states. Keyboard drag mode must provide grab/cancel/move commands, preserve focus, and announce every successful or rejected move through a polite live region. Pointer drag is an enhancement, never the only path.