@corelithzw/react
v0.3.0
Published
Corelith React primitives — thin JSX wrappers over the Corelith design-system CSS.
Maintainers
Readme
@corelithzw/react
Thin React wrappers over the Corelith design-system CSS. Components render the exact class names already shipped in components.css, so any cookbook recipe in the docs site translates verbatim into your app.
Status:
v0.3.0— published to public npm as@corelithzw/react. ~60 components, 14 hooks, full TypeScript types, bundled design-system stylesheet. 188 tests; public API report committed atetc/api-report.md.
Install
npm install @corelithzw/react react react-domNo .npmrc, no PAT, no setup. Works in any repo, any CI, any Docker container. Owner-side publish setup + troubleshooting in INSTALL.md.
Then import the bundled stylesheet once at the root of your app:
import '@corelithzw/react/styles.css';That single import pulls in the design-system tokens, components.css and the per-component portal-positioning fallbacks the package ships. Nothing else is required — no separate tokens.css or font import.
30-second example
import {
AuthShell, Form, Field, Stack,
Input, Button, Alert,
} from '@corelithzw/react';
import '@corelithzw/react/styles.css';
export function SignIn() {
return (
<AuthShell>
<AuthShell.Brand product="Corelith" />
<AuthShell.Card title="Sign in" subtitle="Welcome back">
<Form onSubmit={(e) => e.preventDefault()}>
<Stack gap="md">
<Alert tone="info">Use your work email.</Alert>
<Field label="Email" required>
<Input type="email" autoFocus />
</Field>
<Field label="Password" required>
<Input type="password" />
</Field>
<Button type="submit" fullWidth>Continue</Button>
</Stack>
</Form>
</AuthShell.Card>
</AuthShell>
);
}Components
Primitives
| Component | Summary |
| ----------------- | ------------------------------------------------------------------------- |
| Button | Variants (primary/secondary/ghost), tones, sizes, loading, icons |
| Input | Auto-wires id, aria-describedby, aria-invalid from nearest Field. leadingIcon / trailingIcon / trailingSlot for icon-chrome inputs. |
| InputOtp | Per-digit OTP cells, paste-into-all, autocomplete="one-time-code" |
| Field | Label + description + error wrapper with Field.Label/.Description/.Error |
| Form | <form> with noValidate, Enter-submit toggle |
| Checkbox | .check styled checkbox + optional label, supports indeterminate |
| Radio + RadioGroup | Context-based radio set with shared name and value |
| Switch | Toggle switch with role="switch" |
| Select | Styled <select> with optional options[] shorthand |
| Combobox | Searchable, group-able listbox |
| Badge | Tones (neutral/info/success/warn/danger/clay/outline) |
| Avatar | Initials, image, sizes (sm/md/lg), tones (default/clay/ink) |
| Spinner | role="status", configurable label |
| Skeleton | Width/height props, optional lines for stacked placeholders |
| Tooltip | Hover/focus content; wraps a single child |
| Kbd | Keyboard chip |
| Popover | Outside-click + Escape dismissable; optional arrow |
| Drawer | Portal side panel; collapses to bottom-sheet on phone |
| Tabs | Tabs.List + Tabs.Tab + Tabs.Panel; supports defaultValue, arrow/Home/End keyboard nav |
| Stepper | Pill-style progress; Stepper.Step or total/current shorthand |
| RoleSwitcher | Pill-shaped segmented toggle, generic over any string enum; supports defaultValue |
| Pagination | Page nav + optional page-size picker |
| SaveBar | Sticky save bar that slides in when dirty |
| Grabber | Drag handle for reorderable rows |
| EmptyState | full (column) and inline (banner) variants |
| Menu | Menu.Item + Menu.Label + Menu.Divider (also Menu.Separator) |
| DropdownMenu | Alias for Menu (recipes use both names) |
| NavGroup | Labeled <nav> group for sidebar nav items (label prop renders an <h6>) |
| NavItem | Sidebar nav item: active, to, icon, badge; sets aria-current="page" |
| CommandPalette | ⌘K modal with search, groups, keyboard navigation |
| TextArea | Multi-line <textarea>; consumes FieldContext like Input |
| Meter | Qualitative meter with low/high thresholds (role="meter") |
| Progress | Determinate or indeterminate progress bar |
| SegmentedControl| Radio-group pill switch; supports controlled value and uncontrolled defaultValue |
| InlineEdit | Click-to-edit; save on Enter/blur, cancel on Esc |
| Calendar | Month grid; value, onChange, min, max, disabledDates |
Blocks
| Component | Summary |
| ------------- | --------------------------------------------------------------------------- |
| BottomTabs | Mobile bottom-tab nav with active state + optional badge |
| StatHero | Brand-tinted lead stat + secondary tiles row |
| StatCard | Single stat tile with label/value/delta |
| DayList | Two-column day/value list with optional up/down tone |
| PageHeader | Topbar with optional back button, title, right-side actions slot |
| RowCard | Tap-target row card for mobile lists |
| FilterChips | Horizontal scrolling chip row; supports controlled value and uncontrolled defaultValue |
| BottomSheet | Portal sheet with focus trap + Escape + backdrop dismiss |
| Card | Card.Header / Card.Title / Card.Body / Card.Footer wrapper |
| Checklist | First-run onboarding list with Checklist.Item (done/title/subtitle) |
| DataToolbar | DataToolbar.Search + .Filters + .Actions slot wrapper |
| Chart | Inline-SVG Chart.Line / Chart.Bar / Chart.Donut / Chart.Sparkline |
| KpiGrid | Auto-fit grid wrapper for StatCard / Stat tiles |
| Stat | Alias for StatCard (recipes use both names) |
| FileUpload | Drag-drop + click + paste-image zone (pairs with useUpload) |
| Lightbox | Portal fullscreen image viewer (pairs with useGallery) |
| LocalePicker| Locale <select> driven by I18nProvider |
Patterns
| Component | Summary |
| ----------- | --------------------------------------------------------------------------------- |
| AppShell | Desktop sidebar shell: AppShell.Sidebar + AppShell.Main + AppShell.TopBar (alias Topbar) + AppShell.Brand. collapsed prop drives an icon-rail. AppShell.Sidebar accepts collapsible + onToggle. |
| AuthShell | Centered-card auth shell: AuthShell.Brand + AuthShell.Card |
| DataTable | <table class="dtable"> with sortable headers + row selection |
| Modal | Centered dialog with focus trap + Escape; participates in overlay stack (nested Modal/Drawer Escape closes innermost first) |
| Dialog | Opinionated Modal with built-in confirm/cancel buttons; forwards ref to the dialog element |
| Toast | ToastProvider + useToast() → { show, dismiss } |
| Alert | Inline banner with tones |
| Stack | direction, gap, align, justify, wrap flex helper |
| MobileShell | MobileShell.Body + .BottomTabs mobile-first wrapper |
| I18nProvider | Tiny i18n context with {var} interpolation (useT() returns the t fn) |
| AlertDialog | Opinionated confirm/cancel dialog built on Modal. Plus AlertDialog.confirm() imperative API → Promise<boolean> |
| KanbanBoard | Drag-and-drop kanban with WIP limits, touch long-press, keyboard pick-up; pair with useKanban |
| CommentsThread | Flat-Comment[] threaded discussion with @mention autocomplete, edit-in-place, reactions, resolve; pair with useComments |
| NotificationMatrix | Settings table of events × channels with a master pause row; pair with usePreferences |
| DatePicker | Single-date popover composed from Input + Popover + Calendar |
Hooks
| Hook | Summary |
| ----------------- | -------------------------------------------------------------------------------------------------- |
| useToast() | Returns { show(input), dismiss(id) }. Must be inside <ToastProvider>. |
| useInterval() | setInterval with always-latest callback, cleanup, and a paused option. |
| useUrlState() | Syncs state to ?key= query param via history.replaceState; SSR-safe. |
| useOptimistic() | Base / derived / queue mutation pattern. Returns { base, derived, mutate, queue }. |
| useMatchMedia() | SSR-safe matchMedia subscriber. |
| useMediaQuery() | Alias for useMatchMedia() matching the cookbook naming. |
| useUpload() | XMLHttpRequest-backed file upload with progress fraction and cancel(). |
| useGallery() | Tiny Lightbox controller: { open, index, show, close, next, prev, setIndex }. |
| useT() | Returns the t(key, vars?) translator from <I18nProvider>. |
| usePersistedFlag() | useState-shaped boolean persisted to localStorage. Survives reload + sign-out; SSR-safe. |
| useKanban() | State manager for KanbanBoard: { items, move, addCard, removeCard, setItems }. |
| useComments() | State manager for CommentsThread: { comments, add, edit, resolve, react, reply }. |
| usePreferences() | State manager for NotificationMatrix: { prefs, set, pauseFor, pause, quietHours, setQuietHours }. |
| useDateRange() | Paired ISO-string date range with presets (today, this-month, last-7-days, …) and isPreset(). |
TypeScript
Types are bundled — there's nothing extra to install. Every component exports a named props interface (e.g. ButtonProps, DataTableProps<Row>). Most components forward refs to the underlying DOM element.
Versioning & stability
We follow semver, with one extra rule: deprecated APIs always get one minor of warning before removal in the next major.
- Patch (
0.x.Y) — bug fixes only. No public-API changes. - Minor (
0.X.0) — strictly additive. New components, new props, new exports. Old APIs continue to work. Renames land as new props with the old one kept as a deprecated alias (JSDoc@deprecated). - Major (
X.0.0) — may remove previously deprecated APIs. Removals are listed in the changelog with the alias that replaces them.
The public surface is enumerated in etc/api-report.md — see below.
Public API report
Every release commits a snapshot of the full public surface to etc/api-report.md:
npm run api-reportThe script imports dist/index.cjs, walks every export, classifies it (forwardRef component, forwardRef namespace, hook, function component, constant, type-only, …) and writes a sorted markdown table. PRs that touch the public API must regenerate the file — a diff against etc/api-report.md is the source of truth for whether a change is additive or breaking.
Tree-shaking
The package is a single entrypoint (index.ts) that re-exports every component, hook and type. Bundlers that honour sideEffects (Webpack, Vite, esbuild with treeShaking: true, Rollup) will drop unused exports — the package marks *.css and the CDN bundle as the only side-effectful files in package.json.
What this means in practice: import { Button } from '@corelithzw/react' ships only Button (plus its transitive imports) in your bundle, not the entire library. The shared stylesheet is imported once (top of index.ts) so the *.css sideEffects entry keeps it in.
Docs
Full docs, do/don't, and live previews: huchu docs site. Every recipe in /cookbook/*.html imports from this package — those recipes are the canonical examples for every component above.
