Patterns
Three conventions, inspired by semantic-ui-react, hold the React API together:
shorthand slots for repeated items, the as prop for element polymorphism,
and fixed rules for chart color. Learn them once — they work the same everywhere.
Shorthand slots
Data-driven components with a repeated item slot accept shorthand: a bare
string | number, a props object, or a <Parent.Item>
element. Table rows and Spreadsheet rows both work this way:
<Table
columns={[{ key: 'amount', label: 'Amount', numeric: true }, { key: 'status', label: 'Status' }]}
rows={[
{ amount: '$4,200.00', status: 'Paid' }, // bare value
{ amount: { content: 'Unlimited', numeric: false }, // props object
status: { content: 'Overdue', status: 'err' } }, // typed status dot
]}
/>
<Spreadsheet
readOnly // grid-level default…
rows={[[ 'Cash', 4200, { value: 5060, tone: 'muted' } ],
[ 'Growth %', <Spreadsheet.Cell value="4.0" onValueChange={setGrowth} /> ]]}
/> // …that any cell overrides
Merge order is defaultProps ← value's props ← overrideProps — container
wiring goes in defaults, so an item's own props always win. Build your own with
createShorthandFactory, exported from the package.
The as prop
Components whose element genuinely varies take as, fully typed against the
rendered element — href without as="a" is a compile error:
<Button as="a" href="/reports/latest">Open latest report</Button>
<Button as={Link} to="/invoices">Invoices</Button>
Item slots use the lighter form — as plus a linkProps
passthrough — on Breadcrumb, SideNav, and
Pagination (where items are generated, linkProps is a function
of the page):
<SideNav sections={[{ title: 'Billing', items: [
{ id: 'invoices', label: 'Invoices', as: Link, linkProps: { to: '/invoices' } },
]}]} />
<Pagination page={page} totalPages={12}
as={Link} linkProps={(p) => ({ to: `/invoices?page=${p}` })} />
Chart color rules
- Series take
--corpo-chart-1…5in fixed order, never cycled — filtering series never repaints the survivors. - Five series max; a sixth renders neutral gray as a signal to fold the tail into an "Other" series.
- The palette is CVD-validated on both light and dark surfaces (dark uses its own muted steps).
- Status colors (
green/red/amber) are reserved for status — never chart series. - Legends render only for two or more series; axis and legend text always wears text tokens, never series color.
Voice and layout
- Sentence case everywhere; uppercase mono is a style applied by components, not typed content.
- Verb-first button copy — "Save changes", never "Submit". No exclamation marks, no emoji.
- Two breakpoints only: 768px for structural changes, 480px for content density. Prefer fluid CSS over either.
- Every truncatable text carries the full ellipsis contract; wide content scrolls inside its own container.