Simple Healthcare
Foundations

The tokens every Simple product is built from

Color, layout, type, elevation, motion, and icons. Values shipped by simple-shared-react, and the rules for using them.

simple-shared-reactTailwind 4motion/reactDesign System Checklist

What is real and what is proposed

Items tagged Adopted describe what simple-shared-react ships today. Draft and Proposed items are recommendations to be confirmed. Where the code disagrees with the recommendation, the gap is called out rather than papered over.
Adopted
Draft
Proposed
Not started
Color

Not only a way to show the brand, but a way to communicate. A palette built with purpose adds meaning to the interface.

Palette

Adopted

Six 11-step scales defined in base.css and exposed as Tailwind utilities. Swatches for every step are on the style guide home page.

FamilyNameRoleAnchor
blueSimple BlueBrand. Primary actions, navigation, headings on dark.blue-600
orangePortland OrangeAccent. Calls to action, caveats, estimates.orange-500
tealKeppelData accent. Charts and visualization.teal-600
grayGrayText, borders, surfaces. Cool-tinted to sit with the blue.gray-700
greenGreenSuccess and verified states. Tailwind default.green-600
redRedErrors and danger. Tailwind default.red-600

See the full scales on the Brand page.

Accessibility

Draft

Accessible pairings between the primary colors, with background and text at an AA contrast ratio or better. Ratios below are computed from the palette hex values.

UsePairSampleContrast
Body text (p)gray-700 on whiteAa4.30:1 Fail
Headings (h1)gray-900 on whiteAa7.61:1 AAA
Small print, h6gray-500 on whiteAa2.56:1 Fail
Links (a)blue-400 on whiteAa2.52:1 Fail
Inline codeblue-700 on gray-100Aa6.16:1 AA
Primary buttonwhite on blue-600Aa4.42:1 Fail
Accent buttonwhite on orange-500Aa3.12:1 Fail
Hero, footerwhite on blue-900Aa10.52:1 AAA
Blue tint panelblue-900 on blue-50Aa9.80:1 AAA
Orange calloutorange-800 on orange-50Aa6.69:1 AA
Teal calloutteal-800 on teal-50Aa7.10:1 AAA
Dark mode bodygray-300 on gray-900Aa4.94:1 AA
Dark mode headingsgray-100 on gray-900Aa6.64:1 AAA

Known gaps

Every pair marked Fail is in use today and needs a token change in base.css, not a component workaround. The cheapest fixes that clear AA: body text gray-700 → gray-800 (5.8:1), links blue-400 → blue-700 (7.1:1), primary button blue-600 → blue-700 (7.1:1), accent button orange-500 → orange-700 (5.5:1). Small print at gray-500 has no passing step below gray-800; either accept it for decorative captions only or move it to gray-800.

Semantic colors

Proposed

Besides the brand colors, roles for disabled states, backgrounds, actions, and high-contrast text. Today components pick raw scale steps; naming the roles is the first step to tokens.

RoleTokensSampleUse for
Brand / primary actionblue-600AaPrimary buttons, active navigation, selected states.
Accentorange-500AaOne accent per screen: a call to action, a highlight, the eyebrow dot.
Informationblue-50 / blue-900AaContext and explanations. Blue callouts and tags.
Successgreen-50 / green-800AaCompleted actions, verified data, in network.
Warning / estimateorange-50 / orange-800AaCaveats, estimates, anything the reader should double-check.
Dangerred-50 / red-700AaDestructive actions, validation errors, out of network.
Neutralgray-100 / gray-700AaDisabled states, pending, secondary information.
Data accentteal-500AaCharts and data visualization only, so it never competes with status colors.

Next step: define these as --color-* aliases in base.css (for example--color-primary: var(--color-blue-600)) so components reference the role, not the step.

Dark mode

Draft

A dark version of the palette so the system can adapt to OS color preferences. The plumbing exists; no deployed product switches it on yet.

How it works

