Design system · dev handover
{{ g.group }}
22 components
9 colour tokens
Namespace CausomicDesignSystem_d36d9c
Getting started

Every token, component and rule, as built.

This is the reference of record for the Causomic interface. Each component below is the live component from the bundle, not a picture of one. Props are the shipped API.

Where the system is silent — dark mode, modals, toasts, breakpoints beyond the 720px switch — it is silent on purpose. Ask before inventing; new patterns come back into this file.

Two ways in

Import the React components, or style your own markup against the token variables. Both read from the same CSS custom properties, so they can coexist in one page.

React
import { Button, Kicker } from '@causomic/ds';
import '@causomic/ds/styles.css';

<Button href="/book">Book a call</Button>
Plain CSS
.cta {
  background: var(--ink);
  color: var(--cta-fg);
  padding: var(--btn-pad);
  border-radius: var(--radius);
}

Files to include

Load in this order. styles.css imports the token files, so importing it alone is enough in a bundler.

{{ f.n }}
{{ f.d }}

Non-negotiables

Nine rules that keep the system one visual world. A pull request that breaks one of these is a bug, not a variation.

{{ r.n }}
{{ r.t }}
{{ r.d }}
Foundations

Colour

A committed light theme. Two grounds, two text tones, two rule weights, one accent. There is no third text tier and no dark variant — do not add either.

--bone
#ECEFE9
Page ground. Also button text on ink.
--paper
#F2F4F0
Panel and figure ground. Cells in a grid.
--ink
#33303E · 11.2:1
Text, CTAs, footer band. Solid ink means act here.
--ink-70
rgba(51,48,62,.70) · 4.65:1
Body copy and mono microtype. The only second tone.
--amber
#A8500F · 4.9:1 on paper
The one accent. Means "look here — this is the one". Never decorative.
--rule / --rule-2
.16 / .30 opacity
Hairlines run edge to edge. rule-2 for input borders and hover underlines.
--grid-mid
rgba(51,48,62,.55)
Legend swatches and non-selected data marks.
--plus
34px SVG data-URI
Figure-panel texture only. Never behind body copy.

Semantic aliases

Prefer these in product code. They let the palette move without a find-and-replace.

{{ a.n }}
{{ a.v }}
{{ a.d }}

Amber, in practice

Do
One amber thing per view: the top-ranked design, the cutoff line, the available date. If two things are amber, neither is the one.
Don't
Amber as a hover colour, link colour, brand flourish, error state or chart series. It is not a palette — it is a pointer.
Foundations

Type

Two families. Switzer carries everything a person reads; Geist Mono carries everything the machine measured — labels, doses, counts, caveats. Sizes are exact values, not a modular scale.

--sans
Switzer 500 / 400
Brand face is Suisse Int’l. Switzer is the sanctioned free substitute. Do not substitute any other family. Load it from a self-hosted woff2, not a CDN.
--mono
Geist Mono
Stands in for Suisse Int’l Mono. Tabular numerals on by default — use it for any figure that sits in a column. Also self-host this one.
Note on this page
This reference loads no webfont, so the specimens below render in the fallback stack rather than Switzer and Geist Mono. That matches production: the source repo ships no font binaries either. Vendor licensed Suisse Int’l woff2 files and declare them in tokens/fonts.css to see the real faces — the tracking and size values on this page are correct regardless.
--text-h1 · clamp(40px, 3.9vw, 64px) · 500 · lh 1.0 · track -.045em
Optimise your Phase II trial before enrolling a single patient.
--text-h2 · clamp(30px, 3.3vw, 46px) · 500 · lh 1.05 · track -.04em · max 22ch
240 protocols. Three worth running.
--text-h3 · 21px · 600 · lh 1.25 · track -.02em
What the model does with your compound
--text-lead · 18px · ink (not ink-70)
Bring your compound, your eligibility rules and your endpoint. We say what the model can and cannot do with it.
--text-sub · 17px · ink-70 · max 38ch
Hero subhead. Sits directly under the h1 and stops early.
--text-body · 16.5px · ink-70 · max 64ch
Paragraph text. Claims are made plainly, then qualified. Where a number appears it carries its condition; where a limit exists it is stated rather than implied.
--text-ui · 14.5px
Nav links, small UI
--text-btn · 15px · 500
Book a call
--text-mono · 10.5px · uppercase · track .14em  /  --text-fignote · italic · track .04em
Simulated cohort · 4,000 virtual patients
Illustrative simulation. Not results from a clinical trial.

