Tiered Test Display
A staged test-results panel. Each tier groups a set of related checks and rolls them up into a single status (passed, failing, pending). The first failing tier gates the ones after it — subsequent tiers render in a muted pending row so the reader fixes the earliest failure first. When every tier passes, a success banner renders above the list.
Preview
ascending order
no duplicates
Found duplicate at index 2
Customize
Tiers
0
1
Installation
npx shadcn@latest add https://craftbits.dev/r/tiered-test-display.jsonUsage
import { TieredTestDisplay } from "@craft-bits/core";
<TieredTestDisplay
tiers={[
{ label: "Shape", tests: [{ name: "returns array", status: "passed" }] },
{
label: "Values",
tests: [
{ name: "ascending order", status: "failed", message: "Got [3, 1, 2]" },
],
},
{ label: "Exact", tests: [{ name: "matches reference", status: "passed" }] },
]}
/>Pass an empty tiers array to show the muted fallback, or override the fallback with your own node:
<TieredTestDisplay tiers={[]} emptyFallback={<p>No tests run yet.</p>} />Understanding the component
- Tiers gate each other. The rollup walks tiers in order. Once a tier is failing, every later tier becomes pending regardless of its tests — its row is dimmed and not expandable.
- Each tier auto-rolls up. A tier is passed only when every test in it passed. Any non-passed test flips the tier to failing. Empty tiers count as passed.
- First failing tier auto-expands. When a tier is failing and no
defaultExpandedis set, it opens on mount. Other tiers stay collapsed by default. - Tests are flat. Each test is
{ name, status, message? }. Themessageslot accepts any node — paragraphs, expected/got blocks, links. - Success banner is a slot. When every tier passes, a default "All checks passed." banner renders above the list. Pass
allPassedBanner={null}to suppress, or replace it with your own node.
Variants
All passing
<TieredTestDisplay
tiers={[
{ label: "Shape", tests: [{ name: "returns array", status: "passed" }] },
{ label: "Values", tests: [{ name: "ascending", status: "passed" }] },
]}
/>Middle tier failing
<TieredTestDisplay
tiers={[
{ label: "Shape", tests: [{ name: "returns array", status: "passed" }] },
{
label: "Values",
tests: [{ name: "ascending", status: "failed", message: "Got [3,1,2]" }],
},
{ label: "Exact", tests: [{ name: "matches reference", status: "passed" }] },
]}
/>Compact density
<TieredTestDisplay density="compact" tiers={tiers} />Props
| Prop | Type | Default | Description |
|---|---|---|---|
tiers | readonly { label; tests; id?; defaultExpanded? }[] | required | Ordered tiers. Each tier rolls up to one status; the first failing tier gates the rest. |
density | 'cosy' | 'compact' | 'cosy' | Vertical rhythm between tier rows. |
allPassedBanner | ReactNode | null | built-in success banner | Banner shown when every tier passes. null suppresses. |
emptyFallback | ReactNode | null | "Run tests to see results." | Rendered when tiers is empty. null renders nothing. |
className | string | — | Merged onto the root via cn(). |
...rest | HTMLAttributes<HTMLDivElement> | — | Any other root attribute. |
Accessibility
- The root is a polite live region (
role="region"+aria-live="polite") so streaming test updates surface to assistive tech without interrupting the user. - Each tier toggle is a real button with
aria-expanded/aria-controlstying it to its body. Pending tiers are disabled and a short status message reads "Fix the previous tier first". - The success and failure tick glyphs are
aria-hidden; the tier label and thepassed/totalcounter carry the semantic weight. - Chevron, status pop, and content height transitions all degrade to instant transitions under
prefers-reduced-motion: reduce. - Status colours read from the cb semantic tokens (
--cb-success,--cb-error,--cb-fg-muted) and clear WCAG AA contrast on the default surface.
Credits
- Extracted from:
craftingattention(app/src/lessons/primitives/lab/TieredTestDisplay.tsx). The original coupled to the project'sTestSummary/LabTestMetaschema, ran diagnosis-pattern matching, and rendered an inlineReferenceDiffCard. craft-bits drops all of that — the API now accepts pre-rolled tiers of flat{ name, status, message? }tests, exposesdensityand slot-based banners, and consumes cb tokens for every colour and spring.