Dark mode is class-based, not media-query based: @custom-variant dark (&:where(.dark, .dark *)). Adding .dark to <html> switches every dark: utility. The element styles and shared components all carry dark variants.

SurfaceLightDark
Pagegray-100gray-900
Cardwhiteblack/50
Inputwhiteblack/50
Dropdown, popoverwhitegray-950
Body textgray-700gray-300
Headingsgray-900gray-100
Bordergray-200gray-800

Open questions: whether to follow prefers-color-scheme automatically, and where a manual toggle lives. Until then, treat dark variants as required on every new component so the switch is a one-line change later.

Guidelines

Proposed

How and when to use the palette, what to keep in mind, and how not to use it.

Don’t
Use color as the only signal. A red price with no label.
Do
Pair color with text or an icon. A red tag that reads "Out of network."
Don’t
Introduce a hex value in a component.
Do
Reach for a palette token. If none fits, add one to base.css first.
Don’t
Put orange and red side by side as status.
Do
Orange means caveat, red means error or danger. Never both on the same element.
Don’t
Tint every panel a different brand color.
Do
White surfaces on a gray-100 page. Tints are for callouts and emphasis.
Layout

Consistent use of a grid and spacing makes the interface easier to scan and the content easier to grasp.

Units

Adopted

A 4-point system. Tailwind's spacing scale is 0.25rem per step, so every spacing, sizing, and gap utility lands on a multiple of 4px.

14px
28px
312px
416px
520px
624px
832px
1040px
1248px
1664px
2080px
2496px

Half steps (0.5, 1.5, 2.5) are allowed for optical adjustments inside a component, never for layout between components.

Grid

Proposed

Every layout sits on a grid that brings order to the interface. Today layouts use CSS grid utilities and a set of container widths; the widths are the grid.

ContainerClassWidthUse for
Reading columnmax-w-2xl672pxProse, leads, anything meant to be read line by line.
Hero textmax-w-3xl768pxCentered page titles and leads.
Page contentmax-w-4xl896pxGuide pages, forms, settings.
Wide contentmax-w-6xl1152pxFooter, data tables, dashboards, search results.
Columns

Twelve columns at lg and up, six at md, four below. Express withgrid-cols-* and col-span-*; there is no fixed column component. Gutters are gap-4 or gap-6, matching the spacing scale.

Margins

Page gutters are px-6 (24px) and px-8 from sm. Containers center with mx-auto. Nothing gets a min-widthwider than the smallest breakpoint.

Breakpoints

Adopted

The screen sizes the grid adapts to. Tailwind defaults, mobile-first: each prefix applies at that width and above.

PrefixMin widthTypical device
sm640pxLarge phones, landscape
md768pxTablets
lg1024pxSmall laptops
xl1280pxDesktops
2xl1536pxWide desktops

Design for the base (no prefix) first. Most layouts need only sm and md; reach for lg and up for multi-column data views.

Spacing

Proposed

Straightforward rules for space between elements, independent of the grid. Use gap on flex and grid parents rather than margins on children.

BetweenClassSize
Icon and its labelgap-1.5 / gap-26–8px
Controls in a row (buttons, tags)gap-312px
Fields in a formgap-416px
Cards in a gridgap-4 / gap-616–24px
Blocks within a sectiongap-832px
Sections on a pagegap-1664px
Page gutterpx-6 sm:px-824px, 32px from sm
Card paddingp-5 / p-620–24px

Border radius

Draft

Corner rounding signals what kind of surface an element is: tight on controls, softer on cards, softest on things that float. Tailwind's radius scale; the assignments are what the shared components use today.