Copy rules that affect markup

Three of them need developer attention, because they live in the HTML rather than the CMS.

&nbsp;
Hard space holds “Phase II” together. Never let it break across lines.
.nb
white-space: nowrap. Holds “PD-1 / PD-L1” as one token.
British spelling
optimise, behaviour, centred. Set the linter and the CMS locale to en-GB.
Foundations

Spacing & layout

There is no 4/8 scale. Six named constants and one breakpoint. If a value is not on this page, it should not be in the CSS.

Token
Value
Where it applies
--gutter-x
44px
Section side padding, desktop. Content starts here; rules do not.
--gutter-x-mobile
20px
Below 720px — the system’s only breakpoint.
--section-y
100px / 64px
Vertical section padding; the mobile value applies below 720px.
--nav-h
62px
Nav min-height. The CTA cell is full-bleed to the bar edge.
--btn-pad
14px 22px
Solid-ink button. Gives a 44px hit height at 15px type.
--strap-pad
12px 20px
Figure strap above a VizPanel.
--grid-gap
1px
Cell grids. The gap shows the --rule background beneath.
--radius
0px
Kept as a token so the zero is deliberate and greppable.

The cell grid

This is the layout primitive. A 1px gap over a --rule background, inside a --rule border. Cells share their hairlines, so a sequence reads as one object. Never nest a bordered frame inside a cell.

Cell
Cell
Cell
Cell
Cell
Cell
.cells {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--grid-gap);
  background: var(--rule);
  border: 1px solid var(--rule);
}
.cells > * { background: var(--paper); padding: 24px; }
Foundations

Assets

Three files. Two drawn marks and one photograph. There is no icon set and no illustration library — figures are drawn from data at runtime.

Lockup
assets/causomic-logo.svg · 6.91:1
One drawn object — mark and wordmark do not separate, and nothing is set in live type. Colour it by CSS mask (the Logo component does this), never by editing the SVG fill. Nav 18px, footer clamp(40px, 8vw, 118px).
Mark
assets/causomic-mark.svg · 1:1
Small-size and square-context form: favicon, avatar, app icon. The favicon ships inlined as a data-URI SVG in the document head.
Photograph
assets/scan-array.webp
Greyscale light table of scan film
The only photograph in the system. Greyscale, evidence as texture. No stock photography, no colour imagery, no people. If a page needs an image and this one does not fit, it needs a figure instead.
Foundations

Accessibility

The palette was chosen to clear AA at every combination that ships. Keep it that way: the checks below are the ones a review should run.

Pair
Ratio
Verdict
ink on bone
11.2:1
AAA. All headings and controls.
ink-70 on bone
4.65:1
AA at body size. This is the floor — a third, paler tone would fail.
amber on paper
4.9:1
AA. Do not lighten it for “warmth”.
bone on ink
11.2:1
AAA. Buttons and the footer band.
rule (.16)
non-text
Decorative hairlines only. Control borders use rule-2 (.30) to clear 3:1.
Focus
2px solid ink outline with 2px offset, from base.css. Square, like everything else. Never remove it — if it looks wrong, the offset is wrong.
Focused control
Motion
Transitions are 0.18s ease or shorter, and only on opacity and transform. Hover dims (links .6, buttons .88); press drops 1px. No entrance animation, no parallax, no scroll-triggered reveals. prefers-reduced-motion switches all of it off — test with it on.
Hit targets
44px minimum on touch. Buttons reach it through --btn-pad; chips, checkboxes and switches are smaller than that visually, so give them padded labels rather than growing the box.
Meaning is never colour alone
Amber always coincides with position or a label — rank one, the cutoff row, the kicker that says “Top-ranked design”. A colour-blind reader must still know which one is the one.
Figures need text
Canvas figures are invisible to a screen reader. Every VizPanel needs its strap, legend and caveat as real text — and, where the figure carries the argument, the same numbers in a Table or Stat.
Uppercase microtype
Set mono labels in sentence case in the markup and uppercase them in CSS, so screen readers do not spell them out letter by letter.
Components · core

