Patterns

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…5 in 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.