xs2px
sm4px
md6px
lg8px
xl12px
2xl16px
full9999px
StepClassSizeUse for
xsrounded-xs2pxNothing today. Reserved for hairline details like checkbox boxes.
smrounded / rounded-sm4pxControls: buttons, inputs, selects, tags, inline code, callouts. The default.
mdrounded-md6pxCards. The shared Card component.
lgrounded-lg8pxFloating surfaces: modals, toasts, popovers, tabs, skeleton blocks.
xlrounded-xl12pxStyle guide tiles and panels. Not used in the shared components.
2xlrounded-2xl16pxMarketing and hero surfaces only.
fullrounded-full9999pxPills and circles: toggles, avatars, icon buttons, spinners, range thumbs.

In use today

ComponentClassSize
Button, Callout, Dropdown trigger and menu, input, coderounded4px
Tagrounded-sm4px
Cardrounded-md6px
Modal, Toast, Info popover, Tabs, LoadingSkeletonrounded-lg8px
Toggle, Range thumb, Loading spinner, Modal close, Toast iconrounded-full9999px

The gap: Card sits at md (6px), between the controls it holds and the modals that float above it, and Tag uses rounded-sm where everything else at 4px uses bare rounded. Proposed: keep three working sizes, sm for controls, lg for containers, full for pills, and move Card to lg so cards and modals share a radius. Tag stays at 4px; only the class name changes.

Don’t
Round a nested element more than its container.
Do
Step down as you nest: a card at md holds controls at sm. Outer radius ≥ inner radius.
Don’t
Use an arbitrary value like rounded-[10px].
Do
Pick the nearest step. Every step but full sits on the 4px grid or half of it.
Don’t
Round one corner of a control to fit a layout.
Do
Use rounded-t-* or rounded-b-* only for joined groups: tabs, button groups, stacked inputs.
Don’t
Reach for rounded-full on a rectangle.
Do
Full is for elements that are circular or pill-shaped by design. Rectangles cap at lg.
Typography

One of the main ways content is surfaced. Clear hierarchy and contrasting styles make things easier to read and give the brand its presence.

Scale

Adopted

Manrope for interface text, Noto Sans Mono for code and data. Styles are applied per element in base.css, so semantic HTML gets the right type for free.

ElementSizeLine heightWeightTrackingColor
h124px1.25 (30px)700-0.025emgray-900
h220px1.375 (27.5px)600-0.025emgray-800
h316px1.375 (22px)6000gray-800
h414px1.5 (21px)6000gray-700
h512px1.5 (18px)700, uppercase0.025emgray-600
h612px1.5 (18px)500, uppercase0.05emgray-500
p, li14px1.625 (22.75px)4000gray-700
small12px1.5 (18px)4000gray-500
code12px mono1.54000blue-700 on gray-100
label14px1.55000gray-600

Responsiveness

Draft

A scale that adapts to the viewport for a more meaningful hierarchy. Body text and headings are fixed today; only hero titles step up.

Today

Element styles are one size at every width. Page heroes override totext-3xl md:text-5xl. Body copy stays 14px everywhere, which reads well on phones and slightly small on wide desktops.

Proposal

Step h1 and h2 up one size from md(md:text-3xl, md:text-2xl). Leave body text alone; product screens are data-dense and 14px is deliberate.

Grid relation

Adopted

Font sizes and leading should match the grid so text pairs cleanly with icons and controls.

Tailwind sizes pair each font size with a line height on the 4px grid: 12/16, 14/20, 16/24, 20/28, 24/32. Icon sizes (16, 20, 28, 36) are chosen to sit on those line heights, so anIcon size="sm" next to 14px text centers without adjustment. The relaxed leading on p (1.625) breaks the grid deliberately in favor of readability.

Readability

Proposed

Tracking, leading, and line length tuned for reading.

PropertyValueWhy
Line lengthmax-w-2xlAbout 75 characters at 14px Manrope. Prose wider than this is hard to track from line to line.
Leadingleading-relaxed1.625 on body copy. Manrope has a large x-height and needs the room.
Trackingtracking-tightOnly on h1 and h2, where Manrope loosens at larger sizes. Never tighten body text.
Uppercasetracking-wideUppercase labels (h5, h6, tags, eyebrows) always get extra tracking.
Numberstabular-numsProposed for any column of prices so digits align. Manrope supports it.

