# Digital products — Aleris Brand OS concept bundle

Sources in order; each file follows as it stands in the corpus, frontmatter first.

<!-- source: baseline/BASELINE.md · status: accepted -->

# Baseline — AI Instructions

The Aleris brand is documented in two layers. **Foundation** defines identity and reasoning: colour, typography, voice, imagery, and the two reasoning tools (constant/contextual, emotional modes). **Baseline** is the implementation layer: the token architecture, component rules, and pattern library that apply Foundation across digital products. Read Foundation first; read Baseline for how to execute it.

When Foundation and Baseline conflict, Foundation takes precedence.

Before writing or modifying any UI code in an Aleris product, use only tokens from `aleris-tokens.css`. When uncertain about a decision, flag it as an open question in the code rather than guessing:

```css
/* OPEN: [description of the decision needed] */
```

---

## Token system

All values come from `tokens/aleris-tokens.css` — a three-layer architecture:

- **Primitives** — raw values (colors, spacing, type sizes). Platform-independent.
- **Semantic** — primitives mapped to usage (text colors, surface colors, states). Reference these in product code.
- **Component** — semantic tokens composed into component-level clusters (button, input, card, table, tab-nav).

Machine-readable token data with `usage` and `constraint` fields is in `tokens/baseline-tokens.json`. Consult it for lookup; where the two disagree, `constitutional/tokens-are-canonical.md` § 1 says which one wins and why no document may restate a value from either.

**Never use raw values.** No hex colors, no pixel values for spacing, no raw Tailwind color classes. Everything goes through tokens. This is a rule about product code, and it is not the same as the one above, which governs what a document may say.

**The one documented exception: a page rendered before the token stylesheet can load.** A server-rendered pre-auth surface (login, an error page, a maintenance page) is often served before the built, hashed-filename stylesheet is reachable — it sits behind the gate it is rendering, so it cannot `@import` or link `aleris-tokens.css` and has to inline its colours. The exception exists because a real build's first version of exactly this page used two values that aren't Aleris colours at all (`#d9663d`, `#e6e1da`) — there was nothing here to check against. **The four values a pre-auth page needs, cited from canonical rather than invented for this case:** page background `--surface-page` (sand-100, `#f2ece4`), text `--text-primary` (petrol-500, `#004851`), primary button `--color-orange-600` (`#d14811`), border `--border-default` (gray-100, `#d7d2cb`). Inlining these four is the exception; every other surface still goes through the token file. Pin the values with a test that extracts every hex a pre-auth page uses and asserts each one is one of these four — a build cannot see this rule fail otherwise.

**Serving this file.** The comments in `aleris-tokens.css` are its documentation and they are not optional — every `@usage` and `@constraint` annotation is a rule, and the supersede notes are why a value is what it is. They are also not for browsers. **Strip comments from the stylesheet at build time, keep them in the source a developer opens, and leave one pointer comment in the emitted file saying where the reasoning went.** Stripping is not minifying: remove the comments and the blank runs they leave behind, and nothing else — whitespace crunching, shorthand rewriting and selector merging each change what the browser computes if they are wrong, and the weight here is almost entirely in the prose.

Two reasons, and the second is the one that bites. **Weight:** the file is 50,487 bytes, roughly 34,000 of them comment. Gzipped it is 15.9 KB with the prose and 3.3 KB without, so about four fifths of the compressed download is English sentences — prose has little redundancy to squeeze while a token file is almost nothing but redundancy. On a page that inlines this file that is the largest single saving available, and one consumer measured 50.0 KB of comment in 82.4 KB of inlined stylesheet, on a patient-facing surface, on every load. **Build safety:** a comment body can contain a sequence that closes it early. Browsers recover silently and strict minifiers do not — this has already broken one consumer's production build, in dev-invisible fashion, and the fix at the time was to reword the comment. A build that strips comments before the minifier sees them is not exposed to it at all.

`patientguide/frame/lib/css.js` is a working implementation, including the guard that refuses to emit a stylesheet when an unterminated comment opener survives the strip — that failure mode silently swallows every declaration up to the next closer, which is a page with rules missing and no error anywhere.

---

## Hard rules

They apply to every surface, every product.