Core

Five primitives that appear on every surface. Import path components/core/.

Button

core/Button.jsx

The one CTA object, and the only solid-ink element on a light page. Every one of them books the same 30-minute call, so there is never a second button competing with it.

Book a call No arrow
Book a call
tone="bone" — dark band only
{{ r.n }}
{{ r.t }}
{{ r.d }}
<Button href="/book">Book a call</Button>
<Button tone="bone" href="/book">Book a call</Button>

Kicker

core/Kicker.jsx

The labelling voice: section kickers, figure straps, legend keys, ledger numbers. If a piece of text names something rather than saying something, it is a Kicker.

Simulated cohort Top-ranked design
{{ r.n }}
{{ r.t }}
{{ r.d }}
<Kicker>Simulated cohort</Kicker>
<Kicker color="var(--amber)">Top-ranked design</Kicker>

FigNote

core/FigNote.jsx

The caveat under a figure. Mandatory wherever a simulated number appears — a chart or stat that ships without one is a defect, not a style choice.

Illustrative simulation. These are simulation outputs, not results from a clinical trial.
<FigNote>Illustrative simulation.</FigNote>

Ticks

core/Ticks.jsx

A 9px ruler strip. It exists to frame the ink footer band, above and below. Do not scatter it as decoration elsewhere.

Logo

core/Logo.jsx

Renders either SVG through a CSS mask, so one file serves both ink and bone contexts. See Assets for sizes and clear space.

{{ r.n }}
{{ r.t }}
{{ r.d }}
<Logo src="/assets/causomic-logo.svg" height={18} color="var(--ink)" />
<Logo src="/assets/causomic-mark.svg" height={24} ratio={1} />
Components · panels

Panels

Where simulation output is presented. Both panels carry their own qualifier because both are screenshot-ready — they leave the page in decks and emails.

VizPanel

panels/VizPanel.jsx

Paper ground, plus-grid texture, hairline border, mono strap with square legend swatches. The figure itself is yours to draw — canvas or SVG, ink weights for the series, amber for the one.

Illustrative simulation. 240 protocols against 4,000 virtual patients.
{{ r.n }}
{{ r.t }}
{{ r.d }}
<VizPanel
  strap="Simulated trial designs"
  legend={[
    { label: 'Simulated designs', color: 'var(--grid-mid)' },
    { label: 'Top-ranked', color: 'var(--amber)' }
  ]}
>
  <canvas ref={ref} />
</VizPanel>
<FigNote>Illustrative simulation.</FigNote>

Verdict

panels/Verdict.jsx

The readout for a single design. Amber belongs to the winner only — the quiet variant is what an inspected-but-not-winning design looks like, and both states appear in the same view.

{{ r.n }}
{{ r.t }}
{{ r.d }}
<Verdict
  kicker="Top-ranked design"
  title="300 mg q3w"
  sub="PD-L1 ≥ 50% · combination arm"
  stats={[{ label: 'Median PFS', value: '9.4 mo' }]}
  note="Illustrative simulation."
/>
Components · data

Data

Two ways to present an ordered sequence. Both render as one object with shared hairlines — if you find yourself reaching for a list of cards, one of these is the answer.

Ledger

data/Ledger.jsx

Numbered ruled rows. Use it when the items are read in order and can run long — process steps, commitments, what is and is not included.

{{ r.n }}
{{ r.t }}
{{ r.d }}
<Ledger items={[
  { n: '01', label: 'Scope', body: 'Compound, eligibility, endpoint.' },
  { n: '02', label: 'Calibration', body: 'We fit the model to your prior data.' }
]} />

StepFlow

data/StepFlow.jsx

Three or four short steps across the page, arrows straddling the seams. Keep each step to a title and one line — anything longer belongs in a Ledger.

{{ r.n }}
{{ r.t }}
{{ r.d }}
<StepFlow steps={[
  { n: '01', title: 'Define', say: 'Compound, arms, endpoint' },
  { n: '02', title: 'Simulate', say: '240 protocols' },
  { n: '03', title: 'Rank', say: 'Three worth running' }
]} />
Components · display

Display

