Widget
A generic chrome container for the interactive surfaces that live inside lessons, dashboards, and explorable explanations. Wraps any body in a labelled role="region", exposes an optional actions slot for buttons or live readouts, and supports three surface tones plus an optional collapsible disclosure.
Reach for it when a lesson canvas needs a labelled card around a control surface — a dial, a slider, a small simulator — without committing to a heavier card primitive or hand-rolling the chrome each time.
Live readout
Drag the dial to rotate the basis vector. The widget body crossfades on collapse — never animates height.
Installation
npx shadcn@latest add https://craftbits.dev/r/widget.jsonUsage
import { Widget } from "@craft-bits/edu";
<Widget title="Live readout">
<Dial value={theta} onChange={setTheta} />
</Widget>Render an actions row to the right of the title — useful for status pills, secondary buttons, or a live numeric readout:
<Widget
title="Rotation"
actions={<span className="font-mono tabular-nums">theta</span>}
>
<Dial value={theta} onChange={setTheta} />
</Widget>Wrap the widget in a tone wash for a hero widget on a lesson page:
<Widget title="Key intuition" tone="accent">
<Visualisation />
</Widget>Make the body collapsible — controlled or uncontrolled, both work:
<Widget title="Details" collapsible defaultOpen={false}>
<Details />
</Widget>Anatomy
- Root —
<section role="region">with thecb-bg-elevated/cb-accent-muted/cb-bg-mutedsurface; spreads unknown props onto the element. - Header — the title sits on the left as either an
<h3>or a disclosure<button>; the actions slot sits on the right and stays reachable when the body is collapsed. - Body — wrapped in
AnimatePresencewithinitial={false}. Animatesopacity+ a smalltranslateYenter / exit onSPRINGS.smooth— neverheight, so the motion is GPU-composited. - Chevron — rotates from
-90°(closed) to0°(open) onSPRINGS.snap. - Reduced motion —
prefers-reduced-motion: reduceshort-circuits the body enter / exit and the chevron rotation; the body appears or disappears instantly.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
title | ReactNode | — | Headline rendered in the top-left of the chrome. Omit for an anonymous container. |
actions | ReactNode | — | Slot rendered to the right of the title — buttons, pills, live readouts. |
tone | 'default' | 'accent' | 'muted' | 'default' | Surface tone. Drives both the background wash and the title color. |
collapsible | boolean | false | Render a disclosure affordance that lets the user collapse the body. |
open | boolean | — | Controlled open state. Pair with onOpenChange. |
defaultOpen | boolean | true | Uncontrolled initial open state. Ignored when open is provided. |
onOpenChange | (open: boolean) => void | — | Fires whenever the user toggles the disclosure. |
children | ReactNode | — | The widget body — drop any interactive surface inside. |
className | string | — | Merged onto the root via cn(). |
Accessibility
- The root is a
<section>withrole="region". Whentitleis set, anidis generated and referenced viaaria-labelledbyso the widget reads as a named landmark; otherwise consumers passaria-label. - When
collapsible, the title becomes a<button>witharia-expandedandaria-controlspointing at the body — Radix-equivalent disclosure semantics without a runtime dependency. - Focus styling on the disclosure trigger uses
focus-visible:ring-2 ring-cb-accent ring-offset-2 ring-offset-cb-bgso keyboard focus is unambiguous on every tone. - Motion is
opacity+transformonly — neverheight/top/left. The body slides ontranslateY, the chevron rotates. prefers-reduced-motion: reduceshort-circuits both the body enter / exit and the chevron rotation — content appears or disappears instantly.- Color contrast respects the
cb-*token palette so light and dark themes both clear WCAG AA.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/chrome/Widget.tsx). The source coupled the chrome to lesson curriculum —useWidgetGate,useWidgetHistorywith[/]keybindings,eyebrow/premise/caption/formularows wired to a project-specific narrative shape, and a mandatoryonResetplus per-bookmark "canonical state" pills tied to the lesson's auto-satisfy gate. The craft-bits extract distils the API to its kernel — a labelled region with atitle, anactionsslot, three tones, and an optional collapsible body — and drops the curriculum coupling so the widget can wrap any interactive surface.