Performance

Adopted

Custom fonts must load before they display. Sensible fallbacks and fast loading keep text from flashing.

Self-hosted

Both faces load through next/font/google, which downloads them at build time and serves them from the app. No request ever goes to Google at runtime.

Subset

Latin only. Manrope ships as a variable font, so every weight comes in one file.

Fallback

next/font generates a size-adjusted system fallback and usesfont-display: swap, so text renders immediately and reflows minimally.

Guidelines

Proposed

How and when to use the scale, and how not to.

Don’t
Reach for an arbitrary size like text-[15px].
Do
Use the element styles. If a heading needs to be bigger, override with a scale step (text-2xl!).
Don’t
Use bold to fix a hierarchy problem.
Do
Fix the hierarchy: one h1, h2 for sections, h3 within them. Weight follows.
Don’t
Set long paragraphs at full container width.
Do
Cap prose at max-w-2xl, around 75 characters per line at 14px.
Don’t
Use Noto Sans Mono for emphasis or headings.
Do
Mono is for code, identifiers, and tabular numbers only.
Elevation

The relative distance between surfaces on the z-axis. Shadows carry it in light mode; background color carries it in dark mode.

Shadows

Draft

Shadow values for each supported elevation level. Five levels cover every shared component today; the names are new, the values are what the components use.

LevelNameShadowPreviewUse for
0Flatnone
Page sections, tables, list rows. Borders do the separating.
1Raisedshadow-sm
Inputs, panels, anything resting on the page.
2Cardshadow-md shadow-gray-500/20
Cards. The shared Card component.
3Floatingshadow-lg/15
Dropdowns, popovers, tooltips. Anchored to a trigger.
4Overlayshadow-xl
Modals and dialogs. Blocks the page beneath.

Background colors

Draft

Each elevation level has a linked background. In light mode they all resolve to white; in dark mode they replace the shadow.

LevelLightDark
0 Flatgray-100gray-900
1 Raisedwhiteblack/50
2 Cardwhiteblack/50
3 Floatingwhitegray-950
4 Overlaywhitegray-950 (proposed)

Dark mode currently reuses black/50 for levels 1 and 2, so cards and inputs read at the same height. Proposed: lighten level 2 to gray-925 so cards lift off inputs.

Z-index

Proposed

A system of z-index values that controls which elements render on top. Every overlay in the shared library uses z-50 today, so stacking order is decided by DOM order.

LayerProposedToday
Sticky content (table headers, section nav)z-10unset
Dropdowns, popovers, tooltipsz-20z-50
Fixed headerz-30z-50
Modal scrim and dialogz-40z-50
Toastsz-50z-50

The visible bug this fixes: a dropdown opened inside a modal can render behind a toast, and a fixed header can cover a dropdown that opens above its trigger.

Motion

Shared motion values give a coherent experience and keep animation aligned with the brand. All animation uses motion/react; no CSS keyframes or Tailwind animate-* utilities.

Easing

Draft

Standard easing functions for component transitions: standard, accelerated, and decelerated cover the common cases.

NameValueUse for
EntereaseOutThings appearing: dropdowns, popovers, toasts, modals. Fast start, gentle landing.
ExiteaseInThings leaving. Mirrors enter. (Shared components currently reuse easeOut on exit.)
Standardease-in-outProperty changes in place: color, transform, rotation. Tailwind transition default.
LinearlinearContinuous motion only: spinners, progress.

Duration

Draft

A small set of durations keeps transitions consistent across products.

NameValueUse for
Instant100msHover and focus color changes.
Fast200msDropdowns, popovers, chevron rotation, transition-colors. The most common value in the shared components.
Normal300msToasts, modals, anything moving more than a few pixels.
Slow500msPage-level transitions and reveals. Rare.

Enter transitions in use