Product-surface components. The marketing site defines none of these — they extend its vocabulary (square geometry, hairline borders, ink fills, mono labels) to dashboard views.

Chip

display/Chip.jsx

Filters and applied constraints. Selected fills ink; nothing else changes size, so a row of chips does not reflow as the user picks.

NSCLC PD-L1 ≥ 50% q3w Unavailable
{{ r.n }}
{{ r.t }}
{{ r.d }}

Badge

display/Badge.jsx

Square swatch plus mono label. Status, not decoration. Three tones, and the amber one appears at most once per view.

Queued Complete Top-ranked
{{ r.n }}
{{ r.t }}
{{ r.d }}

Tabs

display/Tabs.jsx

A row on a hairline; the active tab gets full ink and a 1px underline. Controlled or uncontrolled — click through the example.

{{ r.n }}
{{ r.t }}
{{ r.d }}

Table

display/Table.jsx

Mono uppercase headers, hairline rows, tabular numerals. Set mono on any column holding a measured value and align: 'right' on numbers that get compared.

{{ r.n }}
{{ r.t }}
{{ r.d }}
<Table
  columns={[
    { key: 'design', label: 'Design', mono: true, strong: true },
    { key: 'pfs', label: 'Median PFS', mono: true, align: 'right' }
  ]}
  rows={rows}
  onRowClick={(row) => select(row)}
/>

Stat

display/Stat.jsx

Mono label over a 27px tabular value. Lay them out in a cell grid, not as separate boxes. accent is the winner’s only.

{{ r.n }}
{{ r.t }}
{{ r.d }}
Components · forms

Forms

All six are live below — type in them, tab through them, toggle them. Controls sit on bone with a rule-2 border, and every label is mono microtype above the field.

Field, Input, Select

forms/Field · Input · Select

Field owns the label and hint; the control goes inside it. Set mono on inputs that hold identifiers, doses or counts — the data voice is a real signal, not styling.

Field
{{ r.n }}
{{ r.t }}
{{ r.d }}
Input
{{ r.n }}
{{ r.t }}
{{ r.d }}
Select
{{ r.n }}
{{ r.t }}
{{ r.d }}
<Field label="Dose" hint="mg per administration">
  <Input mono value={dose} onChange={e => setDose(e.target.value)} />
</Field>

<Field label="Indication">
  <Select options={['NSCLC', 'Melanoma']} value={ind} onChange={onInd} />
</Field>

Checkbox, Radio, Switch

forms/Checkbox · Radio · Switch

Checkbox and switch are square and fill ink when on. The radio ring is the one curve in the system — it earns it, because roundness is what tells a reader only one can be chosen.

Checkbox
Radio
Switch
Checkbox and Switch
{{ r.n }}
{{ r.t }}
{{ r.d }}
Radio
{{ r.n }}
{{ r.t }}
{{ r.d }}
<Checkbox label="Include combination arm" checked={inc} onChange={setInc} />
<Radio options={['Monotherapy', 'Combination arm']} value={arm} onChange={setArm} />
<Switch label="Log scale" checked={log} onChange={setLog} />
Note for implementers
Checkbox, Radio and Switch pass the next value as the first argument to onChange, not an event. Input and Select pass the native event. That asymmetry is deliberate — it matches what each control is actually for — but it catches people once.
Components · shell

Shell

Nav and footer are full-bleed: they ignore the page gutter and run to the viewport edge. Both are shown here inside a bordered box, which is the one place in this document they are framed.

NavBar

shell/NavBar.jsx

Lockup, two links, one solid-ink CTA cell running to the bar edge. Two links is the rule — a third is a third thing to read before the one action. Omit the CTA on the page it points at.

{{ r.n }}
{{ r.t }}
{{ r.d }}
<NavBar
  logoSrc="/assets/causomic-logo.svg"
  links={[{ label: 'The model', href: '/#model' }, { label: 'Design partners', href: '/#partners' }]}
  cta={{ label: 'Book a call', href: '/book' }}
/>

Footer

shell/Footer.jsx

Ticks, then the ink band with the bone lockup, then ticks, then the mono meta row. This band and the closing band on the roles page are the only dark surfaces in the system.

{{ r.n }}
{{ r.t }}
{{ r.d }}