Ansora section card
A card for one section of a screen — heading with real heading semantics, optional description, a slot for a control, and an optionally scrollable body. The shell every data view on a dashboard sits in.
primitive
Install
pnpm dlx shadcn@4.23.0 add @ansora/section-cardExamples
Usage
title is a required string, not a ReactNode: upstream CardTitle renders <div data-slot="card-title"> with no asChild/render escape hatch, so this component patches heading semantics onto it (role="heading" aria-level={2}) — a visual heading with no semantics fails WCAG 1.3.1. title is required even when the heading is not shown: an accessible name is required, a visible one is not always wanted.
description, action and titleHidden form a discriminated union, the same trick StatusPill uses for children. With titleHidden absent or false (the default): description and action are both optional; action is a slot (a Select, a button, a filter), not configuration — this card does not know what a range is. With titleHidden: true: no CardHeader renders at all — not merely hidden, gone — title instead renders as an sr-only element that still carries role="heading" aria-level={2}, and the Card gets gap-0 py-0 so it hugs its content with no header left to put space between. description and action both fail to compile in that branch (description?: never, action?: never): neither has anywhere left to render once CardHeader is gone, so the type refuses the combination instead of silently dropping one. Reach for titleHidden when a section reads as a distinct block with no printed label of its own — @ansora/kpi-strip is the one consumer today, because its reference has no heading a sighted user can see, only four numbers; that is strictly better than rendering no heading at all, since the scroll region below still needs a real accessible name (WCAG) and a screen-reader user still needs a heading to jump to.
scroll turns the body into a horizontal scroll region; it is { label: string }, not a boolean, so a scroller cannot be switched on without naming it — an unnamed focus stop is announced as an anonymous box, and one with nothing focusable inside is unreachable by keyboard at 200% zoom (WCAG 1.4.10 / 2.1.1). The body renders with no horizontal padding (px-0 on CardContent) — on purpose, so a table can run edge to edge, with its row hover and row separator full-bleed to the card's own border; content that wants the inset back asks for it itself with className="px-(--card-p)", do not remove the px-0 to get it.
import { SectionCard } from "@/components/ui/ansora/section-card"
<SectionCard title="Pacienti" description="Za posledních 30 dní" action={<Select />}>
<DataTable ... />
</SectionCard>
<SectionCard title="Klíčové ukazatele" titleHidden scroll={{ label: "Klíčové ukazatele" }}>
<KpiCells />
</SectionCard>
Files
registry/ansora/ui/section-card.tsx→@ui/ansora/section-card.tsx
Dependencies
@ansora/basehttps://ui.shadcn.com/r/styles/base-nova/card.json
Save status
Whether what the user typed exists off this machine — five states, one recovery action slot, and a live region that speaks only while something is wrong.
Ansora sidebar
Side navigation with collapsing, a mobile drawer and a keyboard shortcut. A vendored and patched copy of the shadcn sidebar.