1. **Page background: sand, with everything layered on it.** Communicative = `--surface-page` (sand-100, #f2ece4). Instrumental = `--surface-page-instrumental` (sand-50, #faf8f6). The page's own background is always one of these two; a white or coloured section is a layer on top of it, even when it fills the screen. A section takes one of four colours: `--surface-section-white`, `--surface-section-cold` (petrol-100), `--surface-section-warm` (orange-300), or `--surface-section-strong` (petrol-500) for the one strong section on a page. orange-100 is not a section colour – it measures 1.01:1 against sand-100 and the two read as one surface.
2. **Cards and surfaces: white, not sand.** `--surface-card` is white. Sand on cards inverts the hierarchy. Cards stay white on any section; a white card on a white section is separated by its border and shadow.
3. **One primary CTA per screen.** Interactive orange (`--color-orange-600`, via `--button-primary-bg`) marks the primary action on a screen. Do not place two orange buttons in the same view. `--brand-accent` (orange-500) is the brand orange for accent, decoration and print, and no longer carries the primary button – white text on it fails AA.
4. **Status colors for state only.** Error red, warning amber, confirm green, goal colors — never decorative.
5. **Confirm green is an indicator, never a button.** `--color-confirm-500` (#4f866e) marks completion — status dots, badges, chips. It is **not an interactive fill**: white on it is 4.23:1, below the floor at rest, before any interaction. The confirm *button variant* was retired rather than recoloured; a completion action takes the petrol button form plus a required check glyph, so "I'm done" is carried by the glyph and the verb. Keep it distinct from `--color-goal-achieved` (#2e8540), which indicates "target met" on dashboards. **This is the rule of record for confirm; other documents reference it rather than restating it.**
6. **No all caps in content.** Sentence case everywhere. The only exception is abbreviations (AB, MRI) — see `foundation/typography.md`.
7. **Animate what does not move the layout, unless the user moved it.** Transform and opacity animate freely. Colour, background colour, shadow and stroke may transition to show a change of state. Size may animate only when the user caused it and it is contained — opening an accordion, expanding a panel — one element at a time, within `--duration-moderate`. Nothing changes size on its own: not on load, not on scroll, not behind the user's back. `prefers-reduced-motion: reduce` disables all of it.
8. **No animation in email.** All Aleris email layouts are static.
9. **14px is the floor.** `--font-size-xs` (14px) is the smallest text in the system. Nothing goes below it.
10. **No font-weight 400.** Museo Sans has no 400 weight. Using it causes browser synthesis. Use `--font-weight-regular` (500) or `--font-weight-bold` (700).

---

## Surface temperature

Baseline applies `foundation/constant-contextual.md` to surfaces through two modes: **communicative** and **instrumental**. Mode is determined by the situation, never the user's role — a patient managing their treatment plan in MyAleris is in instrumental mode; a staff member reading an internal newsletter is in communicative mode.

**Communicative** — Aleris tells, guides, explains, welcomes. Patient-facing content, marketing, service introductions. ~~onboarding~~ **Removed from this list.** Onboarding names when a flow happens, not what the person is doing in it, so it carries no mode of its own. Read the situation: a welcome sequence that introduces a service is communicative; setting up a treatment plan is instrumental from its first screen. The same holds for any word that names a stage rather than a situation — activation, renewal, offboarding.
- Page background: `--surface-page` (sand-100)
- Cards mandatory for content structure
- Generous spacing (`--spacing-lg` to `--spacing-3xl`)
- Full type scale, typography has room to breathe
- ~~Content spans 6–8 of 12 grid columns~~ **Superseded:** one reading column at every width, `--grid-column-communicative` — 640px up to a 768 window, growing a quarter of every further pixel to 768px from 1280 up. Decided by looking at three candidates rendered side by side (`baseline/conformance/layout/index.html`). The column holds 88 characters of 18px body at 768 and 105 at 1280, past the 45–75 guideline; that was seen and chosen, and the token says so. "6–8 columns" was never a measure proxy — 600–800px is 82–109 characters.
- Max-width: `--grid-max-width-communicative` (1200px)

**Instrumental** — The user works, navigates, configures, monitors. Tools, admin, dashboards, documentation, booking flows (a booking flow is instrumental throughout).
- Page background: `--surface-page-instrumental` (sand-50)
- Cards optional — content can live directly on the surface
- Tighter spacing. ~~compressed type range~~ **Superseded — surface temperature does not change type size.** Type size is the reader's to set and the browser already gives them that control — every size in the scale is a rem value, so a reader who raises their root font size raises the whole page, and the 14px floor is a floor at default settings rather than a cap. Instrumental density comes from layout utilisation and information hierarchy, which is what `aleris-design-governance.md` § Scanning vs. attending already says: *"not from spacing compression"*. "Compressed type range" told a builder to shrink type and named no token to do it with, because no mode-scoped type token has ever existed — the five mode-scoped tokens are all grid or background. Asserted in `tokens.test.ts`.
- Content can span all 12 columns. **Tiles and card grids:** fluid, no breakpoints — `repeat(auto-fit, minmax(var(--grid-tile-min-instrumental), 1fr))`, 14rem tiles, so the column count falls out of the width: one to 390, two at 640, three at 768, four at 1024, five from 1280. Four tiles leave an empty fifth slot from 1280 up; rendered, seen, chosen over the stepped 12→6→1 and 12→1 candidates. Tables scroll inside their own container at every width.
- Max-width: `--grid-max-width-instrumental` (1440px or fluid)

Mode does not shift between steps within a single flow.

**Sticky elements.** Three things may stay in view while the page scrolls: the site header, the portal side nav, and an in-page list of a long page's sections, which also marks the section being read. Nothing else sticks to the page. Inside a table's own scroll area its header row may stick, because it takes no space from the page. Tab navigation sticks only when it is the page's list of sections.

---

## Component shapes

Radius by functional role:

- **Cards**: `--radius-l` (16px) — from computed styles measured live on aleris.se/.no/.dk (every card ships at 16px, not Baseline's former 4px). Derived inward: nested elements take the card's own radius at zero gap, floored at 0. Cards only, for now — panels, modals and tables were never measured live and stay `--radius-s`.
- **General containers** (panels, modals, tables): `--radius-s` (4px)
- **Interactive elements** (buttons, inputs, dropdowns): `--radius-m` (8px)
- **Indicators** (badges, tags, avatars): `--radius-full` (100px)
- No full-pill buttons exist in Aleris interfaces.
- **No generated side accent.** **Never** mark an element as active, selected or important with a coloured bar, border or shadow along one of its sides. **Because** it is a default of AI design tools, not an Aleris choice. It makes the work read as generated rather than designed, and it carries state in a thin strip of colour alone. **Instead** mark the state with weight and fill: bold petrol text on a petrol-100 fill for an active navigation item, a 1px petrol-500 border all the way round for a selected card. A quotation may keep a thin, flat, neutral rule along its side (`--border-default`): that is a typographic convention, not a state marker. A rounded, shadowed or curved side treatment is not.
- **Flush top-of-card media** (a hero image with zero clearance from the card's top edge): carries no radius of its own. The card clips its contents to its own rounded shape (`--card-media-overflow: hidden`), which renders the image's top corners matching the card and its bottom edge square, for free — nothing to keep in sync, and the inversion the 4px card tier used to produce against an 8px image (see the corner-radius reasoning below) cannot recur. Scoped to cards that actually contain flush media, not every card, so a plain card stays free to let content overhang its edge without a second decision. Does not apply to an inset image with clearance on all sides, which keeps `--image-radius-default` (8px) unchanged.
- The radius set is `0 / l / m / s / full`.

**The corner-radius reasoning.** Baseline assigned radius by role — container, interactive, indicator — which is easy to teach but ignores the geometric convention that nested rounded rectangles must be concentric: an outer radius has to be at least its inner radius plus the gap between them, or the two curves aren't parallel and the corner reads as an error. At Baseline's 24px card padding, nothing sits close enough to a card's edge for this to show. It stopped being invisible the moment an image sits flush against the card. Rather than resolve that one pair in isolation, the question was reframed as one problem — what should the default corner state be — and answered from what production had already settled empirically rather than from a rule invented for the occasion. A four-model comparison (`baseline/conformance/radius/index.html`, unpublished) made the candidates visible side by side before the decision, per the same design-by-looking approach as `baseline/buttons/index.html`.

---

## Elevation

Shadows come from the ladder `--shadow-e0` to `--shadow-e3`, through the elevation tokens: a card rests at `--elevation-card` (`--shadow-e1`), a dropdown at `--elevation-dropdown` (`--shadow-e2`), a modal at `--elevation-modal` (`--shadow-e3`). **One featured element may rest one step up.** On a communicative page, a single featured element, such as the text card of a hero, may rest at `--shadow-e2` instead of `--shadow-e1`. One per page; a page where several things rest one step up has no featured element.

---

## Lists

Items in a list are separated by space from the spacing scale. A list of six or more items may also carry a horizontal divider between items (`--border-default`); a shorter list never does. This holds for lists across Aleris design. Tables are not lists and keep their own rules in § Tables.

---

## Typography

See `foundation/typography.md` for the typography system and principles. Baseline implementation:

- Reference typography tokens from `aleris-tokens.css` (`--type-body-*`, `--type-lead-*`, `--type-h1-*`, etc.) rather than specifying pixel sizes.
- Font weights available: `--font-weight-regular` (500) and `--font-weight-bold` (700). Do not use font-weight 400 — Museo Sans has no 400 weight and browser synthesis distorts the letterforms.
- **The licensed Museo Sans has no tabular figures, at either weight.** Setting `font-variant-numeric: tabular-nums` against it changes nothing and says so nowhere — measured in a browser against Arial as a control, digit advance widths are identical with and without the property, and the ten digits spread 8.72px at weight 500 and 7.50px at 700 where Arial spreads 0. At a 16px value size that puts 8.2px between "111,1" and "000,0". Right-alignment is what aligns a numeric column; the property is a silent no-op. *(Measured by `sundviktlakemedel`, 2026-08-25.)*
- **The licensed Museo Sans has no arrow glyphs, and no check mark.** U+2192 →, U+2190 ←, U+2264 ≤, U+2265 ≥ and U+2713 ✓ are absent from both licensed weights, so any of them in running text falls back to another face silently — a different stroke weight and baseline beside the words it sits in. Present and safe: en and em dash, bullet, ×, …, °. Use an icon for an arrow or a check, never the character. *(Measured 2026-09-05 by reading the character maps of `MuseoSans_500-webfont.woff2` and `_700`: 229 and 228 mapped codepoints. First noticed by `patientguide`, 2026-08-13.)*
- `--type-lead-*` is used once per section, after the heading.

---

## Spacing

Modular scale, 1.5 ratio, 4px grid. Use tokens — never fabricate values.

| Token | Value | Typical use |
|-------|-------|-------------|
| `--spacing-3xs` | 4px | Minimum gap, label-to-field |
| `--spacing-2xs` | 8px | Tight inline gaps |
| `--spacing-xs` | 12px | Related elements, input padding-y |
| `--spacing-sm` | 16px | Component padding |
| `--spacing-md` | 24px | Section spacing, card padding |
| `--spacing-lg` | 36px | Between components |
| `--spacing-xl` | 48px | Between sections |
| `--spacing-2xl` | 72px | Major section breaks |
| `--spacing-3xl` | 96px | Page-level (communicative only) |

---

## Buttons

Six variants, each with a job:

- **Primary** (orange): "Do this" — main action, booking, submission
- **Secondary** (petrol): "Go here" — navigation, exploration
- **Outline**: Less prominent alternative
- **Ghost**: Tertiary, minimal visual weight
- ~~**Confirm** (green): "I'm done" — completion, sign-off. Only for: Klarmarkera, Godkänn, Signera, Markera som klar. Not for generic "OK" in dialogs.~~
- **Small**: Compact variant for tight spaces

> **Superseded.** The confirm button variant listed above is retired; hard rule 5 is the rule of record for confirm.

All buttons use `--radius-m` (8px). No full-pill buttons.

**Contrast is the constraint.** No interaction state may take a component's text below the AA floor against its own fill.

**Darkening is the convention for interaction states.** If a component already sits at the palette's highest contrast, darkening will lower contrast. That is permitted so long as it stays above the floor.

> **Superseded.** The two rules above replace *"Hover lightens, never darkens"*: the floor is the rule, the direction is the convention.

**Primary on a petrol surface inverts.** orange-600 is specified for light surfaces and must never be placed on petrol. On a petrol ground the primary action becomes white fill with petrol-500 text (10.27:1) – `--button-primary-inverse-*`.

**No primary on the warm section.** orange-600 measures 2.82:1 against `--surface-section-warm` (orange-300), below the 3:1 a fill needs against the surface it sits on. An action on a warm section takes the secondary form. On `--surface-section-cold` the primary holds at 3.40:1.

**Filled buttons carry a bevel.** At rest a filled button has a soft shadow down and to the left and a thin highlight along the top edge, facing the light; when pressed it sinks into an inward shade. This is a top-edge highlight from the light source, not a side accent, so it does not conflict with § Component shapes. Tokens: `--button-{variant}-shadow` at rest, `--button-press-shadow` when pressed, together with `--state-press-translate-y`.

**Hover on a clickable card.** A card that is a link moves from the card shadow to the next step up (`--shadow-e2`) and rises 2px on hover; an arrow in it moves 4px towards where it leads. Transform and shadow only, `--duration-fast`, ease-out, off under reduced motion.

**States.**

- **Hover is a ring, not a fill.** One petrol ring specified for six variants on two surfaces produced two invisible focus rings – petrol-500 on a petrol-500 fill (secondary) and petrol-500 on the petrol-500 page (primary-inverse), both 1.0:1. Rather than add another fill step to fix hover and a second mechanism to fix focus, the two merged: **hover draws the outer ring only, focus draws both.** No `--button-*-hover-bg` token exists any more, and none should be added – a hover fill that changes colour is what the "hover lightens/darkens" rule used to constrain, and the constraint no longer applies because there is no fill to constrain.
- **Focus is a dual ring: 2px white inner, 2px petrol-500 outer**, both always drawn. Whichever ground a button sits on, one ring contrasts – the inner against the button's own fill, the outer against the page. `primary-inverse` swaps the two, because it sits on petrol rather than sand: `--button-primary-inverse-focus-ring` (outer position) is white, `--button-primary-inverse-focus-ring-inner` (inner position) is petrol.
- **Active (press) is per-variant, not a shared tint.** `--state-active` (sand-500) was retired rather than repointed – it was never a button fill, and reads 1.93:1 under white. Primary and primary-inverse reuse what used to be their hover fill (orange-700, petrol-100); secondary and confirm take a new step, petrol-700 (13.88:1); outline and ghost take petrol-100. A supplementary, unasserted second channel – `--state-press-shade`, a neutral inset, plus a 1px translate dropped under `prefers-reduced-motion` – is deliberately not petrol-tinted, for the same reason a single focus ring failed: a petrol inset on a petrol fill is invisible.
- **Buttons do not disable.** A disabled button dissolves into the page (gray-100 on sand-100 is 1.28:1) and offers no explanation for why it is unavailable. Patient-facing: prefer explain-on-click – clicking reveals the prerequisite rather than the control simply not responding. Instrumental: a transparent, gray-500-edged treatment is acceptable where the workflow context is self-evident to the user. `--state-disabled-*` remains for inputs, which have their own disabled semantics – see § Forms.
- **Buttons must survive text-zoom without causing horizontal scroll.** From a real conformance failure: a normal-length label at 200% text size on a 320px viewport measured wider than its container and pushed the page into horizontal scroll — an EN 301 549 11.7 failure, not a cosmetic one, since 11.7 requires layout to follow the platform's text-size setting. `max-width: 100%` and `white-space: normal` (label wraps to a second line rather than the button overflowing) are required on the button primitive, not optional per-instance styling. **The same failure recurs at the row level**: a flex row of buttons or other controls (a role switch, a toolbar) needs `flex-wrap` for the identical reason — a non-wrapping row overflowed a 375px viewport before the fix, independently of any single button's own width.

See `baseline/buttons/index.html` for every variant against every state, measured live from these tokens, and `tokens/tokens.test.ts` for the fitness check. One documented failure remains: ghost's press fill (petrol-100, reused from outline) has no border to carry its boundary, at 1.13:1 against sand-100 – tracked as `KNOWN_BOUNDARY_FAILURES['ghost/active']`. Ghost has no instances in `app/` or `components/`; fixing it means giving ghost a border, which is a design call about what distinguishes it from outline, not made here.

---

## Forms

- Labels above fields.
- Mark optional fields "(valfritt)", not required fields.
- Error text: "what happened + what to do" below the field, replacing helper text.
- `aria-invalid="true"` + `aria-describedby` for error linking.
- Focus: `focus-visible` with petrol ring, 2px offset. Never `focus`.
- Disabled: use sparingly. Patient-facing: prefer explain-on-click. Instrumental: acceptable when workflow context is self-evident.
- Read-only: looks like content, not a greyed-out input.
- **Error text sits on a white card or an instrumental ground, never directly on the communicative ground.** `--input-error-color` (error-500) measures 5.03:1 on white (clears), 4.74:1 on sand-50 (clears) and 4.28:1 on sand-100 (fails). `--input-error-size` is `--font-size-xs` at regular weight, so the failing case is the ordinary one — a field error at the smallest size in the system. A form on a communicative page goes in a card, which is already the rule there (§ Surface temperature: *"Cards mandatory for content structure"*). Enforced by `tokens.test.ts` § *"error text clears AA on every ground this rule permits"*, which reads the permitted grounds out of this bullet rather than holding its own list.

---

## Status messages

**Icon plus label, on error and on success both.** A status message carries its meaning in an icon and in words, never in the fill or text colour alone – red and green are the pair that collapses under red-green colour vision deficiency, which is roughly 8% of men. Success already required a checkmark in the goal-status rules; error now carries the same requirement, so the two are distinguishable with colour removed.

This is the domain implementation of the constitutional rule that meaning is never carried by colour alone. The rule itself lives once, at the constitutional layer – this section says what it means for a status message, it does not restate the rule.

---

## Tables

Two types: **read tables** (display data) and **work tables** (interactive).

- Three density levels: compact (36px), default (48px), comfortable (64px)
- **Note to builders:** user-selectable density that persists as a preference is the expectation for instrumental work tables, not a hard requirement every table must ship on day one. Build toward it; a first cut that ships with only the default density is a known gap, not a violation.
- Headers quieter than data (smaller, sentence case)
- Identifier in column one, always
- Numbers right-aligned, text left-aligned — right-alignment is what actually aligns a numeric column here, for the reason under Typography above
- Actions near the identifier, not at the far right

---

## Motion

Four durations, two curves.

| Token | Value | Use |
|-------|-------|-----|
| `--duration-instant` | 100ms | Focus rings, checkbox toggles |
| `--duration-fast` | 200ms | Buttons, hover, tooltips |
| `--duration-moderate` | 350ms | Modals, accordions, sidebars |
| `--duration-slow` | 500ms | Page transitions (sparingly) |

- `--ease-out`: elements arriving
- `--ease-in-out`: elements moving
- Never use `ease-in` alone — slow start reads as unresponsive.

Loading: skeleton shimmer, not spinners. < 2s: shimmer only. > 2s: shimmer + progress. > 10s: navigate away + notify.

---

## Anti-patterns (healthcare-specific)

If you experience a conflict between design requirements and anti-patterns, please note it and leave a comment.

1. **Consent asymmetry** — consent and decline must be visually equal. Same size, same prominence, same steps.
2. **Cost burial** — surface pricing before any data entry or emotional investment.
3. **Urgency fabrication** — no countdown timers or scarcity messaging unless clinically real and verifiable.
4. **Obstacle withdrawal** — anything booked online can be cancelled online with equal ease.
5. **Informational hiding** — risks and qualifying information inline at point of decision. Never progressively disclosed.
6. **Data maximalism** — collect only what the current service requires. Optional = labeled "(valfritt)".
7. **Default escalation** — no pre-selection on choices with financial, clinical, or privacy consequences.
8. **Emotional exploitation** — no shame, guilt, or fear to drive action. Motivate through clarity.
9. **Exit penalty** — no degraded experience for choosing a less profitable path.
10. **Notification coercion** — notifications for clinical relevance only. Marketing is separate with distinct opt-in.

**Smell test:** Is it easier to accept than to decline? Does the flow hide information that would change the decision? Does it require something not necessary for care?

---

## Accessibility

- WCAG 2.1 AA minimum
- 14px font floor (enforced by token scale)
- Meaning is never carried by colour alone – the rule is constitutional, see `constitutional/accessibility-is-foundational.md`. In this system it means pairing colour with icon, shape, or text, and it means error and success must be distinguishable from each other with colour removed (see Status messages above)
- **44px minimum touch/click target** (`--button-min-height-touch`, `--button-padding-y-touch`) on patient-facing and touch contexts. **Relaxed for dense instrumental surfaces on desktop pointer input:** 24×24 CSS px is the floor there (WCAG 2.1 AA 2.5.8 Target Size Minimum), not 44×44 (AAA 2.5.5 Target Size Enhanced, which 44px implements) — `--button-min-height-pointer`. A dense work table or admin toolbar built for mouse/trackpad use may use the smaller target; anything a patient touches, or anything that could be used on a touchscreen, keeps 44px.
- `focus-visible` for keyboard navigation, never `focus`. **The ring differs by control, and one rule for all of them cannot hold** — a single ring colour collides with something on six button variants across two surfaces (petrol-500 on a petrol-500 fill is 1.0:1). Inputs keep a single petrol ring, 2px offset – see § Forms. Buttons use a dual ring, inner and outer, so one half always contrasts regardless of ground – see § Buttons
- `prefers-reduced-motion: reduce` disables all animation
- `aria-invalid` + `aria-describedby` for form errors

---

## File reference

**Foundation (read first)**

| File | Purpose |
|------|---------|
| `foundation/index.md` | Brand in Brief — entry point to all Foundation pages |
| `foundation/colour.md` | Colour |
| `foundation/typography.md` | Typography |
| `foundation/voice.md` | Voice — "den nära experten" |
| `foundation/constant-contextual.md` | Constant and contextual — a reasoning tool |
| `foundation/emotional-modes.md` | Emotional modes — a reasoning tool |
| `foundation/imagery.md` | Imagery |

**Baseline (implementation)**

| File | Purpose |
|------|---------|
| `data-products/tokens/aleris-tokens.css` | Source of truth — all token values |
| `data-products/tokens/baseline-tokens.json` | Generated — token lookup with usage/constraint |
| `data-products/tokens/aleris-tailwind.css` | Optional Tailwind v4 theme bridge — `bg-aleris-petrol-500`, `p-aleris-md`, and so on, each compiling straight to a token. Generated from the token file, holds no value of its own, adds a namespace and switches Tailwind's own colour palette off; import it after Tailwind |
| `schemas/design-token.md` | The token record's shape, the layer architecture, framework/Figma integration. Not yet in the `agent-baseline` package |
| `principles/design-tokens.md` | Why each token value is what it is. Corpus-only — reasoning, not a build constraint |
| `governance/aleris-anti-patterns.md` | 10 healthcare-specific behavioural guardrails |
| `governance/aleris-design-governance.md` | Design decisions with certainty levels |
| `governance/aleris-privacy-jtbd-analysis.md` | Privacy as interaction design |
| `reference/aleris-progressive-enhancement.md` | Foundational design principle |
| `reference/aleris-grids-tables-dataviz.md` | Grid, table, and dataviz specs |
| `reference/aleris-baseline-animation.md` | Motion system (implements constant-contextual and emotional-modes) |
| `reference/aleris-baseline-images.md` | Image formats, aspect ratios (implements imagery) |
| `voice/` | Digital voice patterns, behaviour notes (implements voice) |
| Icon path data | **3,772 canonical FA Pro Regular SVGs are already extracted and reachable — no FA kit token required.** Fetch a glyph at `https://brand.dev.aleris.ai/icon-picker/svgs/regular/<name>.svg`, with the name index at `public/icon-picker/metadata/icons.json`. Only *re-extraction* on an FA upgrade needs the kit. Lives at `public/icon-picker/svgs/regular/` in the Brand OS repo, regenerable via `scripts/build-icon-metadata.py`. Any name may be used except the motifs `principles/iconography.md` § What we never use excludes |
| `logo/` | The logo files, the four ratified constants (clear space, primary variant, minimum height, prohibitions) and the sender rule. Read `logo/README.md` before placing the logo anywhere. New in `agent-baseline` v0.7 |

---

*Baseline — maintained by Torfinn Almers, Head of Design, Aleris Group.*

> *What changed on this page, and when, is in `workspace/status/changelog.md`. This page carries the rules and the reasoning behind them; the dates live there. A struck-through value beside its replacement is not a change note — it is the rule telling you what it no longer is.*


<!-- source: principles/motion.md · status: accepted -->

---
name: Animation & Motion
type: principles/reference
layer: principle
status: accepted
depends_on: [aleris-tokens.css, foundation/constant-contextual]
propagates_to: [aleris-tokens.css, aleris-design-governance.md, aleris-design-system-open-questions.md, index.html]
---

# Animation & Motion — Aleris Baseline

## The Position

Animation in Aleris Baseline has one job: confirm that the system is listening. Every movement responds to a user action or communicates a system state. Decorative animation — motion that exists because it looks good — does not belong.

This is the same principle as Tufte's data-ink ratio applied to motion: every animation that doesn't carry information or provide feedback is visual noise.

Aleris interfaces should feel alive and modern, not static or dated. But "alive" means responsive to the user, not performing for them. The difference is whether the motion happens because the user did something, or because the designer thought it would look nice.

---

## Timing: Four durations

Effective micro-interactions last between 200-500ms. Below 150ms they're imperceptible. Above 500ms they feel sluggish.

```css
--duration-instant: 100ms;   /* State changes that should feel immediate: focus rings, colour shifts */
--duration-fast: 200ms;      /* Buttons, hover, toggles, small feedback */
--duration-moderate: 350ms;  /* Modals, expanding panels, dropdown menus */
--duration-slow: 500ms;      /* Page transitions, large layout changes, major state shifts */
```

### When to Use Each

**Instant (100ms):** Focus ring appearing, input border colour change, checkbox toggle. These are state indicators, not transitions — the user should perceive the change as immediate.

**Fast (200ms):** Button press response, hover state, tooltip appear/disappear, badge update. The user should feel the system reacting to their action without any perceived delay.

**Moderate (350ms):** Modal opening/closing, accordion expanding, sidebar sliding in, card flipping to detail view. These involve spatial change that the user needs to track — the animation shows where something came from and where it went.

**Slow (500ms):** Full page transitions, major layout reconfigurations, onboarding step changes. Used sparingly. Most Aleris interfaces should never need this duration — it's reserved for moments where the spatial transition is large enough that the user would lose context without animation.

---

## Easing: Two Curves

Linear animation (constant speed) feels mechanical. Natural motion accelerates and decelerates. Two easing curves cover all Baseline needs:

```css
--ease-out: cubic-bezier(0.0, 0.0, 0.2, 1);      /* Elements arriving: fast start, gentle stop */
--ease-in-out: cubic-bezier(0.4, 0.0, 0.2, 1);    /* Elements moving: gentle start and stop */
```

**ease-out** for things that appear: modals opening, dropdowns showing, elements fading in, tooltips arriving. The element enters quickly and settles into place — it feels responsive.

**ease-in-out** for things that move: panels sliding, elements repositioning, layout shifts. The motion is smooth at both ends — it feels natural.

Never use `ease-in` alone (slow start, fast end) for UI — it makes elements feel like they're accelerating away from the user, which reads as unresponsive.

---

## What Gets Animated

### Buttons
Hover no longer changes the background colour on any variant – it draws the outer focus ring. Press still changes the fill (`--button-*-active-bg`) plus a slight translate, dropped under `prefers-reduced-motion`, per `--state-press-translate-y`.

```css
.button {
  transition: box-shadow var(--duration-fast) var(--ease-out),
              background-color var(--duration-fast) var(--ease-out),
              transform var(--duration-instant) var(--ease-out);
}
.button:hover {
  box-shadow: 0 0 0 3px var(--state-focus-ring);
}
.button:focus-visible {
  box-shadow: 0 0 0 2px var(--state-focus-ring-inner), 0 0 0 4px var(--state-focus-ring);
}
.button:active {
  background-color: var(--button-primary-active-bg);
  transform: translateY(var(--state-press-translate-y));
}
```

### Clickable Cards
A card that is a link moves from the card shadow to the next step up (`--shadow-e2`) and rises 2px on hover; an arrow in it moves 4px towards where it leads. Transform and shadow only, `--duration-fast`, ease-out, off under reduced motion.

### Loading States
Skeleton screens with shimmer effect, not spinners. Skeleton communicates "content is coming in this shape" rather than "something is loading somewhere." Shimmer adds a subtle animated gradient that signals activity without demanding attention.

For processes under 2 seconds: skeleton shimmer only.
For processes over 2 seconds: skeleton shimmer + progress indication (percentage or contextual message).
For processes over 10 seconds: consider whether the UI should navigate away and notify when complete.

### View Transitions
Fade (opacity 0→1) or gentle slide for transitions between views within the same context. Duration: moderate (350ms). The user should understand "I moved from here to there" without the transition being a performance.

Page-to-page navigation: fade. Within-page state change (tab switch, filter change): content area crossfade. Panel opening (detail view, sidebar): slide from the relevant direction.

### Expanding/Collapsing
Accordions, detail panels, progressive disclosure — animate height and opacity together. The user needs to see content unfolding, not teleporting in. Duration: moderate (350ms).

Collapse is slightly faster than expand (300ms vs 350ms). Closing feels more decisive; opening feels more gradual. This asymmetry is subtle but contributes to the feeling that the interface responds naturally.

### Feedback: Success and Error
A check icon that draws itself (stroke-dashoffset animation) on form submission success. An input border that transitions to error colour with a single, subtle horizontal shake (2-3px, 2 cycles). These are momentary, purposeful, and then the interface is still again.

### Focus States
Focus rings appear instantly (100ms). They don't animate in — they're state indicators, not transitions. The ring uses `outline` or `box-shadow` with the focus token colour, and it appears the moment the element receives focus.

---

## What Does NOT Get Animated

**Scroll-triggered reveals.** Content that fades in as you scroll down is a cliché that slows the experience and irritates returning users. Show content immediately.

**Page load entrances.** Animated entrances on initial page load consume render time and delay Largest Contentful Paint. The page should appear complete, not perform an arrival sequence.

**Decorative loops.** Rotating icons, pulsing elements, waving hands — anything that moves without responding to an action. Exception: a loading shimmer or spinner during an active wait state.

**Large layout reflows.** Don't animate grid-column changes, responsive breakpoint transitions, or major structural shifts. These are discrete states, not continuous transitions.

**Anything behind the user's back.** If the user isn't looking at the element (it's below the fold, in a background tab, or outside the viewport), don't animate it. Motion that happens off-screen is wasted computation.

---

## Accessibility: prefers-reduced-motion

Non-negotiable. All animations must respect the user's motion preference:

```css
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
```

This is not optional. Users with vestibular disorders (vertigo, motion sickness, migraine triggers) enable this setting for medical reasons. In a healthcare product, respecting it is doubly important — Aleris's users may disproportionately include people with these conditions.

When reduced motion is active, state changes still happen — they just happen instantly instead of transitioning. The user sees the result without the journey.

---

## Performance: What You Animate Matters

### GPU-friendly Properties (animate freely)
- `transform` (translate, scale, rotate)
- `opacity`

These are composited on the GPU and don't trigger layout recalculation. They're effectively free in performance terms.

### State Properties (transition to show a change)
- `color`, `background-color`
- `box-shadow`
- `stroke`, `stroke-dashoffset`

These repaint but do not move the layout. They may transition to show a change of state — a button's hover ring, a card's shadow deepening on hover, a check mark drawing itself. The card's 2px rise that goes with it is a transform, and animates freely.

### Layout Properties (only when the user caused it, and contained)
- `width`, `height`
- `margin`, `padding`
- `top`, `left`, `right`, `bottom` (non-transform positioning)
- `border-width`
- `font-size`

These trigger layout reflow. They may animate only when the user caused the change and it is contained — opening an accordion, expanding a panel — one element at a time, within `--duration-moderate`. Nothing changes size on its own: not on load, not on scroll, not behind the user's back. The costs this guards against are narrow and real: many elements resizing at once, a resize near the top of a long page, content moving under the pointer, and size changes the user did not cause, which count against layout stability where a shift within half a second of the user's own input does not. To animate to a content's natural height, use a grid row from `0fr` to `1fr`.

The rule, as stated in `baseline/BASELINE.md` hard rule 7: **Animate what does not move the layout, unless the user moved it.** Transform and opacity animate freely. Colour, background colour, shadow and stroke may transition to show a change of state. Size may animate only when the user caused it and it is contained — opening an accordion, expanding a panel — one element at a time, within `--duration-moderate`. Nothing changes size on its own: not on load, not on scroll, not behind the user's back. `prefers-reduced-motion: reduce` disables all of it.

### CSS over JavaScript

CSS transitions and `@keyframes` are more performant than JavaScript animation libraries for standard UI interactions. Reserve JS-based animation (GSAP, Framer Motion, Motion One) for complex, state-dependent sequences that CSS can't express.

For most Baseline components, CSS transitions are sufficient:

```css
.element {
  transition: opacity var(--duration-fast) var(--ease-out),
              transform var(--duration-fast) var(--ease-out);
}
```

---

## Surface Temperature and Motion

Communicative surfaces can afford slightly longer, more expressive transitions. The rhythm is slower, the user is browsing, and a 350ms modal opening feels appropriate.

Instrumental surfaces keep motion tight. Fast (200ms) is the default for everything. The user is working, switching between tasks, and any animation longer than necessary is friction. Modals and panels may use moderate (350ms), but buttons, hovers, and state changes stay at fast or instant.

The tokens are the same. The application differs by context.

---

## Motion Tokens Summary

```css
/* Duration */
--duration-instant: 100ms;
--duration-fast: 200ms;
--duration-moderate: 350ms;
--duration-slow: 500ms;

/* Easing */
--ease-out: cubic-bezier(0.0, 0.0, 0.2, 1);
--ease-in-out: cubic-bezier(0.4, 0.0, 0.2, 1);
```

These six tokens, combined with hard rule 7 — animate what does not move the layout, unless the user moved it — cover every animation Aleris Baseline needs. The constraint is the feature.

---

## Design Decision Record

**Decision:** Animation serves feedback and state communication only, not decoration.
**Why:** Aleris's brand is "den nära experten" — calm, precise, professional. Motion that decorates competes with that positioning. Motion that responds to the user reinforces it. The interface should feel responsive and alive, not performative.

**Decision:** Four duration levels, two easing curves. No more.
**Why:** A constrained motion vocabulary creates consistency across products and teams. When every developer picks their own timing and easing, the interface feels incoherent — like an orchestra where everyone plays at different tempos. Six tokens (four durations, two easings) are enough for every interaction pattern.

**Decision:** prefers-reduced-motion support is mandatory, not recommended.
**Why:** Aleris builds healthcare products. A significant portion of users — patients with vestibular conditions, post-surgical patients, users on medication that affects balance — will have this preference active. Ignoring it is both an accessibility failure and a clinical insensitivity.

**Decision:** CSS transitions preferred over JavaScript animation libraries.
**Why:** CSS transitions are GPU-accelerated, declarative, and require no runtime library. They cover 95% of Baseline's animation needs. JavaScript libraries add bundle size, runtime cost, and framework dependency. Use them only when CSS genuinely cannot express the required behaviour.

**Decision:** Skeleton shimmer screens preferred over spinners for loading states.
**Why:** Skeletons communicate the shape and position of incoming content, giving the user a preview of the layout. Spinners communicate only "wait." Skeletons reduce perceived loading time and prevent layout shift when content arrives. They also align with progressive enhancement: the structure is there before the content.


<!-- source: principles/time-and-duration.md · status: accepted -->

---
title: Time and duration
layer: principle
status: accepted
owner: Head of Design
scope: every Aleris surface that states a time, a date, a duration or a wait — patient-facing and instrumental, digital and printed
version: v0.1
depends_on:
  - constitutional/voice-is-the-present-expert
  - constitutional/accessibility-is-foundational
  - principles/constant-and-contextual
propagates_to:
  - baseline/governance/time-display.md
updated: 2026-09-17
lang: en
---

# Time and duration

> Every surface that names a time makes two claims the writer did not intend to make: how well we know it, and how much it matters. "Your result is ready in 31 days" and "in about a month" are not two phrasings of one fact — they are two different promises, and a reader who counts can find one of them wrong. This page is the reasoning behind those choices. The formats, ladders and thresholds that follow from it are in `baseline/governance/time-display.md`; this page is why they are what they are.

---

## 1. What will the reader do with this time?

### Precision shown equals precision needed, never precision available.

A system always knows the instant. It knows the appointment was created at 09:41:17 and that the referral is 19 days old. Almost none of that is what the reader came for. Someone deciding whether to leave the house needs "in 20 minutes". Someone deciding whether to worry needs to know whether a wait is normal. Someone auditing a record needs the instant, to the second, with the time zone attached.

Showing everything the system knows is not thoroughness; it is handing the reader the work of deciding what matters. The Present Expert has already done that work.

**Test:** What decision or action does this number serve? Show the resolution that decision needs.

**Anti-pattern:** The database timestamp. A precise instant printed because it was available, in a place where no reader acts on the seconds. Symptom: a patient-facing surface showing "2026-09-13 09:41:17".

**Implication:** Choose the unit from the reader's decision, not from the stored value.

---

## 2. Is "now" still true when this is read?

### Relative time is a rendering, never a value.

"For två dagar sedan" is only correct if the moment of rendering and the moment of reading are the same, on the same clock. That holds on a live screen. It fails everywhere else: a cached page, an email, an SMS read the next morning, a PDF, a printout, a screenshot in a support ticket, a letter. In those places relative time is not imprecise — it is false.

This makes relative time a property of the display surface, never of the content. The value that is stored, sent, logged and printed is always the absolute instant; a surface may choose to render it relatively when the surface is live.

**Test:** If this is read tomorrow, or printed, is it still true?

**Anti-pattern:** Relative time outside a live surface. Symptom: "i morgon" in a letter, "för 3 timmar sedan" in an email, a countdown in a PDF.

**Implication:** Store and send absolute. Render relative only where the render and the read are the same moment, and only over a recoverable absolute value.

---

## 3. What does this granularity claim about our certainty?

### Granularity is a claim about how well we know, and readers hear it whether or not we meant it.

People read a fine-grained unit as a statement that the speaker knows the thing precisely, and a coarse one as a hedge. This is not a matter of taste; it is measurable. The same estimate stated as "31 days" produced a confidence interval around 20 days wide, and as "1 month" around 25 days wide (Zhang & Schwarz, 2012). "1 år" and "12 månader" are the same duration and different promises.

The same finding carries the warning. The effect only exists for a source the reader considers reliable. A precise number that turns out wrong does not merely miss once — it removes the instrument for everything else we say afterwards, including the numbers we did know.

**Test:** Does the granularity we are showing match the certainty we actually have?

**Anti-pattern:** False precision. A number stated to the day when the underlying estimate is accurate to a fortnight. Symptom: an estimate that has to be revised in the next message.

**Implication:** Pick the unit from what we know, not from what sounds authoritative.

---

## 4. Do we control the thing we are being precise about?

### Precision is a commitment. Only commit where we can keep it.

Where Aleris controls the thing — the appointment slot, the callback window, when a result is released, opening hours — precision is a promise we can keep, and vagueness reads as not having our own house in order. Be as precise as the reader can act on.

Where we do not control it — the position in a queue, a recovery time, when a referral is processed, a prognosis — precision is a promise we will break. Here the answer is not to be vaguer. It is to move the precision onto the uncertainty: say the typical case and say the tail. Medicine has already made this move for prognosis, from a single median to best case, typical case and worst case with their proportions attached (the Kiely scenario method). The striking finding is which failure dominates in practice: shown the full distribution, most clinicians will communicate it, but very few do so unprompted. The default error is collapsing to one number.

"Oftast 2–4 veckor, för några längre" is *more* specific than "några veckor", not less, and it commits to nothing we cannot keep.

**Test:** If this turns out wrong, will the reader experience a broken promise or a missed estimate? If the first, we were too precise about the value and not precise enough about the spread.

**Anti-pattern:** The lone point estimate for something we do not control. Symptom: a single number for a wait, with no typical range and no tail, that customer service later has to walk back.

**Implication:** Where we control it, state the value. Where we do not, state the distribution.

---

## 5. What does this granularity say about how much this matters?

### Granularity also signals importance, so it has to be consistent to mean anything.

Alongside the certainty claim rides a second one: a fine unit tells the reader to care at that resolution. The two can point in opposite directions — we can be entirely certain about something trivial — so a rule that only handles certainty will still misdirect attention. Over-precision on a routine notice quietly elevates it; under-precision where precision is expected reads as carelessness, and in a clinical setting that is usually the more expensive of the two.

Two limits follow. Granularity only carries meaning against a baseline: if every surface is stamped to the minute, the minute says nothing and the signal is spent. And it is a weak signal next to placement, typography and repetition — it should not be asked to establish importance, only to avoid contradicting importance established elsewhere. The failure readers actually notice is the mismatch: a critical result stamped "för en stund sedan", a routine notice stamped to the second.

**Test:** Does the resolution here match the weight this item has everywhere else on the surface?

**Anti-pattern:** Granularity drift. A surface where the unit varies item by item for no reason the reader can see, or where two comparable items use different units ("3 veckor" on one card, "21 dagar" on the next).

**Implication:** Set a default granularity per surface and deviate deliberately. One granularity within any set the reader compares.

---

## The gate

One question decides the unit in most real cases:

**Could the reader check this against a calendar and find us wrong?**

If they can, do not round into a coarser unit. Six days is "6 dagar", not "1 vecka" — the reader counts, finds six, and now discounts every other number on the page. A coarser unit is honest at an exact multiple, or with an explicit hedge ("ungefär tre veckor"), and nowhere else.

---

## Origin

**Harvest provenance:** Research pass and dialogue, 2026-09-11 to 2026-09-13 (session research document `tidsvisning-research.md`, prep). The attention-management framing — that over-precision wrongly elevates a subject while under-precision reads as unprofessional, asymmetrically so in a clinical setting — is Torfinn's and is the reason §5 exists as its own principle rather than a note under §3. No Aleris surface audit has been run yet; that is the first Open item.

**External lineage:** Zhang & Schwarz (2012), *How and Why 1 Year Differs from 365 Days*, Journal of Consumer Research 39(2) — the granularity/certainty finding in §3, including the expertise and trustworthiness conditions. Lewis & Oyserman (2015), *When Does the Future Begin?*, Psychological Science — fine-grained units and action on distant goals. Pandelaere, Briers & Lembregts (2011), *The Unit Effect*, JCR 38(2) — perceived magnitude. Krifka (2007), *Approximate Interpretations of Number Words* — round numbers read on a coarser scale, the linguistic half of §3 and the gate. The scenario method in §4 from the cancer-prognosis communication literature (Kiely et al.; see "The median isn't the message", 2022). Convention sources for the formats, not for the reasoning: GitLab Pajamas (absolute by default), AWS Cloudscape (relative by default) — two careful systems that contradict each other, which is the evidence that the format question is convention rather than settled.

---

## Open

1. **The relevance inference in §5 is sound as a mechanism and untested in interfaces.** The pragmatics literature supports that readers draw conclusions from over-specification; none of it measures the effect in a UI, and no study was found comparing "om 4 veckor" with "om 28 dagar" in a care context. Choosing to act on it anyway costs little; treating its magnitude as known would be wrong. A small test on a waiting-time surface would close it.
2. **Whether the weekday step in the past-event ladder earns its place in Swedish.** The ladder in `baseline/governance/time-display.md` shows a weekday name for 2–6 days back, on the reasoning that a reader can place a weekday on a calendar without counting. Whether Swedish readers prefer the date immediately is unknown. Closed by a preference test, or by dropping the step if it complicates implementation more than it helps.
3. **How this interacts with clinically owned wording.** Care instructions that state intervals ("ta tabletten var åttonde timme") are clinical substance, not form, and sit outside the voice scope. Where the boundary runs — an appointment interval in a treatment plan, say — is not decided here.

---

## Related

- [[constitutional/voice-is-the-present-expert]] — the stance this reasons from; a time stated from the organisation's perspective fails there first
- [[constitutional/accessibility-is-foundational]] — the floors the markup rules in Baseline serve
- [[constant-and-contextual]] — why the ladders differ between communicative and instrumental surfaces
- [[baseline/governance/time-display]] — the formats, ladders and thresholds this page governs
- [[patterns/chat-response-simple]] — the existing rule that a time or date in a chat answer is written out in plain text


<!-- source: principles/grids-tables-dataviz.md · status: accepted -->

---
name: Grids, Tables & Data Visualization
type: principles/reference
layer: principle
status: accepted
depends_on: [aleris-tokens.css, foundation/constant-contextual]
propagates_to: [aleris-tokens.css, aleris-design-governance.md, aleris-design-system-open-questions.md, index.html]
---

# Grids, Tables & Data Visualization — Aleris Design Tokens

## Part 1: Layout Grid

### The Grid

12-column grid. Gutters use spacing tokens. Max-width capped per surface mode.

```
Columns:    12
Gutter:     var(--spacing-md)  — 24px
Margin:     var(--spacing-sm)  — 16px (mobile), var(--spacing-md) — 24px (desktop)
Max-width:  1200px (communicative), 1440px or fluid (instrumental)
```

### Why 12 Columns

12 divides cleanly into halves, thirds, quarters, and sixths. It covers every common layout pattern without custom math. This is settled consensus across the industry — the value is in not overthinking it.

### Grid and Surface Temperature

Communicative surfaces set running text in one reading column that grows with the window (below), with generous margins, creating the whitespace and reading rhythm that defines Aleris's warm register. Max-width is capped to maintain comfortable line lengths.

Instrumental surfaces use the grid fully: content can span all 12 columns, sidebars take 2–3 columns, and the layout maximizes usable screen area. Max-width is fluid or set at a higher breakpoint.

The grid is the same structure in both modes. The difference is how much of it is used.

### Grid Tokens

```css
--grid-columns: 12;
--grid-gutter: var(--spacing-md);
--grid-margin-mobile: var(--spacing-sm);
--grid-margin-desktop: var(--spacing-md);
--grid-max-width-communicative: 1200px;
--grid-max-width-instrumental: 1440px;
```

### The column ladders

Chosen by looking: three candidates per surface rendered at 320 / 390 / 640 / 768 / 1024 / 1280 / 1440 from the real token file and the licensed face, each frame reporting its own column width and characters per line.

**Communicative — one column that grows.** `--grid-column-communicative: clamp(40rem, 40rem + (100vw − 48rem) / 4, 48rem)`. 640px up to a 768 window, then a quarter of every further pixel, held at 768 from 1280 up; anchors are `--breakpoint-sm` and `--breakpoint-md`, asserted. It holds 83 / 88 / 96 / 105 characters of 18px body at 640 / 768 / 1024 / 1280 — past the 45–75 guideline from 640 up. A 548px measure-capped column and a two-column split were rendered beside it and not chosen. This supersedes "content spans 6–8 of 12 columns" above.

**Instrumental — fluid, no breakpoints.** `repeat(auto-fit, minmax(var(--grid-tile-min-instrumental), 1fr))`, 14rem tiles: one to 390, two at 640, three at 768, four at 1024, five from 1280. Four tiles leave an empty fifth slot from 1280 up; seen and chosen over 12→6→1 and 12→1. Tables scroll inside their own container at every width.

### Responsive Behaviour

Use CSS Grid with `fr` units and container queries. The grid adapts to its container, not just the viewport — this matters when the same component appears in different contexts (full-width page vs sidebar panel).

Breakpoints follow the spacing scale logic but are practical, not mathematical:

```css
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;
```

---

## Part 2: Data Tables

### Two Types of Tables

**Read tables** display data. The user scans, compares, and perhaps filters or sorts, but doesn't edit. Patient lists, appointment overviews, compliance reports.

**Work tables** (data grids) are interactive workspaces. The user edits inline, takes actions per row, reorders, configures columns. Scheduling tools, checklist administration, CMS interfaces.

Both use the same token foundation but have different interaction patterns. Read tables are simpler; work tables are more complex. Don't design one pattern and stretch it to cover both.

### Table Tokens

```css
/* Row density — three levels, user-selectable in instrumental surfaces */
--table-row-height-compact: 36px;
--table-row-height-default: 48px;
--table-row-height-comfortable: 64px;

/* Cell spacing */
--table-cell-padding-x: var(--spacing-sm);    /* 16px */
--table-cell-padding-y: var(--spacing-2xs);   /* 8px */

/* Header */
--table-header-bg: var(--surface-subtle-cold);
--table-header-text: var(--text-primary);
--table-header-weight: var(--font-weight-bold);
--table-header-size: var(--font-size-xs);      /* 14px — smaller than body */
--table-header-transform: none;                /* sentence case — aligns with the no-all-caps rule */

/* Body */
--table-body-size: var(--font-size-sm);        /* 16px — one step below body */
--table-body-color: var(--text-primary);

/* Row states */
--table-row-hover-bg: var(--surface-subtle-warm);
--table-row-selected-bg: var(--color-petrol-100);
--table-row-border: var(--border-default);

/* Zebra striping — optional, off by default */
--table-row-stripe-bg: var(--color-sand-100);
```

### Table Design Principles

**Identifier in the first column, always.** Whatever identifies the row (patient name, clinic name, document title) goes in column one and stays fixed during horizontal scroll. This is both a usability principle and a privacy principle — in clinical contexts, the identifier column is the one that carries patient safety weight.

**Numbers right-aligned, text left-aligned.** Decimal points and digit columns should line up vertically for scanning. Text reads left to right. Currency and percentages align right. Dates can go either way depending on whether they're scanned as text or compared as values.

**Density is a user choice, not a design choice.** Offer compact, default, and comfortable row heights in instrumental surfaces. Different tasks need different density: scanning a long list calls for compact; reviewing individual records calls for comfortable. Don't decide for the user — let them decide based on their current task.

**Headers are quieter than data.** Table headers use smaller type in sentence case — they're navigational landmarks, not content. The data in the cells is what the user came for. Headers serve the data, not the other way around.

**Avoid full-width tables.** Stretching a table to fill available width creates meaningless whitespace between columns that makes data harder to scan. Let the table take the width it needs, cap it, and let the page breathe around it.

**Actions near the identifier, not at the far right.** If a table has row actions (edit, view, delete), place them next to the identifying column — not at the opposite edge of the table where the user has to scan the full width to find them.

**Visual summary above, not instead of.** When a table has aggregate meaning (totals, averages, trends), show a summary view above the table. This lets users see the pattern before the details. Sparklines embedded in table cells can serve this purpose for trends within the data.

### Table and Privacy

Patient tables in clinical contexts need the patient identification component from the privacy JTBD analysis (Job 2.1): name + one secondary identifier, consistently placed, full ID expandable. In non-clinical tables (compliance dashboards, aggregate reports), patient identity is replaced by pseudonymised references or removed entirely.

Export from any table follows the tiered export dialog pattern: anonymous aggregate = no friction, individual data = confirmation + logging, bulk = formal confirmation + purpose.

---

## Part 3: Data Visualization

### Existing Foundation

The token file already provides a solid chart palette (12 colours in cool/warm series with paired assignment for comparisons) and a goal-status palette (traffic-light with accessibility requirement for icon/shape pairing). What follows are principles for how to use them.

### Five Principles for Aleris Data Visualization

These are inspired by Edward Tufte's work but translated into practical Aleris guidance — not academic rules.

#### 1. Show the data, then stop

Every visual element in a chart should either represent data or directly help interpret it. If it does neither, remove it. This applies to gridlines (lighten or reduce), borders (usually unnecessary), background fills (almost always unnecessary), decorative icons, and 3D effects (never).

In practice: start with just the data marks (bars, lines, points). Add elements one at a time only when the chart becomes harder to read without them. The moment you stop needing to add things, you're done.

This is the same principle as Aleris's "calm, professional aesthetic" applied to data: the chart should feel considered and restrained, not decorated.

#### 2. Let comparison happen naturally

The most useful thing a chart can do is make comparison easy. The design should support this without requiring the user to work for it.

**Common baseline.** Bars start from the same line. Lines share the same Y-axis when comparing similar measures. If scales differ too much, use separate charts placed next to each other rather than dual Y-axes (dual axes create confusion more often than they create insight).

**Small multiples over complex single charts.** When comparing the same measure across 5+ categories (clinics, departments, time periods), repeat a simple chart for each category rather than cramming all series into one chart. Small multiples are easier to scan, less prone to visual clutter, and don't require a colour-coded legend that the user has to memorise.

**Consistent colour assignment.** If "Aleris Stockholm" is chart-1 (teal) in one chart, it should be teal in every chart on the same page. The paired assignment tokens (chart-pair-1-a/b etc.) exist for this — use them for any view where two series are compared.

#### 3. Use colour with intention, not just variety

The chart palette has 12 colours. That doesn't mean a chart should use 12 colours. Most effective charts use 2–4 colours. Using more than 6 in a single chart is a signal that the visualisation should be restructured (filtered, split into small multiples, or simplified).

**Sequential:** Use chart-1 through chart-N in order for categorical data with no inherent ranking. The ordering puts the highest-contrast colours first.

**Paired:** Use the paired tokens (chart-pair-1-a / chart-pair-1-b) when comparing two related series (before/after, target/actual, this year/last year). The cool/warm pairing creates intuitive distinction.

**Highlight + mute:** For "one thing vs everything else" comparisons, use a single strong colour for the focus item and desaturated grey (chart-11-warm-grey) for all others. This focuses attention without a legend.

**In charts, the second carrier is pattern, label, or position.** This is the chart implementation of the constitutional rule that meaning is never carried by colour alone – the rule itself lives once, at `constitutional/accessibility-is-foundational.md`, and is not restated here. What it means for a chart: series must be distinguishable from each other with colour removed, which the goal-status palette already handles through icon/shape pairing. Beyond accessibility, it is good information design.

#### 4. Annotate the important, not the obvious

Labels and annotations should mark what matters, not what the reader can already see. A bar chart doesn't need a value label on every bar — label the highest, lowest, or target-crossing bar. A trend line doesn't need a data point marker at every interval — mark the inflection points.

**Reference lines** (targets, averages, thresholds) are among the most useful annotations. A line showing "national average" or "target" transforms a chart from "here is data" into "here is data in context." Use a dashed or lighter stroke to distinguish reference lines from data.

**Direct labelling over legends.** Where possible, label data series directly on the chart (at the end of a line, inside a bar, near a data point) rather than asking the user to match colours to a separate legend. This eliminates the back-and-forth eye movement between chart and legend.

#### 5. Respect the reader's time

A chart should be interpretable within a few seconds of looking at it. If it requires extended study, it's either too complex, poorly structured, or showing the wrong thing.

**Title as insight, not description.** "Waiting times by clinic" is a description. "Stockholm clinic exceeds waiting time target in Q3" is an insight. The title should tell the reader what the chart reveals, not what it contains. (There are contexts where neutral titles are appropriate — research presentations, regulatory reports. For operational dashboards, insight titles are better.)

**Progressive detail.** Show the overview first (the chart). Let the user drill into details on interaction (hover for exact values, click for underlying data, filter to zoom in). This mirrors the surface temperature approach: the default is clean and scannable; complexity is available on demand.

**Sparklines in context.** Small inline trend indicators (sparklines) embedded in tables or cards are often more useful than standalone charts. They show "is this going up or down?" without requiring the user to navigate to a separate view. The chart palette's first colour (chart-1, teal) works well as a single-colour sparkline.

### What to Avoid

**Pie charts for comparison.** Humans are poor at comparing angles and areas. Use horizontal bar charts instead — they're always more readable and take less space.

**Dual Y-axes.** They imply a relationship between two measures that may not exist, and they're easy to misread. Use separate charts placed side by side.

**3D effects.** They distort data perception and add visual complexity without information value. Always flat.

**Excessive animation.** Subtle transitions when data changes are fine; animated entrances and bouncing bars are noise. Motion should serve comprehension (showing what changed), not decoration.

**Dashboard overload.** A dashboard with 12 charts is not a dashboard — it's a report page. A dashboard should show 3–5 things that matter right now, with clear paths to explore further. If everything is highlighted, nothing is.

### Chart Typography

Charts use the token system but at reduced sizes:

```
Axis labels:     var(--font-size-xs) / var(--font-weight-regular)
Axis titles:     var(--font-size-xs) / var(--font-weight-bold)
Data labels:     var(--font-size-xs) / var(--font-weight-regular)
Chart title:     var(--font-size-lg) or var(--font-size-md) / var(--font-weight-bold)
Legend:           var(--font-size-xs) / var(--font-weight-regular)
```

All chart text uses the primary font family. Axis labels and legends use --text-secondary for a quieter tone — the data visuals should dominate, not the labels.

### Chart Spacing

Charts breathe with the same spacing tokens as everything else:

```
Padding inside chart container:     var(--spacing-md)
Space between chart and title:      var(--spacing-sm)
Space between chart and legend:     var(--spacing-xs)
Space between small multiples:      var(--spacing-md)
Minimum bar width:                  var(--spacing-2xs)
```

---

## Design Decision Record

**Decision:** 12-column grid with spacing-token gutters, surface temperature controlling utilisation (not structure).
**Why:** The grid is infrastructure, not expression. The same 12 columns serve both communicative and instrumental surfaces. The difference is how many columns content occupies — not how the grid is built.

**Decision:** Three density levels for table rows, selectable by the user in instrumental surfaces.
**Why:** Different tasks need different density. Scanning a list of 50 patients calls for compact; reviewing one patient's checklist history calls for comfortable. Designers shouldn't pre-decide — the user's current task determines the right density.

**Decision:** Table headers use smaller type than body, in sentence case.
**Why:** Headers are navigational landmarks. Data is content. The visual hierarchy should reflect this: data is primary, headers are secondary. This inverts the common pattern of bold/large headers dominating small data — and it's what actually helps scanning.

**Decision:** Data visualisation follows five practical principles rather than a rigid rule set.
**Why:** Rigid rules ("never use more than 5 colours", "always label directly") break in specific contexts. Principles ("use colour with intention", "respect the reader's time") guide judgment across contexts. The design system should develop judgment, not enforce compliance.

**Decision:** Pie charts are not recommended; horizontal bar charts are preferred for categorical comparison.
**Why:** Humans compare lengths more accurately than angles or areas. This is well-established in perception research (Cleveland & McGill, 1984, refined by Heer & Bostock, 2010). Horizontal bars also handle long category labels better and work better on mobile.

---

## Key References

- Edward Tufte, *The Visual Display of Quantitative Information* (1983) — foundational principles: data-ink ratio, small multiples, graphical integrity
- Edward Tufte, *Envisioning Information* (1990) — layering, colour use, small multiples in depth
- Steve Few, *Information Dashboard Design* (2006) and *Show Me the Numbers* (2004) — pragmatic dashboard and chart design for business contexts
- Tamara Munzner, *Visualization Analysis and Design* (2014) — academic framework for choosing visualisation type based on data type and task
- Andrew Coyle, "Design Better Data Tables" — the definitive practitioner reference for table UI patterns
- Stéphanie Walter, "Enterprise UX: Essential Resources for Complex Data Tables" — curated resource collection
- Cleveland & McGill, "Graphical Perception" (1984) — empirical basis for why position/length beats angle/area
- JMIR 2026 scoping review of healthcare dashboard design practices — current state of the field


<!-- source: principles/design-tokens.md · status: accepted -->

---
title: Design tokens — why each value is what it is
layer: principle
status: accepted
owner: Head of Design
scope: Aleris Group
version: v0.1
depends_on: [data-products/tokens/aleris-tokens.css]
related: [schemas/design-token]
updated: 2026-08-10
---

# Design tokens — why each value is what it is

Moved 2026-08-10 from `baseline/reference/aleris-design-tokens.md` and `baseline/reference/baseline-token-architecture.md` (Phase B step 3, board cards 46/48), where these records sat mixed in among 158 rows of value restatement — the exact material that drifted for four months and took two partial corrections before the 2026-08-07/10 sweep closed it. The values themselves never belonged here; the reasoning does. Each entry below leads with the question it answers, per the convention this layer already documents in `principles/_index.md` — the question is what transfers to a situation this file never named; the declarative claim underneath keeps the position unambiguous.

---

## 1. Why petrol, orange and sand instead of clinical blue and white?

### The palette is warm-shifted on purpose, because "institutional" is the thing it has to avoid.

Petrol reads as trustworthy and medical without being cold corporate blue. Orange is warm and approachable without being alarming red. Sand feels calm and natural — not clinical white. Even the grays carry warmth (#585044 rather than #555555). This is deliberate: Aleris's brand essence ("den nära experten") requires interfaces that feel human and present, not institutional.

Turquoise was in the palette historically and was removed — it competed with petrol and created visual noise.

## 2. Should a surface's tone follow the user's role, or the situation?

### The situation. Surface temperature is a property of a whole flow, never a person or a single step.

A patient managing their treatment plan is in instrumental mode. A staff member reading a newsletter is in communicative mode. The same person moves between both depending on what they're doing, not who they are.

Communicative surfaces (guides, marketing, onboarding) use sand tones, generous spacing, softer hierarchy. Instrumental surfaces (dashboards, scheduling, admin) use lighter/neutral tones, tighter spacing, higher contrast. Temperature doesn't shift between steps within a single flow — a booking flow is instrumental throughout, even where it carries informational content, because mixing modes mid-flow reads as incoherent.

## 3. Is confirm a colour decision, or an interaction decision?

### It turned out to be an interaction decision wearing a colour question's clothes.

`--color-confirm-500` (#4f866e) marks completion — set by Foundation F1 (status colour Bekräftelse), decided 2026-06-12. That value stands and remains distinct from `--color-goal-achieved` (#2e8540): confirm marks "I'm done," goal-achieved marks "target met" on a dashboard.

What changed (2026-07-31): this record used to call confirm-500 "the interactive confirm colour" for actions like *Klarmarkera*, *Godkänn*, *Signera*. White text on it is 4.23:1 — it failed the AA floor at rest, as an interactive fill, and always had. Rather than search for a darker green that would pass, the confirm *button variant* was retired outright, and completion moved to the petrol button form plus a required check glyph. Both confirm-500 and goal-achieved are now indicators, distinguished by what they indicate rather than by interactive-versus-not. Rule of record: `BASELINE.md` hard rule 5. Info status stays undefined on purpose — informational messages use petrol, since info is neutral brand communication (`principles/colour.md`).

## 4. Should dashboard status colours match the brand palette?

### No — traffic-light convention outranks brand consistency here, because the user's prior expectation is the whole point.

Goal-tracking colours (`--goal-achieved`, `--goal-borderline`, `--goal-missed`, `--goal-no-data`) are deliberately standard green/yellow/red rather than brand-derived. In a goal-tracking context, users expect exactly these associations; remapping them to petrol/orange/sand would trade a real, useful convention for brand purity, for no benefit.

The "no data" blue (#007bc7) is a distinct hue from both petrol and the chart blues on purpose — it has to signal "neutral information," not "disabled" (which gray would imply) or "error" (which red would imply). About 8% of men have red-green colour deficiency, so the icon/shape pairing on every goal state isn't optional decoration — it's this system's implementation of the one constitutional rule that meaning is never carried by colour alone (`constitutional/accessibility-is-foundational.md`, `normative: true`).

## 5. Should chart colours borrow the brand palette?

### No — a chart category that looks like a brand-orange button invites the user to click it.

The data-visualization palette is deliberately distinct from the brand palette: chart teal (#0f9081) is greener and brighter than petrol (#004851); chart terracotta (#d77a61) is warmer and more muted than orange (#f58c61). On an instrumental surface holding both interactive UI and data charts, a user needs to instantly tell "I can click this" (brand orange) from "this is a data category" (chart terracotta) — sharing a palette would erase that distinction.

The palette provides six light/dark pairs, enough for most healthcare data-visualization needs, and a canonical numbered order (01–12) so "category 1" always means teal across different dashboards and reports — consistency that builds pattern recognition over repeated use.

## 6. When does a new product get Museo Sans, and when does it get Arial?

### Museo Sans is the typeface; Arial is the fallback where it cannot load.

A new product uses Museo Sans, as `constitutional/typography-is-museo-sans.md` rules for everything Aleris makes. The webfont licence covers every website Aleris owns or controls, with no count of domains (`how-to/install-the-tokens.md` § Step 1 — Pull tokens into the project), so a new web product needs no licence of its own. Arial is the fallback: in the font stack while Museo Sans loads, and wherever it cannot load at all, such as a surface hosted outside Aleris.

## 7. How aggressive should the type scale's jumps feel?

### Calm, not dramatic — which is why perfect fifth won over golden ratio.

Golden ratio (1.618) and its square root (1.272) were both evaluated. Golden ratio produces dramatic jumps, unsuitable for healthcare UI where hierarchy should read clear but calm. √φ gives more steps, but its 27.2% intervals land on sizes (23px, 29px, 37px, 47px) that don't resonate with spacing or line-height values elsewhere in the system.

Perfect fifth (1.5) produces visible hierarchy without drama — deliberate, not theatrical, matching Aleris's character of precision and warmth over impact. Using the same ratio for typography and spacing means line-heights at each type level land on values already in the spacing scale: body line-height (27px) equals h3's size; h3's line-height (36px) equals `--spacing-lg`; h2's line-height (48px) equals `--spacing-xl`. That internal resonance — sizes and gaps agreeing with each other across dimensions — is the practical payoff of sharing one modular scale, not an aesthetic flourish.

## 8. Why is Aleris's body text larger than the web default, and where's the floor?

### 18px because anxious readers on unfamiliar devices need calm, not haste; 14px because nothing goes lower.

Body text at 18px — larger than the web default of 16px — reads calm rather than rushed. Healthcare interfaces serve users who may be reading under stress, on devices they didn't choose. Larger body text is a structural empathy decision, not an aesthetic preference.

14px is an absolute floor: nothing in any Aleris interface renders below it. The Figma export once contained tokens at 12px and 13px; both were removed from the canonical scale entirely, not just flagged as discouraged.

## 9. Why do type and spacing share one ratio?

### Because the alternative is a designer memorising two unrelated scales instead of reasoning from one.

Using the same 1.5 ratio for typography and spacing means a question like "how much space goes above this heading" already has an answer built into the system — space proportional to the heading's visual weight, derived from the same ratio that sized the heading. The system teaches correct usage through its own internal logic, rather than through a lookup table someone has to remember.

Communicative surfaces use the full spacing range (`--spacing-sm` through `--spacing-3xl`); instrumental surfaces use the tighter range (`--spacing-3xs` through `--spacing-lg`) — same tokens, different slice. **Note.** This sentence read *"exactly like the type scale's own communicative/instrumental split"*. There is no such split and there never was: the type scale is one scale on both surfaces, and surface temperature does not change type size. Spacing is sliced by mode; type is not.

## 10. Why round the modular scale to a grid instead of using its exact values?

### Because sub-pixel rounding is a rendering bug waiting to happen, and 4px is already the industry's own half-step.

The spacing scale aligns to a 4px grid rather than a pure 1.5× progression. Sub-pixel values create visual inconsistency across devices and browsers; a 4px grid keeps rendering crisp and matches the industry-standard 8px grid with a 4px half-step. The scale's proportional relationships survive the rounding — the feel of the ratio is preserved even where an individual value is nudged onto the grid.

## 11. Why do shadows carry colour instead of staying neutral black?

### Because a black shadow on a warm palette reads as a mistake, even to someone who can't say why.

Shadows use `rgba(0, 72, 81, ...)` — petrol — rather than `rgba(0, 0, 0, ...)` — black. Black shadows on a warm palette create visual discord: they read as "off" without most viewers being able to articulate the cause. Petrol-tinted shadows integrate with the warm sand backgrounds and hold the overall warm-shifted aesthetic. It's a subtle detail with disproportionate impact on whether an interface "feels like Aleris." (The Figma export still uses black-based shadows as of this writing — syncing it to the petrol-tinted canonical values is outstanding, tracked in `schemas/design-token.md`'s Figma sync notes.)

## 12. Should the design system depend on a specific frontend framework?

### No — CSS custom properties are the whole system; frameworks are optional bridges on top.

Aleris operates across three countries with separate IT infrastructure, different digital maturity levels, and products on different technology stacks. Coupling the design system to one framework creates a dependency that limits adoption and introduces maintenance risk whenever frameworks change. CSS custom properties are a W3C standard with universal browser support and no versioning risk — a design system built on them outlives any single framework choice.

## 13. Is the Tailwind mapping part of design governance?

### No — it's disposable convenience infrastructure, kept intentionally separate from the tokens it reads.

Tailwind is the current utility framework in Aleris product development, and it earns real productivity benefits for layout and responsive design. But framework popularity shifts over time. Because the bridge pattern reads from tokens without tokens ever referencing the bridge, Aleris can adopt or drop frameworks without touching the design system itself. The bridge earns its place through developer productivity, not through architectural necessity — which is exactly why it's documented in `schemas/design-token.md` (mechanics) rather than governed here (reasoning that would make it feel load-bearing).

## 14. Should a framework bridge match Baseline's type scale pixel-for-pixel?

### No — matching the framework's own nearest step matters more than matching Baseline's exact pixel.

The type scale's primary job is providing clear visual hierarchy across Aleris products. The modular scale gives the mathematical foundation — a rationale for why each size exists and a way to generate new ones coherently. In rendered output, the gap between a heading at 27px and one at 30px doesn't weaken that hierarchy. Requiring pixel-perfect precision in every framework context would force either framework divergence (confusing for developers) or scale compromise (weakening the mathematical foundation) — accepting an approximate bridge mapping avoids both, and the token file remains the authority for any context where exact values matter (patient guides, PDF generation, print).

## 15. Are Aleris buttons pills?

### No, and they never were — the record claiming otherwise was never in force.

`baseline/reference/aleris-design-tokens.md` once carried a Design Decision Record arguing Aleris buttons are pills and cards are 12px, plus a "Beginner" summary instructing the same. Torfinn confirmed 2026-08-07 that this was never true in force — it's deleted rather than marked superseded, because there's no earlier accepted state to point back to. The hard rule (`BASELINE.md`): no full-pill buttons exist in Aleris interfaces; all buttons use `--radius-m` (8px). A 2026-07-30 partial correction had already fixed the one component-token row that stated `radius-full (pill)` for `--button-primary-radius`, contradicting both `aleris-tokens.css` and this hard rule — but left the prose record and its summary standing for another eight days. Card 45's 2026-08-10 production measurement confirmed it a second, independent way: nothing on any live Aleris site is a pill.

## 16. What decides a container's corner radius: its role, or its geometry?

### Both, and the tension between them sat undetected for months because nothing ever brought the two into the same room.

Baseline assigned radius by functional role — containers 4px, interactive elements 8px, indicators pill — which is easy to teach but silently inverts the concentric-corner convention that a nested rounded rectangle's outer radius must be at least its inner radius plus the gap between them. At Baseline's 24px card padding nothing shares a corner region, so the inversion was invisible; it became visible the moment something sat flush against a card edge, and by 2026-08-07 the live case was named: an 8px image inside a 4px card.

Rather than patch that one pair, board card 45 (2026-08-10) reframed the question as one problem — what should a card's default corner state be — and answered it from what production had already settled empirically: computed styles measured live on aleris.se/.no/.dk showed every card shipping at 16px, not Baseline's 4px, already nesting correctly by the geometric convention (card 16 > inset image 8 > full-bleed hero 0). The decision: cards derive inward from a 16px base, floored at 0. `--radius-l` — retired 2026-08-07 at its old, never-assigned 12px value — is reinstated to carry it, at the new value, assigned to cards specifically. **Scoped to cards only, deliberately:** panels, modals and tables were never measured live, so they keep `--radius-s` until they are — this is a card exception, not a change to the general role-based model.

## 17. Does a flush image need its own corner radius?

### No, and the first answer that said yes was reverted the same day it shipped.

The initial implementation of principle 16 gave a flush top-of-card image its own paired tokens — a top radius matching the card's, a bottom radius fixed at zero — to hand-match the result geometry demanded. Torfinn asked the obvious question a few hours later: why not let the card simply clip its own contents, so a plain, radius-less image renders with the identical result for free?

It works, and it's strictly better: a card with `overflow: hidden` and its own `border-radius` clips any child to its shape automatically, including a flush image at the top — rounded where the corner is shared, square at the bottom because that edge sits nowhere near a corner at all. `--card-media-overflow: hidden` replaced the paired radius tokens everywhere they'd landed. The one real trade-off — clipping affects *everything* that overflows the card, not just the image, which would clip a badge or dropdown meant to overhang the edge — is scoped away by applying the token to cards that actually contain flush media, not to every card, so a plain card stays free to host overhanging content later without reopening this decision.

---

## Related

- [[schemas/design-token]] — the shape, the architecture, the framework integrations
- [[data-products/tokens/aleris-tokens.css]] — the canonical values these principles explain
- `BASELINE.md` § Component shapes, § Buttons — the rules of record these principles argue for
- `workspace/archive/2026-08-07-radius-contradiction-and-nesting.md`, `2026-08-10-radius-verify-prototype-and-package.md` — the full radius-decision working notes behind principles 15–17


<!-- source: constitutional/accessibility-is-foundational.md · status: accepted -->

---
title: Accessibility is foundational
layer: constitutional
status: accepted
normative: true
owner: Head of Design
scope: all Aleris surfaces
version: v0.2
depends_on: []
propagates_to:
  - foundation/*
  - baseline/*
  - communication/*
  - physical/*
updated: 2026-08-11
lang: en
---

# Accessibility is foundational

> **This node is the canonical, single home for the three shared accessibility floors.** Every other location in the corpus references it and none restates it. `normative: true` is set: the rules here are binding, independent of the page's own status badge — a `proposed` node can still carry a binding rule, the way a constitution can be ratified in stages.

---

## 1. The non-negotiables

Pulled from the identity-element sorts so they live once and are *referenced* by each page, not restated.

- **No information by colour alone.** Always pair colour with text, icon, or pattern. *(from Colour — colour blindness affects ~8% of men; red/green is not distinguishable.)*
- **No text below 14px** or its equivalent. *(from Typography — the audience includes patients with reduced vision and stressed readers.)*
- **Contrast meets WCAG AA** – 4.5:1 for normal text, 3.0:1 for large text and UI components.

**Implication:** All three are clean bright-lines and clean fitness functions.

> **Contrast is a bright-line with two known breaches, accepted knowingly** – the secondary button on hover and the confirm button at rest. They are breaches of a non-negotiable rather than departures from a default, which is what makes them a resolution path instead of a backlog entry. Both are documented failures in the fitness check.

---

## 2. Why these are shared, not per-page

Every identity-element page will carry an accessibility floor, and most are *measurable* — "no text < 14px," "contrast ≥ threshold" are binary, runnable checks. That makes them fitness functions, the slice external review (granskning) actually tests. Keeping them in one node means a single source of truth, one place to update when WCAG moves, and no drift between the colour page's contrast rule and the type page's size rule.

**Implication:** Identity-element pages reference this node; they don't copy it. When a new page surfaces an accessibility floor, it lands here.

---

## Detection of violation

Caught by the fitness functions in `data-products/tokens/tokens.test.ts` where the floor is machine-checkable (font size, contrast ratio), and by review otherwise (colour-alone meaning is a claim about a rendered component, not a token ratio — see `baseline/BASELINE.md § Accessibility`'s test-coverage table for what is and isn't asserted today).

---

## Related

- [[colour-is-the-aleris-palette]] — references this node
- [[typography-is-museo-sans]] — references this node
- `documentation/constitutional-core-test.md` — Open #3, the cross-page accessibility question
- `documentation/aleris-meta-alignment.md` — §3 qualities/ilities


<!-- source: how-to/install-the-tokens.md · status: accepted -->

---
title: Setup for AI coding tools
type: how-to/recipe
layer: how-to
status: accepted
label: shareable
passed: 2026-09-28
lang: en
last_verified: 2026-09-28
---

# Setup for AI coding tools

You are an AI coding assistant (Claude Code, Cursor, or similar) starting work on a new Aleris project. This page walks you through the four steps needed to bootstrap the design system into the project.

The base URL throughout this document is `https://brand.dev.aleris.ai`. Substitute your own host if you are reading a fork or proxied deployment.

---

## Step 1 — Pull tokens into the project

The token CSS file is the only load-bearing build dependency. Everything else is reference.

Fetch it and write it into the project as `src/styles/aleris-tokens.css` (or wherever the project keeps global stylesheets):

```bash
curl -fsSL https://brand.dev.aleris.ai/baseline/raw/tokens/aleris-tokens.css \
  -o src/styles/aleris-tokens.css
```

Import it from the app entry point so every component can read the custom properties:

```ts
// app/layout.tsx, src/main.tsx, _app.tsx — wherever the global stylesheet lives
import '@/styles/aleris-tokens.css'
```

**Keep your own values in a second file that loads after this one — never append them to it.** A project will need values Baseline does not carry, and the instinct is to add them to the bottom of the vendored file. Do not: the vendored copy has to stay byte-comparable to the source, because that comparison is the only thing that can tell you the tokens have moved. Put local additions in `src/styles/<project>-tokens.css`, import it *after* the Aleris file, and let the cascade do the overriding. Then re-vendoring is a file replacement instead of a merge.

**Do not strip the comments out of the file by hand.** Roughly two thirds of it is prose, and every `@usage` and `@constraint` annotation is a rule rather than a note — deleting them to save bytes deletes the documentation a developer opens the file for. The saving is real and the place to take it is the build: `BASELINE.md` § *Serving this file* has the rule, the reasoning, the two failure modes and a pointer to a working implementation in another repo. Strip at build time, keep the source whole.

**Step 1b — Link the hosted font stylesheet**

Do not copy or bundle the woff2 files — not because the licence forbids it, but because a copied font file is a copy that drifts and a version nobody tracks. The fonts are served only to pages on `aleris.ai`, `aleris.se`, `aleris.no` or `aleris.dk` or any of their subdomains, and to `localhost` for development. An app on a platform's default hostname — an `azurewebsites.net` address, say — gets Arial with no error, so serve it from an Aleris domain. Add these two tags to the `<head>` of your root layout or HTML template:

*(What the licence actually says, read from the primary text: §2d limits use to websites the licensee **owns or controls**, with no domain count, and Fontspring's Worry-Free terms state unlimited domains explicitly. The Aleris-domain restriction is our own CORS configuration on `brand.dev.aleris.ai` — infrastructure, not a licence limit. Describing it as legal makes a compliant build look non-compliant. Claude-drafted; the clause numbers are checkable.)*

```html
<!-- Preconnect to reduce latency on first font fetch -->
<link rel="preconnect" href="https://brand.dev.aleris.ai" crossorigin />

<!-- Hosted Museo Sans @font-face declarations -->
<link
  rel="stylesheet"
  href="https://brand.dev.aleris.ai/fonts/aleris-fonts.css"
  crossorigin
/>
```

In a React/Next.js layout this looks like:

```tsx
// app/layout.tsx (or equivalent)
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="sv">
      <head>
        <link rel="preconnect" href="https://brand.dev.aleris.ai" crossOrigin="" />
        <link rel="stylesheet" href="https://brand.dev.aleris.ai/fonts/aleris-fonts.css" crossOrigin="" />
      </head>
      <body>{children}</body>
    </html>
  )
}
```

The stylesheet uses `font-display: swap` so text is visible immediately in the fallback font while the woff2 loads. Set `font-synthesis-weight: none` on `body` to prevent the browser from synthesising fake bold when a weight is slow to arrive.

Optional but recommended — also fetch the JSON form for token lookup, autocomplete, and constraint metadata:

```bash
curl -fsSL https://brand.dev.aleris.ai/baseline/raw/tokens/baseline-tokens.json \
  -o src/styles/aleris-tokens.json
```

**Step 1c — Tailwind projects only: fetch the theme bridge**

Optional. It changes what is convenient to type and nothing about what the design system says — skip it and every token still works through `var(--token-name)`.

```bash
curl -fsSL https://brand.dev.aleris.ai/baseline/raw/tokens/aleris-tailwind.css \
  -o src/styles/aleris-tailwind.css
```

Import it **after** the token file and **after** Tailwind:

```css
@import "./aleris-tokens.css";
@import "tailwindcss";
@import "./aleris-tailwind.css";
```

You then get `bg-aleris-petrol-500`, `text-aleris-text-secondary`, `p-aleris-md`, `rounded-aleris-m`, `shadow-aleris-e1`, `leading-aleris-body` and the `aleris-md:` breakpoint variant, each compiling straight to the token. **Tailwind's own colours are switched off** — `bg-blue-500` does not compile, so every colour on the page is an Aleris one. Tailwind's spacing, type and breakpoints are untouched. The switch-off holds only with the bridge after Tailwind. That is also how you read a diff: `bg-aleris-*` is a token, `bg-[#004851]` is not.

The bridge is generated from the token file and holds no value of its own, so it cannot drift from it. Do not hand-edit it, and do not hand-write your own — `schemas/design-token.md` § "Tailwind CSS v4" carries the reason, which is that the hand-written example this replaced had seven dead token references nobody had noticed.

---

## Step 2 — Drop a CLAUDE.md into the project root

Create `CLAUDE.md` at the project root with the contents below. Future sessions will read this and stay aligned without refetching this scaffolder every time.

````markdown
# Aleris design system

This project uses the Aleris brand and design system. Tokens live in
`src/styles/aleris-tokens.css` (already imported). Reference material is at
https://brand.dev.aleris.ai/llms.txt — fetch that for the AI-readable
index of every guideline page.

## Non-negotiables

- Only use tokens from `aleris-tokens.css`. No raw hex, no pixel values, no
  raw Tailwind colour classes.
- The page's own background is always sand: `--surface-page` (communicative)
  or `--surface-page-instrumental`. Full-width sections are layered on it,
  in `--surface-section-white`, `-cold`, `-warm` or `-strong`.
- One primary CTA per screen — `--button-primary-bg`. Never `--brand-accent`:
  white text on it fails contrast. No primary on `--surface-section-warm`.
- No font-weight 400. Museo Sans has no 400 weight. Use 500 or 700 only.
- 14px font floor — `--font-size-xs`. Nothing smaller.
- No all caps in content. Sentence case everywhere.
- Animate what does not move the layout, unless the user moved it. State
  colours and shadows may transition; size only when the user caused it and
  it is contained. `prefers-reduced-motion: reduce` disables all of it.

The full list of ten hard rules and their reasoning lives at
https://brand.dev.aleris.ai/baseline/raw/BASELINE.md — read it before
writing UI.

## Surface mode

This project is **[communicative | instrumental]**.
<!-- Pick one. Communicative = patient-facing content, marketing, service introductions.
     Instrumental = tools, admin, dashboards, internal apps, booking flows. -->

## When uncertain

Don't guess. Leave a comment in the code:

```css
/* OPEN: [the decision needed] */
```

Then continue with a reasonable placeholder. Surface OPEN comments at review.

## Out of scope for AI

- Don't write external-facing brand copy. Voice work is a human decision.
- Don't redesign tokens or invent new ones. Raise as an OPEN comment instead.
- Don't override hard rules to make a layout work — the layout is wrong.

## Owner

Torfinn Almers, Head of Design, Aleris Group.
````

Replace `[communicative | instrumental]` with one of the two values. The choice does not change between steps within a single flow.

---

## Step 3 — Pick the surface mode

Every Aleris surface is either **communicative** or **instrumental**. The two modes use different page backgrounds, spacing, grid widths, and tone.

**Communicative** — Aleris guides, explains, welcomes. Patient-facing flows, marketing pages, service introductions.
- Page background: sand-100 (`--surface-page`)
- Generous spacing, full type scale
- Cards mandatory for content structure
- Max width 1200px

**Instrumental** — The user works. Tools, admin, dashboards, internal documentation, booking flows.
- Page background: sand-50 (`--surface-page-instrumental`)
- Tighter spacing; the type scale is the same, because surface temperature does not change type size
- Cards optional — content can live directly on the surface
- Max width 1440px or fluid

If unsure, ask the project owner. If the project mixes audiences (e.g. a clinician dashboard with a patient-facing report view), pick the dominant surface and treat the secondary view as a known divergence with a note.

Full reasoning is in the surface temperature section of [BASELINE.md](https://brand.dev.aleris.ai/baseline/raw/BASELINE.md).

---

## Step 4 — Read these before writing UI

Fetch and read in this order:

1. https://brand.dev.aleris.ai/baseline/raw/BASELINE.md — hard rules, components, motion, accessibility.
2. https://brand.dev.aleris.ai/foundation/raw/voice.md — how Aleris sounds.
3. https://brand.dev.aleris.ai/foundation/raw/colour.md — palette and rules.
4. https://brand.dev.aleris.ai/foundation/raw/typography.md — type system.
5. https://brand.dev.aleris.ai/baseline/raw/governance/aleris-anti-patterns.md — ten healthcare anti-patterns. Hard constraints.

When you start building specific UI, fetch the matching pattern:

- Error messages → https://brand.dev.aleris.ai/baseline/raw/patterns/error-message.md
- Confirmations → https://brand.dev.aleris.ai/baseline/raw/patterns/confirmation.md
- Empty states → https://brand.dev.aleris.ai/baseline/raw/patterns/empty-state.md
- Button labels → https://brand.dev.aleris.ai/baseline/raw/patterns/button-label.md
- Chat responses → https://brand.dev.aleris.ai/baseline/raw/patterns/chat-response-simple.md

The full machine-readable index is at https://brand.dev.aleris.ai/llms.txt.

---

## Known gaps (as of 2026-06-12)

These are not yet in the system. If the project needs them, ask the project owner before inventing:

- F3 Logo placement, clearspace, sizing
- F5 Writing conventions (microcopy, terminology, capitalisation rules beyond "no all caps")
- Communication genres (email, SMS, push, letter formats)

---

## Out of scope for this scaffolder

- Picking a framework or build tool. Aleris doesn't mandate React vs Svelte vs anything else — but tokens must be importable as CSS custom properties, so ESM or postcss-friendly stacks are easiest.
- Component library installation. There is no Aleris npm package today. Tokens are the contract; components are built per project.
- Authentication, data layer, deployment. Out of brand scope.