ComponentInitial stateScrim
Modalopacity 0, scale 0.95, y 20opacity 0 → 1
Dropdownopacity 0, y ∓10 (toward trigger)
Info popoveropacity 0, scale 0.95, y 10
Toastopacity 0, scale 0.95, y 50

The pattern is consistent: fade in, scale from 0.95, and slide a short distance from the direction the element comes from. Exit reverses it. New components should copy it rather than invent a new one.

Accessibility

Not started

Respect the reduced-motion preference by making animations less prominent or removing them.

Not handled today

None of the shared components check prefers-reduced-motion.
Proposal

Wrap each app in <MotionConfig reducedMotion="user"> frommotion/react. It disables transform and layout animations for users who ask for less motion while keeping opacity fades, which is exactly the split the enter transitions above need. Spinners should switch to a static icon viauseReducedMotion().

Iconography

Icons represent functionality or content at a glance. They reach their potential when used meaningfully and consistently.

Style

Draft

The icon family should make visual sense as a whole. The set mixes two sources today.

Filled, 32px box

Most of the set. Solid glyphs drawn with fill="currentColor" on a 32-unit viewBox. This is the house style.

Stroked, 24px box

About fifteen utility glyphs (chevrons, plus, X, search, sort) drawn with a 2px stroke on a 24-unit viewBox. They read lighter next to the filled set.

Decision needed: redraw the stroked glyphs as filled to match, or accept a deliberate split where filled icons represent things and stroked icons represent controls. Either is fine; mixing by accident is not.

Accessibility

Not started

Functional icons need an accessible name that says what they do. Decorative icons need none.

Not handled today

The Icon component takes icon, size, and className. It renders a bare <svg> with no role, aria-label, or aria-hidden, so screen readers either skip it or read nothing useful.
Proposal

Add an optional label prop. With a label, renderrole="img" aria-label="...". Without one, renderaria-hidden="true". An icon-only button then passes its label to the icon or sets its own aria-label.

Naming

Draft

Name icons for what they communicate, not how they look. A play button is Play, not Triangle.

Names are PascalCase keys on the Icons map and mostly follow the rule:Search, Trash, LogOut. A few are named for their shape or source rather than their job and should be aliased: Funnel (filter),Asclepius (medical), Outbound (external link). Keep the old key, add the purpose name, and migrate over time.

Relation with grid

Adopted

Icons draw in a bounding box that plays well with the grid so they pair cleanly with text.

SizeRenderedPairs withPreview
sm16pxInline with 14px text, tags, small buttons
md20pxDefault. Buttons, inputs, list rows
lg28pxSection headers, empty states
xl36pxFeature callouts, hero

The component strips the SVG width and height and sizes by class, so any icon renders at any of the four sizes. All four land on the 4px grid.

Keywords

Not started

Keywords make each icon discoverable for anyone using the system.

There is no icon browser and no keyword metadata. When the set grows past what fits on one screen, add a keywords list per icon and a searchable gallery to this style guide. Until then the components page and the Icons map are the catalog.

Reserved icons

Proposed

Icons for common actions reserved so they are never used for anything else. This makes the interface predictable.

Close or dismiss
X
Delete
Trash
Edit
Pencil
Add or create
Plus
Search
Search
Filter
Funnel
Expand, open a menu
ChevronDown
Context, definition, source
Info
Caveat or warning
TriangleAlert
Success, verified
CircleCheck
Error, failed
CircleX
Opens in a new tab or leaves the site
Outbound

Guidelines

Proposed

How and when to use icons, and how not to.

Don’t
Use an icon alone for an action that is not in the reserved list.
Do
Pair unfamiliar icons with a text label.
Don’t
Mix icon sizes in one row of controls.
Do
One size per context. Buttons in a row share a size.
Don’t
Re-purpose a reserved icon (a Trash for "clear filters").
Do
Reserved icons mean one thing everywhere. Find another icon or use text.
Don’t
Color an icon differently from its label.
Do
Icons inherit currentColor so they always match the text beside them.