Askr
UI & Components

Alert, Badge, Empty, Skeleton, Spinner, and Stat

Use alert, badge, empty, skeleton, spinner, and stat with the current published Askr packages.

  • @askrjs/themes/alert0.0.13
  • @askrjs/themes/badge0.0.13
  • @askrjs/themes/empty0.0.13
  • @askrjs/themes/skeleton0.0.13
  • @askrjs/themes/spinner0.0.13
  • @askrjs/themes/components0.0.13

How to use alert, badge, empty, skeleton, spinner, and stat

Alert, Badge, Empty, Skeleton, Spinner, and Stat should use the standard themed surface unless the application is deliberately implementing its own design system on top of @askrjs/ui.

  1. Import the themed component from @askrjs/themes/components; use @askrjs/ui directly only when building a custom visual system.
  2. Keep alert, badge, empty, skeleton, spinner, and stat labels, state, and application actions visible at the call site instead of styling internal DOM nodes.
  3. Verify keyboard behavior, focus movement or return, disabled state, and both standard themes.
import { Alert, AlertDescription, AlertTitle, Badge, Empty, EmptyDescription, EmptyTitle, Spinner } from '@askrjs/themes/components';

{query.pending ? <Spinner aria-label="Loading projects" /> :
 query.error ? <Alert variant="destructive"><AlertTitle>Could not load projects</AlertTitle><AlertDescription>{query.error.message}</AlertDescription></Alert> :
 query.data.length === 0 ? <Empty><EmptyTitle>No projects</EmptyTitle><EmptyDescription>Create a project to get started.</EmptyDescription></Empty> :
 <Badge>{query.data.length} active</Badge>}

Purpose

None of Alert, Badge, Empty, Skeleton, Spinner, or Stat ships a headless counterpart in `@askrjs/ui` — each lives only in the themes package, since their behavior amounts to static markup and styling rather than anything complex enough to justify a separate logic layer. Stat is the odd one out in a different way: it has no dedicated `@askrjs/themes/stat` subpath, so you reach it through the shared `@askrjs/themes/components` barrel instead of importing it on its own.

Install and import

Alert, Badge, Empty, Skeleton, and Spinner each have their own themes subpath — `@askrjs/themes/alert`, `@askrjs/themes/badge`, `@askrjs/themes/empty`, `@askrjs/themes/skeleton`, `@askrjs/themes/spinner`. Stat has no subpath of its own: import `Stat`, `StatLabel`, `StatValue`, and `StatDescription` from the full `@askrjs/themes/components` barrel instead — or pull all six components from that same barrel if you'd rather have one import for the whole group.

Live examples

Alert, Badge, Empty, Skeleton, and Spinner all have live, interactive renders on this page so you can see their variants and states without wiring up your own harness. Stat renders live too, just via the `@askrjs/themes/components` barrel import rather than its own subpath — the example is otherwise a normal `Stat` / `StatLabel` / `StatValue` composition.

Anatomy

Alert is a single component with `icon`, `title`, `description`, and `actions` slots plus a `titleAs` heading tag; Badge and Skeleton are similarly flat, single components with no compound parts. Empty is the exception — it's compound, with EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription, and EmptyContent slotting into an EmptyState wrapper, closer in shape to Card than to Alert or Badge. Spinner has no compound structure either; it's a themed wrapper around ProgressCircle's indeterminate presentation. Stat is compound too: a `Stat` wrapper composes with `StatLabel`, `StatValue`, and `StatDescription` children.

State model

None of these six own any state — every one of them is presentational, driven entirely by the props you pass on each render, and that includes Stat. Alert accepts an `onDismiss` callback for a caller-owned dismiss button, but the decision to mount or unmount the alert is still yours; Alert doesn't track its own visibility internally.

Keyboard and accessibility

Alert accepts a `dismissLabel` for the accessible name of its optional dismiss control, since that's the only interactive part across this group. Badge, Skeleton, Empty, Stat, and Spinner are non-interactive and have no keyboard behavior to test; because Spinner is effectively ProgressCircle's indeterminate look, give it a `label` so assistive tech gets something better than silence while it spins.

Styling and tokens

Alert and Badge both take a `variant` prop that maps to theme tokens instead of requiring custom color CSS — Alert's set is `'default' | 'info' | 'success' | 'warning' | 'danger'`, Badge's is a bit wider with `'secondary'` and `'outline'` added. Skeleton takes explicit `width`/`height` so its placeholder shape matches the real content it's standing in for, and Spinner takes a `size` of `'sm' | 'md' | 'lg'`.

API

Badge supports `asChild` to render as something other than a `span`, and Skeleton supports the same pattern for its wrapping `div`. Alert's props are `title`, `description`, `icon`, `actions`, `titleAs`, `dismissLabel`, `onDismiss`, and `variant`; EmptyState's are `icon`, `title`, `titleAs`, `description`, and `action`.

Edge cases

Stat has no dedicated subpath, so a `@askrjs/themes/stat` import will fail — reach for `@askrjs/themes/components` instead, which is easy to miss if you're used to every other component here having its own subpath. Skeleton has no built-in timeout, so pair it with the real loading/error state next to it instead of letting it sit on screen indefinitely if a request stalls.

See Progress for a determinate alternative to Spinner's indeterminate look, and Toast and Sonner for time-boxed feedback instead of a persistent inline Alert.