- Accordion
- Alert
- Alert Dialog
- Avatar
- Badge
- Button
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Circular Progress
- Collapsible
- Compare Slider
- Dialog
- Dropdown Menu
- Gauge
- Input
- Label
- Marquee
- Navigation Menu
- Number Field
- Pagination
- Popover
- Progress
- Radio Group
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Switch
- Table
- Tabs
- Textarea
- Toggle
- Tooltip
PREDICT uses a small set of colour tokens. Components never use raw colours; they use tokens like bg-primary or text-muted-foreground, which switch automatically between light and dark mode.
How colour works
- Tokens are CSS variables. Each token, such as
--primary, is defined twice inpackages/ui/src/styles/globals.css: once in:rootfor light mode and once in.darkfor dark mode. - Values are OKLCH. OKLCH keeps lightness even across hues, so the purple scale and the status colours step evenly. Hex values on this page are worked out from the OKLCH source for reference.
- Tailwind classes map to tokens.
@theme inlineturns--primaryintobg-primary,text-primary,border-primary,fill-primary, and so on. Add an opacity with a slash, such asbg-primary/10.
Brand palette
Mostly black and white, with Predict Purple (#8504FE) as the one brand colour and lilac as its soft partner. Use purple for the main action and brand moments, not large areas. See Brand Guidelines for proportions and logo colours.
Tokens
Every token, in light and dark mode. Click a token name to copy it.
Surfaces
| Token | Light | Dark |
|---|---|---|
The page. | #FFFFFFoklch(1 0 0) | #000000oklch(0 0 0) |
Cards and panels. | #FFFFFFoklch(1 0 0) | #000000oklch(0 0 0) |
Popovers, menus, and tooltips. | #FFFFFFoklch(1 0 0) | #000000oklch(0 0 0) |
Text
| Token | Light | Dark |
|---|---|---|
Headings and main text. | #000000oklch(0 0 0) | #FFFFFFoklch(1 0 0) |
Descriptions, captions, and hints. | #161616oklch(0.2 0 0) | #BEBEBEoklch(0.8 0 0) |
Text on cards. | #000000oklch(0 0 0) | #FFFFFFoklch(1 0 0) |
Text in popovers. | #000000oklch(0 0 0) | #FFFFFFoklch(1 0 0) |
Brand
| Token | Light | Dark |
|---|---|---|
Predict Purple. The main action and brand moments. | #8504FEoklch(0.536 0.2917 295.68) | #8504FEoklch(0.536 0.2917 295.68) |
Text and icons on primary. | #FFFFFFoklch(1 0 0) | #FFFFFFoklch(1 0 0) |
Lilac. Secondary buttons and soft highlights. | #F4EEFFoklch(0.961 0.0343 295.7) | #28114Doklch(0.253 0.1037 295.7) |
Text on secondary. | #030303oklch(0.1 0 0) | #DEDEDEoklch(0.9 0 0) |
Neutrals
| Token | Light | Dark |
|---|---|---|
Quiet fills, such as skeletons and tracks. | #BEBEBEoklch(0.8 0 0) | #161616oklch(0.2 0 0) |
Hover and selected fills. | #9E9E9Eoklch(0.7 0 0) | #2E2E2Eoklch(0.3 0 0) |
Text on accent. | #2E2E2Eoklch(0.3 0 0) | #9E9E9Eoklch(0.7 0 0) |
Lines and focus
| Token | Light | Dark |
|---|---|---|
Dividers and card edges. The default border colour. | #DEDEDEoklch(0.9 0 0) | #2E2E2Eoklch(0.3 0 0) |
Input borders and fills. | #DEDEDEoklch(0.9 0 0) | #2E2E2Eoklch(0.3 0 0) |
Focus rings. | #9E9E9Eoklch(0.7 0 0) | #484848oklch(0.4 0 0) |
Feedback
| Token | Light | Dark |
|---|---|---|
Delete actions and errors. | #DF2225oklch(0.58 0.22 27) | #DF2225oklch(0.58 0.22 27) |
Sidebar
| Token | Light | Dark |
|---|---|---|
Sidebar background. | #DEDEDEoklch(0.9 0 0) | #030303oklch(0.1 0 0) |
Sidebar text. | #030303oklch(0.1 0 0) | #DEDEDEoklch(0.9 0 0) |
Active item in the sidebar. | #030303oklch(0.1 0 0) | #DEDEDEoklch(0.9 0 0) |
Text on the active item. | #DEDEDEoklch(0.9 0 0) | #030303oklch(0.1 0 0) |
Sidebar hover fill. | #BEBEBEoklch(0.8 0 0) | #161616oklch(0.2 0 0) |
Text on sidebar hover. | #161616oklch(0.2 0 0) | #BEBEBEoklch(0.8 0 0) |
Sidebar dividers. | #DEDEDEoklch(0.9 0 0) | #2E2E2Eoklch(0.3 0 0) |
Focus rings in the sidebar. | #9E9E9Eoklch(0.7 0 0) | #484848oklch(0.4 0 0) |
Pairs
Most surface tokens come with a -foreground partner for the text on top of it. Always use them together:
| Surface | Text on it |
|---|---|
bg-background | text-foreground, text-muted-foreground |
bg-card | text-card-foreground |
bg-popover | text-popover-foreground |
bg-primary | text-primary-foreground |
bg-secondary | text-secondary-foreground |
bg-accent | text-accent-foreground |
bg-sidebar | text-sidebar-foreground |
destructive has no foreground token. Use white text on it, or use it as text and borders on a light tint, such as bg-destructive/10 text-destructive.
Status colours
Five colours show how far a biomarker result is from healthy. They follow the Z-score (how many SDs from the healthy mean), and they are the same in light and dark mode.
These are the thresholds used by Reference Range and the Bio Clock severity scale. A low value on the harmless side of a directional range, such as a low hs-CRP, is not a concern and shows in neutral grey.
- Always pair colour with a word or icon. About one in twelve men has some colour blindness, and green and orange are easy to confuse. Show "High" or "Normal" next to the colour.
- Use the tint pattern for text. On white, the status colours are too light for body text (see the contrast table). Use a tinted badge,
bg-status-mild/15 text-status-mild, or a solid fill with a label beside it. - Keep them for results. Don't use status colours for decoration, brand, or general success and error messages. Use
destructivefor errors.
Chart colours
A sequential purple scale for charts, from chart-1 (lowest) to chart-5 (highest). In dark mode the scale gets darker so the light end doesn't glare.
#000000
#28114D
#431A7D
#6033A9
#8504FE
- Use
chart-5for the main or only series, and lighter steps for comparisons and backgrounds. - The scale shows amount, not category. For results by status, use the status colours.
chart-1is almost the page colour, so give it a border or use it only for backgrounds.
Contrast
WCAG asks for a contrast of 4.5:1 for body text and 3:1 for large text (24px, or 19px bold) and icons. Ratios below are worked out from the token values.
| Pair | Light | Dark |
|---|---|---|
| foreground on background | Aa21.00:1AAA | Aa21.00:1AAA |
| muted-foreground on background | Aa18.10:1AAA | Aa11.24:1AAA |
| primary-foreground on primary | Aa6.09:1AA | Aa6.09:1AA |
| primary on background | Aa6.09:1AA | Aa3.45:1AA large |
| secondary-foreground on secondary | Aa18.14:1AAA | Aa12.17:1AAA |
| foreground on muted | Aa11.24:1AAA | Aa18.10:1AAA |
| accent-foreground on accent | Aa5.10:1AA | Aa5.10:1AA |
| destructive on background | Aa4.78:1AA | Aa4.39:1AA large |
| white on destructive | Aa4.78:1AA | Aa4.78:1AA |
| status-normal on background | Aa2.22:1Fail | Aa9.45:1AAA |
| status-mild on background | Aa1.91:1Fail | Aa10.97:1AAA |
| status-moderate on background | Aa3.76:1AA large | Aa5.58:1AA |
| status-severe on background | Aa2.89:1Fail | Aa7.26:1AAA |
| status-extreme on background | Aa3.82:1AA large | Aa5.50:1AA |
What this means in practice:
foreground,muted-foreground,secondary-foreground, andaccent-foregroundpass for any text size, in both modes.- White on
primarypasses for body text (6.1:1).primaryas text on white also passes; on black it only passes for large text (3.5:1). destructiveas text passes on white (4.8:1) but only for large text on black (4.4:1). In dark mode, pair it with an icon or use it as a fill.- On white, no status colour passes for body text, and
normal,mild, andsevereare below 3:1 even for icons. Use them as fills and tints with a label, as above. On black, all five pass.
Using colours in code
Use token classes in components. Don't hard-code hex values or Tailwind palette colours like text-green-500, which won't follow the theme.
Adding a token
- Add the variable to
:rootand.darkinglobals.css, in OKLCH. - Map it in
@theme inline, such as--color-warning: var(--warning);. - Use it as
bg-warning,text-warning, and so on. - Check its contrast against the surfaces it will sit on, in both modes.