---
title: Public CSS API
description: Every load-bearing wk-* class WireKit ships in dist/wirekit.css — emitter, contract, customization pattern, and stability tier
visibility: guest
---

# Public CSS API

WireKit's component Blade templates emit a curated set of `wk-*` CSS class names alongside the Tailwind utility classes that drive most of the styling. These `wk-*` classes are **part of the public API surface** — developers depend on them to compose custom CSS, defend against typography-wrapper carve-outs, drive animation cascades, or layer their own design-system on top. This page is the canonical catalog of every public `wk-*` class, the component that emits it, the contract it satisfies, and how to customize it.

## What's public, what's internal

**Public (this page).** Every class name starting with `wk-` that appears in shipped `dist/wirekit.css`. These are deliberate emissions. Anti-drift enforcement guarantees every class in `dist/wirekit.css` has a row in the table below, AND every row references a class that actually ships.

**Internal (NOT in this catalog).** Tailwind utility classes (`flex`, `grid`, `px-[var(...)]` etc.) emitted into Blade templates via `WireKit::resolveClasses()`. Those are styling implementation details — they may change between minor versions without notice. Use the `wk-*` classes below as your stable contract.

**Tokens (separate surface).** CSS custom properties (`--color-wk-*`, `--font-wk-*`, `--space-wk-*`, etc.) are the **themeable value surface** — developers override values via `:root {}` declarations. The catalog below covers class names; the token surface is documented in [Theming](../theming.md) and [Design Token Reference](../theming.md#design-token-reference).

## Stability tiers

Each row carries one of three stability markers:

| Tier | Meaning |
|---|---|
| **Stable** | Covered by anti-drift tests. Won't change shape without a `### Changed` entry in `CHANGELOG.md` AND a major-version bump for behavior breaks. Build developer CSS against these freely. |
| **Provisional** | Emitted today, contract may evolve in a future minor release. CHANGELOG entries flag the change; developer prep window typically one minor version. |
| **Internal-with-exception** | Emitted to compiled CSS but developers should not depend on. Examples: animation-state classes (`wk-animate-fade-in` etc.) — they're visible in the DOM but their lifecycle is controlled by `<x-wirekit::reveal>` and developers should target the surrounding wrapper instead. |

## Class catalog

### Layout / chrome markers

| Class | Emitter | Customization contract | Tier | Docs |
|---|---|---|---|---|
| `wk-announcement-banner` | `<x-wirekit::announcement-banner>` | Page-edge announcement-bar marker. Reads `data-position` (top/bottom) and `data-intent`. Its inline padding IS the content-edge spine (`--padding-wk-x-lg`), so scope any override here rather than re-padding the bar and breaking the spine alignment. | Stable | [announcement-banner](../components/announcement-banner.md) |
| `wk-app-shell-aside` | `<x-wirekit::app-shell>` sidebar column | The in-flow sidebar `<aside>` column (`lg`). On `lg` the dist rule sizes it to `var(--wk-sidebar-w, 16rem)` and shrinks it to the 3.5rem icon rail when the nested `<x-wirekit::sidebar collapsible>` is `data-collapsed`, so the main content reflows; `w-64` stays the mobile overlay width. | Stable | [app-shell](../components/app-shell.md) |
| `wk-brand` | `<x-wirekit::brand>` | Reserved for developer prose-wrapper `[class*="wk-"]` carve-out — prevents `max-width: 75ch` typography clamps from breaking the brand's intrinsic width. | Stable | [brand](../components/brand.md) |
| `wk-brand-bar` (via `[class*="wk-"]`) | `<x-wirekit::brand-bar>` | Same carve-out role on the page-chrome header. | Stable | [brand-bar](../components/brand-bar.md) |
| `wk-carousel-viewport` | `<x-wirekit::carousel>` | The scroll region. Hides the scrollbar (`scrollbar-width: none` + the WebKit pseudo-element) because the dots and arrows already say where you are; the region stays keyboard-scrollable regardless. `scroll-behavior` is deliberately NOT set here — it is passed per call from JS, because the choice depends on `prefers-reduced-motion` and a CSS-only rule could not stop the auto-advance timer, only make unwanted motion smoother. | Stable | [carousel](../components/carousel.md) |
| `wk-carousel-slide` | `<x-wirekit::carousel.slide>` | Snap target. Its `flex-basis` comes from the parent's `per-view` via `[data-wk-carousel-per-view]` rules, so a slide never needs to know how many siblings share the view. Every step holds a 4/5 basis on phones — three-up on 375px is three unreadable slivers, and the leftover fifth is what tells the reader there IS a next slide. | Stable | [carousel](../components/carousel.md) |
| `wk-bottom-nav` | `<x-wirekit::bottom-nav>` | Fixed tab-bar root. Pads itself by `env(safe-area-inset-bottom)`: the bottom of the viewport is not the bottom of the usable screen on a phone with a home indicator, and without it the labels sit under the indicator while the swipe-up gesture eats the taps. Resolves to 0 on devices with no inset. | Stable | [bottom-nav](../components/bottom-nav.md) |
| `wk-bottom-nav-item` | `<x-wirekit::bottom-nav.item>` | Tab. Carries the 52px floor (WCAG 2.5.5 — a nav item is the primary control here and a 32px tab is a tap people miss) and, when current, a marker line via `::before` so the state is not carried by color alone (WCAG 1.4.1). | Stable | [bottom-nav](../components/bottom-nav.md) |
| `wk-bottom-nav-offset` | developer-applied | Reserves room for the fixed bar on whatever scrolls, so the last item on a page does not hide behind it. Opt-in: the component cannot know which element scrolls in your layout, and padding the wrong box is worse than padding none. Clears itself at `md` with the bar; add `wk-bottom-nav-offset-always` if you kept the bar on desktop. | Stable | [bottom-nav](../components/bottom-nav.md) |
| `wk-fab` | `<x-wirekit::fab>` | Floating-trigger root. Clears the home indicator via `env(safe-area-inset-bottom)`. | Stable | [fab](../components/fab.md) |
| `wk-fab-block-start` | `<x-wirekit::fab.button placement="block-start">` | Moves the button to the top edge: releases the bottom offset and sets a top one that clears the status bar and notch via `env(safe-area-inset-top)`. | Stable | [fab](../components/fab.md) |
| `wk-fab-above-nav` | developer-applied | Lifts the FAB above a bottom nav. Opt-in: the component cannot see whether a nav exists elsewhere in the document, and guessing would lift every FAB off the corner. Clears at `md` with the nav; `wk-fab-above-nav-always` keeps the lift. | Stable | [fab](../components/fab.md) |
| `wk-fab-actions` / `wk-fab-action` | `<x-wirekit::fab>` / `<x-wirekit::fab.action>` | The menu panel and its items. Actions are 44px — the touch minimum, and the smallest targets on the screen. | Stable | [fab](../components/fab.md) |
| `wk-fab-action-labeled` / `wk-fab-action-label` | `<x-wirekit::fab.action>` | The action and the name shown beside it. An icon-only action carries its name for assistive tech; the label makes it visible on hover and on keyboard focus, so a sighted pointer user is not left guessing. Positioned so revealing it never shifts the buttons, and it cannot intercept a click. | Stable | [fab](../components/fab.md) |
| `wk-fab-icon` | `<x-wirekit::fab>` | The trigger's plus/close crossfade. Both icons stay stacked in one grid cell so neither moves; `data-shown` is written by Alpine, and the inactive one is hidden from assistive tech so the trigger does not announce both states. Rotation is dropped under `prefers-reduced-motion`. | Stable | [fab](../components/fab.md) |
| `wk-calendar` | `<x-wirekit::calendar>` | Calendar-root marker. Below the `sm` breakpoint (`@media (max-width: 639.98px)`) the calendar flips to `display: block; width: 100%` so the day grid fills its container instead of sitting at its fixed ~312px content width; from `sm` up it keeps the compact inline-block grid. | Stable | [calendar](../components/calendar.md) |
| `wk-card-body` | `<x-wirekit::card.body>` | Marker the table-aware padding rule keys off (`.wk-card-body[data-wk-padded="false"] > table td`). Carries a `data-wk-padded` attribute reflecting the `padded` prop. | Stable | [card](../components/card.md) |
| `wk-button` | `<x-wirekit::button>` | Button marker. On touch devices (`@media (pointer: coarse)`) pins the control to a 44px minimum height — the WCAG 2.5.5 target — while the desktop keeps the designed size ladder. Carries no styling of its own. | Stable | [button](../components/button.md) |
| `wk-field` | select / input / textarea + composite form controls (combobox, multi-select, command-palette, number/password/otp/tags input, date/time picker, color-picker hex, filter-builder + data-table search, calendar header) | Text-entry-control marker. On touch devices (`@media (pointer: coarse)`) pins the control to `font-size: 16px` so mobile Safari never zooms-on-focus (it zooms any focused field below 16px). Desktop keeps the design-token sizing. | Stable | [input](../components/input.md) |
| `wk-cta` | `<x-wirekit::cta>` | Same carve-out role on the marketing CTA banner. | Stable | [cta](../components/cta.md) |
| `wk-data-list-item` | `<x-wirekit::data-list.item>` | Row-separator marker — the inter-row border lives in `.wk-data-list-item:not(:last-child)` so the last row omits it. | Stable | [data-list](../components/data-list.md) |
| `wk-day-blocked` | `<x-wirekit::event-calendar>` | Blocked / unavailable-day marker — renders the diagonal hatch (`repeating-linear-gradient` at −45° in `--color-wk-text-subtle`) on day cells flagged blocked, paired with a muted background. | Stable | [event-calendar](../components/event-calendar.md) |
| `wk-range-thumb` | `<x-wirekit::range-slider>` | Slider thumb marker owning the thumb size (20px, 28px on coarse pointers) + `touch-action: none`. | Stable | [range-slider](../components/range-slider.md) |
| `wk-swap` | `<x-wirekit::swap>` | Two-state icon root. Both children stay in the DOM — you cannot crossfade between an element and nothing — stacked in one grid cell so neither moves when the other appears. Visibility is driven from `data-active` rather than a class per state. The hidden child is `aria-hidden`, so a screen reader announces one state rather than both. | Stable | [swap](../components/swap.md) |
| `wk-swap-fade` / `wk-swap-rotate` / `wk-swap-flip` | `<x-wirekit::swap effect="…">` | How the two states trade places: crossfade, half-turn, or Y-axis flip. Under `prefers-reduced-motion` the state still changes but the transform is dropped too, not only the transition — a half-turn that snaps is still a half-turn nobody asked for. | Stable | [swap](../components/swap.md) |
| `wk-theme-switch-track` / `wk-theme-switch-knob` | `<x-wirekit::theme-controller variant="switch">` | The knob's travel, keyed off the real checkbox's `:checked`. `peer-checked:` cannot reach it — that variant only reaches siblings of the peer, and the knob is a grandchild of the track. | Stable | [theme-controller](../components/theme-controller.md) |
| `wk-table` | `<x-wirekit::table>` | Table-root marker — auto-pads raw `<td>`/`<th>` cells (those without a `data-wk-table-*` sub-component marker). | Stable | [table](../components/table.md) |
| `wk-footer` | `<x-wirekit::footer>` | Same carve-out role on the page footer. | Stable | [footer](../components/footer.md) |
| `wk-header` | `<x-wirekit::header>` | Same carve-out role on the page header. | Stable | [header](../components/header.md) |
| `wk-hero` | `<x-wirekit::hero>` | Same carve-out role on the marketing hero. Outer-spine participant, inner content NOT spine-aware — see [Content-Edge Spine](spine-contract.md). | Stable | [hero](../components/hero.md) |
| `wk-list` | `<x-wirekit::list>` | Reserved carve-out marker — prevents prose `padding-left: 1.75rem` rules from shifting WireKit list items off the content edge. | Stable | [list](../components/list.md) |
| `wk-list-spacing-sm` / `wk-list-spacing-md` / `wk-list-spacing-none` | `<x-wirekit::list spacing="…">` | Spacing-tier modifier marker emitted alongside `wk-list`. Lets developer CSS scope to a specific spacing tier (e.g. `.wk-list.wk-list-spacing-md > li { … }`). | Stable | [list](../components/list.md) |
| `wk-logo-cloud` | `<x-wirekit::logo-cloud>` | Logo-wall marker. The dist rule gives each `<li>` a `min-height: 3rem` flex center and caps logos at `max-height: 2.5rem` with `width: auto`. The cells own the geometry on purpose: logos are lazy-loaded, so an unloaded `<img>` measures 0×0 and the rows would collapse and then snap open as each logo arrives. | Stable | [logo-cloud](../components/logo-cloud.md) |
| `wk-logo-cloud-muted` | `<x-wirekit::logo-cloud>` (default) | Adds the muted logo treatment — desaturated at reduced opacity, restored on `li:hover` **and** `li:focus-within` so the reveal is not a mouse-only affordance. Decorative only: every logo keeps its `alt`. The filter transition is dropped under `prefers-reduced-motion`. Absent when `:muted="false"`. | Stable | [logo-cloud](../components/logo-cloud.md) |
| `wk-main` | `<x-wirekit::main>` | Primary-content carve-out marker on `<main>`. | Stable | [main](../components/main.md) |
| `wk-mockup` | `<x-wirekit::mockup>` | Frame-chrome root (browser / window / code / phone / tablet). Every surface, border, radius and shadow is a design token, so the frame follows the theme; the chrome parts hang off `data-wk-mockup-*` attributes rather than more classes. Reads `data-variant`. All chrome is decorative (`aria-hidden`) — style it freely, it carries no meaning. | Stable | [mockup](../components/mockup.md) |
| `wk-map-pin` | `<x-wirekit::map>` (Leaflet provider) | Intent-colored marker-pin marker on the inline-SVG `divIcon` the map glue generates per marker, so the pin honors the marker's `intent` token (Leaflet's default PNG can't be themed). Resets Leaflet's default white div-icon box (transparent background, no border). Leaflet-only — MapLibre colors its own marker. | Stable | [map](../components/map.md) |
| `wk-navbar-mobile` | `<x-wirekit::navbar>` mobile breakpoint | Variant marker emitted on the mobile-collapsed shape of the navbar. Lets developer CSS scope mobile-only navbar overrides. | Stable | [navbar](../components/navbar.md) |
| `wk-product-card-link` | `<x-wirekit::product-card href="…">` | Stretches the product-name link over the whole card via `::after`, so the pointer gets the big target while the LINK is still just the name. Wrapping the card in one anchor instead makes the link's accessible name the entire card — image, price, rating, button read as one breathless sentence — and swallows the CTA, which cannot nest inside an anchor at all. The overlay sits at `z-index: 1`, under anything the card marks `relative z-10` (the CTA), so the button keeps its own clicks. | Stable | [product-card](../components/product-card.md) |
| `wk-prose` | `<x-wirekit::prose>` | Marker for the prose wrapper — developers wiring their own typography overlay use this to scope rules without colliding with non-WireKit prose blocks. | Stable | [prose](../components/prose.md) |
| `wk-scroll-fade` | `<x-wirekit::scroll-area fade="…">` + apply directly | Edge-fade mask on a scroll container: dissolves the CONTENT at the overflow edge(s) via `mask-image` (background-agnostic — nothing to keep in sync with the surface color, unlike a colored overlay gradient). Reads `data-fade-axis="y\|x"` + `data-fade="both\|start\|end"`; depth tunes with `--fade-wk-size`. Dropped on `:focus-within` so a keyboard-focused child at an edge is never clipped. | Stable | [scroll-area](../components/scroll-area.md) |
| `wk-scrollbar` | Multiple scrollable components | Themeable scrollbar surface; pair with `--scrollbar-wk-*` tokens for cross-browser scrollbar customization. | Stable | [theming](../theming.md#scrollbar-tokens) |
| `wk-scroll-shadow` | Apply directly to your own scroll containers | Pure-CSS top/bottom overflow shadows drawn in the container's background (no JS). Tune with `--wk-scroll-shadow-bg` (cover color — must match the container background), `--wk-scroll-shadow-color`, `--wk-scroll-shadow-size`. Note: children paint above a background, so a hovered row at a scroll edge can cover these — the sticky-panel uses the overlay variants below instead. | Stable | [sticky-panel](../components/sticky-panel.md) |
| `wk-scroll-shadow-top` / `wk-scroll-shadow-bottom` | `<x-wirekit::sticky-panel>` + apply directly | Overlay-variant scroll shadows painted ABOVE the scrolled content (absolute siblings of the scroll container inside a `position: relative` wrapper; `pointer-events: none`). Visibility is yours to drive — the sticky-panel toggles them from an IntersectionObserver over two edge sentinels. Share the utility's `--wk-scroll-shadow-color` / `--wk-scroll-shadow-size` variables. | Stable | [sticky-panel](../components/sticky-panel.md) |
| `wk-scroll-shadow-start` / `wk-scroll-shadow-end` | Apply directly + `wirekitStickyPanelShadows()` | The same overlay affordance on the inline axis, for a bar that scrolls sideways — a tab strip, a chip row, a toolbar. Placed exactly like the block-axis pair and sharing the same two variables, so a theme tunes both axes at once. **The markup is usually yours; the driving never is.** `<x-wirekit::table>` renders this pair for its horizontal scroll, and `<x-wirekit::sticky-panel>` emits only the block-axis one — but the Alpine factory behind it, `wirekitStickyPanelShadows()`, reads `startSentinel` / `endSentinel` refs alongside `topSentinel` / `bottomSentinel` and drives whichever are present. So add the two sentinels and the two overlays to your own scroller and the shipped observer handles them, including all four at once on a container that scrolls both ways. Named `start` / `end` rather than left / right, so a right-to-left interface gets the cue on the side its content continues toward. Not interchangeable with `fade`: that mask is static and dims the edge even where nothing follows, leaving the last item looking disabled. | Stable | [sticky-panel](../components/sticky-panel.md) |
| `wk-overlay-root` | Read-only — WireKit creates it | The landmark every teleported overlay panel lives in. Dropdowns, tooltips, popovers, comboboxes and menus teleport their panel out of the document flow so an `overflow: hidden` ancestor cannot clip it — which also put every panel outside every landmark on the page, where an audit reports it and the developer cannot fix it because the markup is ours. WireKit's own JavaScript creates this container as `role="region"` with an accessible name and teleports into it instead. Created from JavaScript rather than a Blade directive on purpose: `x-teleport` treats a selector matching nothing as fatal, so a container that depended on a directive would break the page for anyone loading the bundle another way. An element you place yourself with this id is adopted rather than duplicated. | Stable | — |
| `wk-section` | `<x-wirekit::section>` | Carve-out marker for the page-section primitive. | Stable | [section](../components/section.md) |
| `wk-spine-aware` | `<x-wirekit::spine-aware>` | Marker emitted by the opt-in spine wrapper. Use as `[class*="wk-"]` carve-out target alongside the other layout markers. | Stable | [spine-aware](../components/spine-aware.md) |
| `wk-touch-target` | Component-internal icon buttons + apply directly | On a coarse pointer only, centers a transparent `::before` of 44x44 inside the control so the hit area clears the WCAG 2.5.5 target while the visible size stays exactly as designed. Gives its host a positioned ancestor, but only when the host does not already declare one — so it is safe on a `fixed` or `absolute` element, which it used to displace. (Before v2.24.0 it set `position: relative` unconditionally, and an unlayered declaration beats Tailwind's layered position utilities whatever the specificity.) Not for a dense grid: overlapping hit areas take taps meant for a neighbor. | Stable | [integration](../getting-started/integration.md#touch-targets) |

### Reading family

| Class | Emitter | Customization contract | Tier | Docs |
|---|---|---|---|---|
| `wk-reading-bookmark` (+ BEM `__dismiss`, `__label`, `__resume`) | `<x-wirekit::reading-bookmark>` | Resume-pill anchor; per-element BEM sub-classes scope dismiss / label / resume rules. Also serves as a carve-out marker for prose wrappers. | Stable | [reading-bookmark](../components/reading.md#reading-bookmark) |
| `wk-reading-meta` (+ BEM `__remaining`, `__separator`, `__total`) | `<x-wirekit::reading-meta>` | Block-level meta display; BEM sub-classes scope the remaining / separator / total spans. | Stable | [reading-meta](../components/reading.md#reading-meta) |
| `wk-reading-meta-paragraph` | `<x-wirekit::reading-meta perParagraph>` | Per-paragraph annotation span; carries `aria-hidden="true"`. | Stable | [reading-meta](../components/reading.md#reading-meta) |
| `wk-reading-minimap` (+ BEM `__anchor`, `__anchors`, `__bookmark-marker`, `__preview`, `__rendered`, `__rendered-canvas`, `__stripe`, `__stripe--active`, `__stripes`, `__tooltip`, `__viewport`) | `<x-wirekit::reading-minimap>` | Per-element type color palette via `--reading-minimap-color-*` tokens. Override individual BEM elements for custom shapes. | Stable | [reading-minimap](../components/reading.md#reading-minimap) |
| `wk-reading-progress` (+ modifier `--dot`, BEM `__fill`) | `<x-wirekit::reading-progress>` | Top-of-page progress-bar marker. `--dot` modifier swaps to the dot-indicator variant; `__fill` is the inner fill element. | Stable | [reading-progress](../components/reading.md#reading-progress) |
| `wk-reading-shell` | `<x-wirekit::reading-shell>` | Combined sugar-wrapper marker for the reading-* family. | Stable | [reading-shell](../components/reading.md#reading-shell) |
| `wk-reading-spine` (+ BEM `__back-to-top`, `__filter`, `__item`, `__label`, `__link`, `__number`, `__tick`) | `<x-wirekit::reading-spine>` | Sidebar TOC marker; reads `--reading-spine-color-{idle,active}` tokens. BEM sub-classes scope per-element overrides (item, link, number indicator, tick marker, filter, back-to-top button). | Stable | [reading-spine](../components/reading.md#reading-spine) |
| `wk-reading-toc` (+ `wk-reading-toc--flush`, `wk-reading-toc__item`, `wk-reading-toc__link`, `wk-reading-toc__list`) | `<x-wirekit::reading-toc>` | Horizontal TOC strip. Inline-padding ships WITHOUT `!important`; developer prose-wrappers should carve out `[class*="wk-"]` from `ul/ol/li` rules. The `--flush` modifier zeros horizontal padding on the nav, the inner list, AND the first / last link so the strip runs viewport-edge-to-viewport-edge as a chrome surface. | Stable | [reading-toc](../components/reading.md#reading-toc) |
| `wk-reading-content` | Hand-applied on the `<article>` in a reading-spine composition | Reserves the spine's inline-end gutter only at `md`+ (where the spine is visible) and uses full width below `md`. Override the gutter with `--reading-content-spine-gutter` (default `12rem`). | Stable | [reading-shell](../components/reading.md#reading-shell) |

### Responsive utilities

Breakpoint-scoped helpers for app-shell layouts where a fixed-width sidebar or two-pane split must collapse on narrow viewports. Both flip at the `md` (48rem) breakpoint, in lockstep with the reading-spine `hideBelow="md"` default.

| Class | Applied on | Purpose | Stability | Docs |
|-------|-----------|---------|-----------|------|
| `wk-hide-below-md` | Any secondary nav / filter sidebar | `display: none` below `md` so a desktop-only rail (calendar sources, mail folder list, kanban swim picker) doesn't crush the main content on a phone. | Stable | — |
| `wk-only-below-md` | Any element that is the phone-shaped half of a pair | `display: none` at `md` and up — the counterpart to `wk-hide-below-md`. A dense desktop affordance often wants a different SHAPE on a phone rather than a smaller one: the calendar's month grid swaps its event chips for dots plus an agenda list below, the way calendar apps do. Swapping the two takes both halves. | Stable | — |
| `wk-optimistic-surfaced` | Nothing — the optimistic layer creates and removes it | The refusal message, said where it can be seen. An optimistic control inside a menu is legitimate, and clicking a menu item closes the menu — so the message would otherwise render into a subtree with no box, heard by a screen reader and seen by nobody. The layer surfaces it only when the control's own host has no surface; a visible control says it in place and never reaches this rule. Restyle it to match your app's toast treatment. | Stable | [Optimistic UI](optimistic-ui.md) |
| `wk-autosize` | A `<textarea>` that should grow with its content | Sets `field-sizing: content`, so the control's height follows what is typed instead of staying at its `rows` value. `<x-wirekit::textarea rows="auto">` applies it for you. **Progressive enhancement:** the property is newer than the supported-browser floor, so the rule sits inside `@supports (field-sizing: content)`. Where it is not supported the textarea keeps its `rows` minimum and stays scrollable and resizable — nothing depends on the growing. | Stable | [Textarea](../components/textarea.md) |
| `wk-stack-below-md` | A flex-row two-pane container | Switches to `flex-direction: column` + full-width children below `md` so a master / detail split stacks vertically on a phone. | Stable | — |

### Color-mode utilities

Visibility helpers for swapping an asset (logo, illustration, screenshot) between light and dark mode without the Tailwind `dark:` variant — WireKit components never use `dark:`. Scoped to the `.dark` class, so they follow the same toggle as every WireKit token.

| Class | Applied on | Purpose | Stability | Docs |
|-------|-----------|---------|-----------|------|
| `wk-light-only` | An element that should show in light mode only | Hidden under `.dark` so its dark-mode partner shows instead. Used by `<x-wirekit::brand darkLogo>` for the light logo; apply it directly for your own mode-aware asset swaps. | Stable | [brand](../components/brand.md#dark-mode-logo) |
| `wk-dark-only` | An element that should show in dark mode only | `display: none` by default; restored to its natural display under `.dark`. The dark-mode partner of `wk-light-only`. | Stable | [brand](../components/brand.md#dark-mode-logo) |

### Animation / motion

| Class | Emitter | Customization contract | Tier | Docs |
|---|---|---|---|---|
| `wk-animate-{preset}-in` / `wk-animate-{preset}-out` (22 classes — 11 base presets × in/out) | `<x-wirekit::reveal>` and `wirekitAnimate` Alpine helper | One class per base preset × direction. See [Animations](../animations.md) for the catalog. Honors `prefers-reduced-motion: reduce`. | Internal-with-exception | [animations](../animations.md) |
| `wk-animate-fast` / `wk-animate-normal` / `wk-animate-slow` | `<x-wirekit::reveal duration="…">` | Duration modifiers (150ms / 300ms / 600ms). Themeable via `--motion-wk-duration-*` tokens. | Stable | [animations](../animations.md#durations) |
| `wk-stagger` | `<x-wirekit::feature-grid stagger>` / `<x-wirekit::stats stagger>` | Cascades `:nth-child(N)` animation-delay across direct children. Drive the step via `--wk-stagger-step` (default 75 ms). Caps at child index 8. Collapses to 0 under `prefers-reduced-motion: reduce`. | Stable | [feature-grid](../components/feature-grid.md#custom-stagger-via-css) |
| `wk-transition` | Multiple interactive components | Shared transition timing-function + duration shim; reads `--transition-wk-*` tokens. | Stable | [theming](../theming.md#transition-tokens) |
| `wk-countdown-pulse` | `<x-wirekit::countdown variant="segments">` | Change-pulse on a boxed unit (the `animate="box"` style, default): border + accent-tinted background flash with a small scale pop when the value changes. Accent-driven (re-tints with the theme). Honors `prefers-reduced-motion: reduce`. | Stable | [countdown](../components/countdown.md#change-animation) |
| `wk-countdown-text-flash` | `<x-wirekit::countdown variant="segments" animate="text">` | Change-flash on a boxed unit's NUMBER only (the `animate="text"` style): the digit's color eases from the accent back to its resting color when the value changes; the box stays still. Honors `prefers-reduced-motion: reduce`. | Stable | [countdown](../components/countdown.md#change-animation) |

### Display / loading

| Class | Emitter | Customization contract | Tier | Docs |
|---|---|---|---|---|
| `wk-avatar-group` | `<x-wirekit::avatar.group>` | Overlapping-avatar stack marker. Owns the children's logical negative margin (overlap) + the surface-color ring; scope per-instance overlap / ring overrides here. | Stable | [avatar](../components/avatar.md) |
| `wk-button-group` | `<x-wirekit::button.group>` | Joined-button-bar marker. Squares inner radii + collapses the shared seam (reads `data-orientation` for horizontal / vertical). | Stable | [button](../components/button.md) |
| `wk-chart-mixed` | `<x-wirekit::chart-mixed>` | Marker for the multi-dataset / mixed-type chart variant. | Stable | [chart](../components/chart.md) |
| `wk-command-list` | `<x-wirekit::command-palette>` inner list | Marker on the command-palette's filtered-result list. Scope developer overrides to this class to avoid touching other listbox-shaped components. | Stable | [command-palette](../components/command-palette.md) |
| `wk-editor-content` | `<x-wirekit::editor>` editable surface (set on the Tiptap element via `editorProps`) | Document typography target — default token-driven styling for headings, lists, blockquotes, code, and links inside the rich-text document. Override here to restyle the editor body. | Stable | [editor](../components/editor.md) |
| `wk-glass-refract` | Liquid-glass extension | Refraction overlay marker for the optional Liquid Glass extension. Reads the `--glass-wk-*` tokens documented in [theming → Liquid Glass Extension](../theming/cupertino.md#liquid-glass-extension). | Stable | [theming](../theming/cupertino.md#liquid-glass-extension) |
| `wk-radial` | `<x-wirekit::radial-progress>` | Ring root. Draws the whole ring in `::before` as one `conic-gradient` masked into an annulus — no canvas, no SVG arc math, so a value change is a single custom-property write. Reads `--wk-radial-value` (0–100) and `--wk-radial-fill`, both written inline by the component. The track uses `--color-wk-bg-muted`, the same track color the linear bar uses, so the two shapes read as one family. The sweep transition is dropped under `prefers-reduced-motion`. | Stable | [radial-progress](../components/radial-progress.md) |
| `wk-radial-sm` / `wk-radial-md` / `wk-radial-lg` / `wk-radial-xl` | `<x-wirekit::radial-progress size="…">` | Size steps. Each sets `--wk-radial-size`, `--wk-radial-thickness` and the center `font-size` together, so the ring stays proportional. Override `--wk-radial-thickness` for a hairline or a heavy ring without touching the component. | Stable | [radial-progress](../components/radial-progress.md) |
| `wk-radial-animate` | `<x-wirekit::radial-progress animate>` | Opt-in sweep. A keyframe animation interpolates the registered `--wk-radial-value` (via `@property`) from 0 up to the ring's value on mount, so the fill fills in instead of snapping; a re-mount restarts it. Gated by `prefers-reduced-motion`. | Stable | [radial-progress](../components/radial-progress.md) |
| `wk-progress-indeterminate` / `wk-progress-circle-indeterminate` | `<x-wirekit::progress>` indeterminate mode | Drives the indeterminate-state keyframes for linear + circular progress. Honors reduced motion. | Stable | [progress](../components/progress.md) |
| `wk-progress-stripes` / `wk-progress-shimmer` | `<x-wirekit::progress animation="stripes\|shimmer">` | Optional motion on a determinate fill — barber-pole stripes or a light sweep, the "work in flight" affordance. Honors reduced motion. | Stable | [progress](../components/progress.md) |
| `wk-replay-button` | `<x-wirekit::replay-button>` | Replay-control marker; reserved for developer-side replay-state styling (e.g. animation reset triggers). | Stable | [replay-button](../components/replay-button.md) |
| `wk-skeleton` | `<x-wirekit::skeleton>` | Skeleton-loading surface; read `--color-wk-skeleton-*` tokens for the shimmer-tint. | Stable | [skeleton](../components/skeleton.md) |
| `wk-typing-dots` | `<x-wirekit::message-typing>` | Flex row wrapping the three typing dots. Scope your own spacing/size overrides here without touching every dot. Decorative (`aria-hidden`) — the indicator's meaning lives in its visually-hidden text. | Stable | [message-typing](../components/message-typing.md) |
| `wk-typing-dot` | `<x-wirekit::message-typing>` | A single bouncing dot; colored with `--color-wk-text-subtle` and rounded with `--radius-wk-full`. Children 2 and 3 carry the stagger delays. Frozen under `prefers-reduced-motion: reduce`. | Stable | [message-typing](../components/message-typing.md) |
| `wk-slider` | `<x-wirekit::slider>` | Range-input wrapper marker. | Stable | [slider](../components/slider.md) |
| `wk-sparkline` | `<x-wirekit::sparkline>` | Inline trend-sparkline marker. Use for developer-side per-instance overrides without colliding with the generic chart classes. | Stable | [sparkline](../components/sparkline.md) |
| `wk-stream` | `<x-wirekit::stream>` | Streaming-text-output root marker. Scope the output typography or spacing per instance without touching the Alpine wiring. | Stable | [stream](../components/stream.md) |
| `wk-stream-output` | `<x-wirekit::stream>` | The streamed text region itself (`white-space: pre-wrap`). Target it to restyle the output type (font, leading, max-width) independent of the root. | Stable | [stream](../components/stream.md) |
| `wk-stream-caret` | `<x-wirekit::stream>` | The inline typing caret that trails the streamed text while a response is generating. Restyle its color / width / blink here; it is decorative (`aria-hidden`) and motion-safe. | Stable | [stream](../components/stream.md) |
| `wk-submenu-indicator` | `<x-wirekit::dropdown>` / `<x-wirekit::menubar>` submenu | Chevron / glyph marker on submenu trigger items. Scope rotation / color overrides without targeting every chevron in the page. | Stable | [dropdown](../components/dropdown.md) |
| `wk-text-shimmer` | `<x-wirekit::shimmer>` | Animated text-glyph shimmer (gradient clipped to the letterforms via `background-clip:text`) for live/streaming status. Tune the sweep with `--shimmer-wk-duration`; disabled under reduced-motion, forced-colors, and reduced-transparency. | Stable | [shimmer](../components/shimmer.md) |

### BEM sub-class enumeration

For drift-audit completeness, the reading-family BEM sub-classes are enumerated here as explicit literals. Each falls under the parent class's row above and follows the same Stable tier + customization contract.

`wk-reading-bookmark__dismiss`, `wk-reading-bookmark__label`, `wk-reading-bookmark__resume`,
`wk-reading-meta__remaining`, `wk-reading-meta__separator`, `wk-reading-meta__total`,
`wk-reading-minimap__anchor`, `wk-reading-minimap__anchors`, `wk-reading-minimap__bookmark-marker`, `wk-reading-minimap__preview`, `wk-reading-minimap__rendered`, `wk-reading-minimap__rendered-canvas`, `wk-reading-minimap__stripe`, `wk-reading-minimap__stripe--active`, `wk-reading-minimap__stripes`, `wk-reading-minimap__tooltip`, `wk-reading-minimap__viewport`,
`wk-reading-progress--dot`, `wk-reading-progress__fill`,
`wk-reading-spine__back-to-top`, `wk-reading-spine__filter`, `wk-reading-spine__item`, `wk-reading-spine__label`, `wk-reading-spine__link`, `wk-reading-spine__number`, `wk-reading-spine__tick`.

## AI tooling / LLM consumption

The entire catalog above is also emitted as machine-readable JSON via the `css-classes` group of `wirekit:export-api-map`:

```bash
# List every public CSS class as JSON for AI tooling consumption.
php artisan wirekit:export-api-map --pretty | jq '.groups[] | select(.id == "css-classes")'
```

The output shape per class:

```json
{
    "id": "wk-stagger",
    "tier": "Stable",
    "docs_url": "https://docs.wirekit.app/extending/public-css-api#animation--motion"
}
```

The `tier` value mirrors the [Stability tiers](#stability-tiers) section above — `wk-animate-*` state classes carry `Internal-with-exception`; every other class is `Stable`. The `docs_url` anchors to the catalog sub-section (Layout / Reading family / Animation / Display) the class lives under, NOT to the emitter component's page, so AI tooling can route to the canonical contract in one hop.

Cursor / Claude Code / MCP-server developers wire this group as a discovery surface for `wk-*` autocomplete — pair with [`wirekit:list --as=json`](../cli-reference.md#wirekitlist) and [`wirekit:show {name} --as=json`](../cli-reference.md#wirekitshow-name) for full component + class coverage in one toolchain.

## Lockstep guarantee

The catalog is anti-drift enforced in both directions:

1. **Every `wk-*` class WireKit emits (compiled CSS OR Blade templates) has a row in the catalog above.** Adding a new `wk-{name}` to a Blade emission without updating this page fails the upstream build.
2. **Every catalog row references a class that actually ships in `dist/wirekit.css`.** Removing a class without removing its row fails the upstream build.

Combined, the two directions make the catalog the single source of truth for the public CSS API surface — drift between this page and the shipped CSS is impossible.

## See Also

- [Content-Edge Spine](spine-contract.md) — the per-component inline-padding contract.
- [Theming](../theming.md) — CSS variable / token surface (the OTHER half of the public CSS API).
- [Animations](../animations.md) — full `wk-animate-*` preset catalog.
- [CLI Reference — wirekit:export-api-map](../cli-reference.md#wirekitexport-api-map) — the AI-tooling export this catalog feeds.
