opsinjs
FoundationsTheming & tokens

Token reference

Every opsinjs token you are allowed to set, grouped by tier, with what it controls and which parts of the system read it.

How this is generated

Source files: tokens/color.json, tokens/material.json, tokens/motion.json, tokens/type.json, tokens/space.json, tokens/shape.json. Script: scripts/build-tokens.mts. Command: pnpm run generate.

Every token and contrast row below is generated. The token tables are rendered by <TokenTable> from lib/generated/tokens.ts, which the script writes from those JSON files, and the measured-contrast table by <ContrastReport> from the same generated output. No value in those tables is typed by a person, and pnpm run check:generated fails the build if the committed generated output no longer matches its source. The one hand-authored part is the Data attributes section at the foot of the page: it mirrors ADR 0011 by hand rather than a generator, and ADR 0011 is the record that wins if the two ever disagree.

This page is the theme author's view: it is grouped by tier, and it says what you may set. The exhaustive machine-readable dump is Reference → Tokens. It includes the internal tokens no one outside the generator should reference. Where the two disagree, the generated reference is right and this page has a stale grouping.

To change a value, change the JSON and regenerate. Editing app/tokens.generated.css directly is caught by the check and reverted.

Tier 2 role tokens

The tier you retheme, and the tier the versioning policy covers. An opsinjs role is named for the job it does on one of the two colour axes, and every ramp exposes exactly four: -surface is the tinted background, -line the boundary or icon stroke, -ink the text and text-sized icons that sit on the surface, and -accent the identity fill. There is no -foreground partner. That is the shadcn convention the documentation chrome is built on, and those variables are not opsinjs tokens and carry no promise.

<TokenTable> filters by source file rather than by tier, so the table below is tokens/color.json in full. It contains the neutrals, plus the status and category ramps the two Tier 1 sections further down explain. Read the -surface, -line, -ink and -accent rows and ignore the numbered steps beside them; those are primitives, and a component that references one has skipped the tier this page is about. The generated reference carries the same rows with a tier column and separate light and dark values: Reference → Tokens.

TokenWhat it controlsUsed by
--opsin-neutral-0oklch(1 0 0)Neutral ramp, step 0.no component
--opsin-neutral-50oklch(0.985 0.002 250)Neutral ramp, step 50.no component
--opsin-neutral-100oklch(0.967 0.003 250)Neutral ramp, step 100.no component
--opsin-neutral-200oklch(0.929 0.004 250)Neutral ramp, step 200.no component
--opsin-neutral-300oklch(0.871 0.005 250)Neutral ramp, step 300.no component
--opsin-neutral-400oklch(0.708 0.008 250)Neutral ramp, step 400.no component
--opsin-neutral-500oklch(0.556 0.009 250)Neutral ramp, step 500.no component
--opsin-neutral-600oklch(0.439 0.009 250)Neutral ramp, step 600.no component
--opsin-neutral-700oklch(0.371 0.008 250)Neutral ramp, step 700.no component
--opsin-neutral-800oklch(0.269 0.007 250)Neutral ramp, step 800.no component
--opsin-neutral-900oklch(0.205 0.006 250)Neutral ramp, step 900.no component
--opsin-neutral-950oklch(0.145 0.005 250)Neutral ramp, step 950.no component
--opsin-neutral-1000oklch(0 0 0)Neutral ramp, step 1000.no component
--opsin-chrome-backgroundoklch(0.985 0.002 250)Chrome role background, resolved to the ladder step it names.no component
--opsin-chrome-foregroundoklch(0.205 0.006 250)Chrome role foreground, resolved to the ladder step it names.no component
--opsin-chrome-cardoklch(1 0 0)Chrome role card, resolved to the ladder step it names.no component
--opsin-chrome-card-foregroundoklch(0.205 0.006 250)Chrome role cardForeground, resolved to the ladder step it names.no component
--opsin-chrome-mutedoklch(0.967 0.003 250)Chrome role muted, resolved to the ladder step it names.no component
--opsin-chrome-muted-foregroundoklch(0.439 0.009 250)Chrome role mutedForeground, resolved to the ladder step it names.no component
--opsin-chrome-borderoklch(0.556 0.009 250)Chrome role border, resolved to the ladder step it names.no component
--opsin-chrome-inputoklch(0.556 0.009 250)Chrome role input, resolved to the ladder step it names.no component
--opsin-chrome-ringoklch(0.55 0.13 250)Chrome role ring, resolved to the ladder step it names.no component
--opsin-chrome-primaryoklch(0.51 0.135 250)Chrome role primary, resolved to the ladder step it names.no component
--opsin-chrome-primary-foregroundoklch(0.99 0 0)Chrome role primaryForeground, resolved to the ladder step it names.no component
--opsin-category-sleep-50oklch(0.972 0.013 275)Sleep ramp, step 50.no component
--opsin-category-sleep-100oklch(0.941 0.028 275)Sleep ramp, step 100.no component
--opsin-category-sleep-200oklch(0.884 0.057 275)Sleep ramp, step 200.no component
--opsin-category-sleep-300oklch(0.806 0.092 275)Sleep ramp, step 300.no component
--opsin-category-sleep-400oklch(0.714 0.123 275)Sleep ramp, step 400.no component
--opsin-category-sleep-500oklch(0.622 0.14 275)Sleep ramp, step 500.no component
--opsin-category-sleep-600oklch(0.541 0.136 275)Sleep ramp, step 600.no component
--opsin-category-sleep-700oklch(0.452 0.123 275)Sleep ramp, step 700.no component
--opsin-category-sleep-800oklch(0.362 0.104 275)Sleep ramp, step 800.no component
--opsin-category-sleep-900oklch(0.276 0.081 275)Sleep ramp, step 900.no component
--opsin-category-sleep-950oklch(0.208 0.062 275)Sleep ramp, step 950.no component
--opsin-category-sleep-surfaceoklch(0.972 0.013 275)The tinted background a component in this ramp sits on.no component
--opsin-category-sleep-lineoklch(0.541 0.136 275)The boundary, icon stroke or chart mark. The lightest value in the ramp that clears the non-text floor against `surface`.trend-sparkline
--opsin-category-sleep-inkoklch(0.362 0.104 275)Text and text-sized icons on `surface`. Clears the text floor against `surface`.metric-tile, range-bar, result-card, score-dial
--opsin-category-sleep-accentoklch(0.58 0.14 275)The identity fill: a bar fill, a dial track, a legend dot. Chosen for recognition, not for contrast. It must be bounded by `line` or labelled in `ink`; it is never the only thing that carries the meaning.log-sheet
--opsin-category-heart-50oklch(0.972 0.014 15)Heart ramp, step 50.no component
--opsin-category-heart-100oklch(0.941 0.03 15)Heart ramp, step 100.no component
--opsin-category-heart-200oklch(0.884 0.062 15)Heart ramp, step 200.no component
--opsin-category-heart-300oklch(0.806 0.112 15)Heart ramp, step 300.no component
--opsin-category-heart-400oklch(0.714 0.15 15)Heart ramp, step 400.no component
--opsin-category-heart-500oklch(0.622 0.17 15)Heart ramp, step 500.no component
--opsin-category-heart-600oklch(0.541 0.165 15)Heart ramp, step 600.no component
--opsin-category-heart-700oklch(0.452 0.15 15)Heart ramp, step 700.no component
--opsin-category-heart-800oklch(0.362 0.126 15)Heart ramp, step 800.no component
--opsin-category-heart-900oklch(0.276 0.099 15)Heart ramp, step 900.no component
--opsin-category-heart-950oklch(0.208 0.075 15)Heart ramp, step 950.no component
--opsin-category-heart-surfaceoklch(0.972 0.014 15)The tinted background a component in this ramp sits on.no component
--opsin-category-heart-lineoklch(0.541 0.165 15)The boundary, icon stroke or chart mark. The lightest value in the ramp that clears the non-text floor against `surface`.trend-sparkline
--opsin-category-heart-inkoklch(0.362 0.126 15)Text and text-sized icons on `surface`. Clears the text floor against `surface`.metric-tile, range-bar, result-card, score-dial
--opsin-category-heart-accentoklch(0.62 0.17 15)The identity fill: a bar fill, a dial track, a legend dot. Chosen for recognition, not for contrast. It must be bounded by `line` or labelled in `ink`; it is never the only thing that carries the meaning.log-sheet
--opsin-category-activity-50oklch(0.972 0.022 140)Activity ramp, step 50.no component
--opsin-category-activity-100oklch(0.941 0.038 140)Activity ramp, step 100.no component
--opsin-category-activity-200oklch(0.884 0.067 140)Activity ramp, step 200.no component
--opsin-category-activity-300oklch(0.806 0.106 140)Activity ramp, step 300.no component
--opsin-category-activity-400oklch(0.714 0.141 140)Activity ramp, step 400.no component
--opsin-category-activity-500oklch(0.622 0.16 140)Activity ramp, step 500.no component
--opsin-category-activity-600oklch(0.541 0.155 140)Activity ramp, step 600.no component
--opsin-category-activity-700oklch(0.452 0.141 140)Activity ramp, step 700.no component
--opsin-category-activity-800oklch(0.362 0.118 140)Activity ramp, step 800.no component
--opsin-category-activity-900oklch(0.276 0.092 140)Activity ramp, step 900.no component
--opsin-category-activity-950oklch(0.208 0.07 140)Activity ramp, step 950.no component
--opsin-category-activity-surfaceoklch(0.972 0.022 140)The tinted background a component in this ramp sits on.no component
--opsin-category-activity-lineoklch(0.541 0.155 140)The boundary, icon stroke or chart mark. The lightest value in the ramp that clears the non-text floor against `surface`.trend-sparkline
--opsin-category-activity-inkoklch(0.362 0.118 140)Text and text-sized icons on `surface`. Clears the text floor against `surface`.metric-tile, range-bar, result-card, score-dial
--opsin-category-activity-accentoklch(0.7 0.16 140)The identity fill: a bar fill, a dial track, a legend dot. Chosen for recognition, not for contrast. It must be bounded by `line` or labelled in `ink`; it is never the only thing that carries the meaning.log-sheet
--opsin-category-nutrition-50oklch(0.972 0.02 70)Nutrition ramp, step 50.no component
--opsin-category-nutrition-100oklch(0.941 0.034 70)Nutrition ramp, step 100.no component
--opsin-category-nutrition-200oklch(0.884 0.059 70)Nutrition ramp, step 200.no component
--opsin-category-nutrition-300oklch(0.806 0.092 70)Nutrition ramp, step 300.no component
--opsin-category-nutrition-400oklch(0.714 0.123 70)Nutrition ramp, step 400.no component
--opsin-category-nutrition-500oklch(0.622 0.135 70)Nutrition ramp, step 500.no component
--opsin-category-nutrition-600oklch(0.541 0.118 70)Nutrition ramp, step 600.no component
--opsin-category-nutrition-700oklch(0.452 0.099 70)Nutrition ramp, step 700.no component
--opsin-category-nutrition-800oklch(0.362 0.08 70)Nutrition ramp, step 800.no component
--opsin-category-nutrition-900oklch(0.276 0.062 70)Nutrition ramp, step 900.no component
--opsin-category-nutrition-950oklch(0.208 0.049 70)Nutrition ramp, step 950.no component
--opsin-category-nutrition-surfaceoklch(0.972 0.02 70)The tinted background a component in this ramp sits on.no component
--opsin-category-nutrition-lineoklch(0.541 0.118 70)The boundary, icon stroke or chart mark. The lightest value in the ramp that clears the non-text floor against `surface`.trend-sparkline
--opsin-category-nutrition-inkoklch(0.362 0.08 70)Text and text-sized icons on `surface`. Clears the text floor against `surface`.metric-tile, range-bar, result-card, score-dial
--opsin-category-nutrition-accentoklch(0.75 0.14 70)The identity fill: a bar fill, a dial track, a legend dot. Chosen for recognition, not for contrast. It must be bounded by `line` or labelled in `ink`; it is never the only thing that carries the meaning.log-sheet
--opsin-category-mind-50oklch(0.972 0.018 310)Mind ramp, step 50.no component
--opsin-category-mind-100oklch(0.941 0.034 310)Mind ramp, step 100.no component
--opsin-category-mind-200oklch(0.884 0.059 310)Mind ramp, step 200.no component
--opsin-category-mind-300oklch(0.806 0.092 310)Mind ramp, step 300.no component
--opsin-category-mind-400oklch(0.714 0.123 310)Mind ramp, step 400.no component
--opsin-category-mind-500oklch(0.622 0.14 310)Mind ramp, step 500.no component
--opsin-category-mind-600oklch(0.541 0.136 310)Mind ramp, step 600.no component
--opsin-category-mind-700oklch(0.452 0.123 310)Mind ramp, step 700.no component
--opsin-category-mind-800oklch(0.362 0.104 310)Mind ramp, step 800.no component
--opsin-category-mind-900oklch(0.276 0.081 310)Mind ramp, step 900.no component
--opsin-category-mind-950oklch(0.208 0.062 310)Mind ramp, step 950.no component
--opsin-category-mind-surfaceoklch(0.972 0.018 310)The tinted background a component in this ramp sits on.no component
--opsin-category-mind-lineoklch(0.541 0.136 310)The boundary, icon stroke or chart mark. The lightest value in the ramp that clears the non-text floor against `surface`.trend-sparkline
--opsin-category-mind-inkoklch(0.362 0.104 310)Text and text-sized icons on `surface`. Clears the text floor against `surface`.metric-tile, range-bar, result-card, score-dial
--opsin-category-mind-accentoklch(0.62 0.14 310)The identity fill: a bar fill, a dial track, a legend dot. Chosen for recognition, not for contrast. It must be bounded by `line` or labelled in `ink`; it is never the only thing that carries the meaning.log-sheet
--opsin-category-labs-50oklch(0.972 0.015 210)Labs ramp, step 50.no component
--opsin-category-labs-100oklch(0.941 0.026 210)Labs ramp, step 100.no component
--opsin-category-labs-200oklch(0.884 0.046 210)Labs ramp, step 200.no component
--opsin-category-labs-300oklch(0.806 0.073 210)Labs ramp, step 300.no component
--opsin-category-labs-400oklch(0.714 0.097 210)Labs ramp, step 400.no component
--opsin-category-labs-500oklch(0.622 0.108 210)Labs ramp, step 500.no component
--opsin-category-labs-600oklch(0.541 0.094 210)Labs ramp, step 600.no component
--opsin-category-labs-700oklch(0.452 0.079 210)Labs ramp, step 700.no component
--opsin-category-labs-800oklch(0.362 0.063 210)Labs ramp, step 800.no component
--opsin-category-labs-900oklch(0.276 0.049 210)Labs ramp, step 900.no component
--opsin-category-labs-950oklch(0.208 0.038 210)Labs ramp, step 950.no component
--opsin-category-labs-surfaceoklch(0.972 0.015 210)The tinted background a component in this ramp sits on.no component
--opsin-category-labs-lineoklch(0.541 0.094 210)The boundary, icon stroke or chart mark. The lightest value in the ramp that clears the non-text floor against `surface`.trend-sparkline
--opsin-category-labs-inkoklch(0.362 0.063 210)Text and text-sized icons on `surface`. Clears the text floor against `surface`.metric-tile, range-bar, result-card, score-dial
--opsin-category-labs-accentoklch(0.6 0.11 210)The identity fill: a bar fill, a dial track, a legend dot. Chosen for recognition, not for contrast. It must be bounded by `line` or labelled in `ink`; it is never the only thing that carries the meaning.log-sheet
--opsin-status-steady-50oklch(0.972 0.015 178)Steady ramp, step 50.no component
--opsin-status-steady-100oklch(0.941 0.026 178)Steady ramp, step 100.no component
--opsin-status-steady-200oklch(0.884 0.046 178)Steady ramp, step 200.no component
--opsin-status-steady-300oklch(0.806 0.073 178)Steady ramp, step 300.no component
--opsin-status-steady-400oklch(0.714 0.097 178)Steady ramp, step 400.no component
--opsin-status-steady-500oklch(0.622 0.11 178)Steady ramp, step 500.no component
--opsin-status-steady-600oklch(0.541 0.1 178)Steady ramp, step 600.no component
--opsin-status-steady-700oklch(0.452 0.084 178)Steady ramp, step 700.no component
--opsin-status-steady-800oklch(0.362 0.067 178)Steady ramp, step 800.no component
--opsin-status-steady-900oklch(0.276 0.052 178)Steady ramp, step 900.no component
--opsin-status-steady-950oklch(0.208 0.041 178)Steady ramp, step 950.no component
--opsin-status-steady-surfaceoklch(0.972 0.015 178)The tinted background a component in this ramp sits on.alert-banner, status-pill
--opsin-status-steady-lineoklch(0.541 0.1 178)The boundary, icon stroke or chart mark. The lightest value in the ramp that clears the non-text floor against `surface`.alert-banner, range-bar, range-legend, status-pill
--opsin-status-steady-inkoklch(0.362 0.067 178)Text and text-sized icons on `surface`. Clears the text floor against `surface`.alert-banner, score-dial, status-pill
--opsin-status-steady-accentoklch(0.62 0.11 178)The identity fill: a bar fill, a dial track, a legend dot. Chosen for recognition, not for contrast. It must be bounded by `line` or labelled in `ink`; it is never the only thing that carries the meaning.score-dial
--opsin-status-watch-50oklch(0.972 0.02 82)Watch ramp, step 50.no component
--opsin-status-watch-100oklch(0.941 0.034 82)Watch ramp, step 100.no component
--opsin-status-watch-200oklch(0.884 0.059 82)Watch ramp, step 200.no component
--opsin-status-watch-300oklch(0.806 0.092 82)Watch ramp, step 300.no component
--opsin-status-watch-400oklch(0.714 0.123 82)Watch ramp, step 400.no component
--opsin-status-watch-500oklch(0.622 0.129 82)Watch ramp, step 500.no component
--opsin-status-watch-600oklch(0.541 0.112 82)Watch ramp, step 600.no component
--opsin-status-watch-700oklch(0.452 0.094 82)Watch ramp, step 700.no component
--opsin-status-watch-800oklch(0.362 0.076 82)Watch ramp, step 800.no component
--opsin-status-watch-900oklch(0.276 0.059 82)Watch ramp, step 900.no component
--opsin-status-watch-950oklch(0.208 0.046 82)Watch ramp, step 950.no component
--opsin-status-watch-surfaceoklch(0.972 0.02 82)The tinted background a component in this ramp sits on.alert-banner, status-pill
--opsin-status-watch-lineoklch(0.541 0.112 82)The boundary, icon stroke or chart mark. The lightest value in the ramp that clears the non-text floor against `surface`.alert-banner, range-bar, range-legend, status-pill
--opsin-status-watch-inkoklch(0.362 0.076 82)Text and text-sized icons on `surface`. Clears the text floor against `surface`.alert-banner, score-dial, status-pill
--opsin-status-watch-accentoklch(0.60 0.123 82)The identity fill: a bar fill, a dial track, a legend dot. Chosen for recognition, not for contrast. It must be bounded by `line` or labelled in `ink`; it is never the only thing that carries the meaning.score-dial
--opsin-status-attention-50oklch(0.972 0.015 45)Needs attention ramp, step 50.no component
--opsin-status-attention-100oklch(0.941 0.032 45)Needs attention ramp, step 100.no component
--opsin-status-attention-200oklch(0.884 0.063 45)Needs attention ramp, step 200.no component
--opsin-status-attention-300oklch(0.806 0.099 45)Needs attention ramp, step 300.no component
--opsin-status-attention-400oklch(0.714 0.132 45)Needs attention ramp, step 400.no component
--opsin-status-attention-500oklch(0.622 0.15 45)Needs attention ramp, step 500.no component
--opsin-status-attention-600oklch(0.541 0.145 45)Needs attention ramp, step 600.no component
--opsin-status-attention-700oklch(0.452 0.13 45)Needs attention ramp, step 700.no component
--opsin-status-attention-800oklch(0.362 0.105 45)Needs attention ramp, step 800.no component
--opsin-status-attention-900oklch(0.276 0.082 45)Needs attention ramp, step 900.no component
--opsin-status-attention-950oklch(0.208 0.065 45)Needs attention ramp, step 950.no component
--opsin-status-attention-surfaceoklch(0.972 0.015 45)The tinted background a component in this ramp sits on.alert-banner, status-pill
--opsin-status-attention-lineoklch(0.541 0.145 45)The boundary, icon stroke or chart mark. The lightest value in the ramp that clears the non-text floor against `surface`.alert-banner, range-bar, range-legend, status-pill
--opsin-status-attention-inkoklch(0.362 0.105 45)Text and text-sized icons on `surface`. Clears the text floor against `surface`.alert-banner, score-dial, status-pill
--opsin-status-attention-accentoklch(0.58 0.15 45)The identity fill: a bar fill, a dial track, a legend dot. Chosen for recognition, not for contrast. It must be bounded by `line` or labelled in `ink`; it is never the only thing that carries the meaning.score-dial
--opsin-status-urgent-50oklch(0.972 0.014 27)Urgent ramp, step 50.no component
--opsin-status-urgent-100oklch(0.941 0.03 27)Urgent ramp, step 100.no component
--opsin-status-urgent-200oklch(0.884 0.061 27)Urgent ramp, step 200.no component
--opsin-status-urgent-300oklch(0.806 0.11 27)Urgent ramp, step 300.no component
--opsin-status-urgent-400oklch(0.714 0.176 27)Urgent ramp, step 400.no component
--opsin-status-urgent-500oklch(0.622 0.2 27)Urgent ramp, step 500.no component
--opsin-status-urgent-600oklch(0.541 0.194 27)Urgent ramp, step 600.no component
--opsin-status-urgent-700oklch(0.452 0.176 27)Urgent ramp, step 700.no component
--opsin-status-urgent-800oklch(0.362 0.148 27)Urgent ramp, step 800.no component
--opsin-status-urgent-900oklch(0.276 0.115 27)Urgent ramp, step 900.no component
--opsin-status-urgent-950oklch(0.208 0.088 27)Urgent ramp, step 950.no component
--opsin-status-urgent-surfaceoklch(0.972 0.014 27)The tinted background a component in this ramp sits on.alert-banner, status-pill
--opsin-status-urgent-lineoklch(0.541 0.194 27)The boundary, icon stroke or chart mark. The lightest value in the ramp that clears the non-text floor against `surface`.alert-banner, range-bar, range-legend, status-pill
--opsin-status-urgent-inkoklch(0.362 0.148 27)Text and text-sized icons on `surface`. Clears the text floor against `surface`.alert-banner, score-dial, status-pill
--opsin-status-urgent-accentoklch(0.52 0.2 27)The identity fill: a bar fill, a dial track, a legend dot. Chosen for recognition, not for contrast. It must be bounded by `line` or labelled in `ink`; it is never the only thing that carries the meaning.score-dial
--opsin-status-unknown-50oklch(0.972 0.003 250)Unknown ramp, step 50.no component
--opsin-status-unknown-100oklch(0.941 0.005 250)Unknown ramp, step 100.no component
--opsin-status-unknown-200oklch(0.884 0.008 250)Unknown ramp, step 200.no component
--opsin-status-unknown-300oklch(0.806 0.013 250)Unknown ramp, step 300.no component
--opsin-status-unknown-400oklch(0.714 0.018 250)Unknown ramp, step 400.no component
--opsin-status-unknown-500oklch(0.622 0.02 250)Unknown ramp, step 500.no component
--opsin-status-unknown-600oklch(0.541 0.019 250)Unknown ramp, step 600.no component
--opsin-status-unknown-700oklch(0.452 0.018 250)Unknown ramp, step 700.no component
--opsin-status-unknown-800oklch(0.362 0.015 250)Unknown ramp, step 800.no component
--opsin-status-unknown-900oklch(0.276 0.012 250)Unknown ramp, step 900.no component
--opsin-status-unknown-950oklch(0.208 0.009 250)Unknown ramp, step 950.no component
--opsin-status-unknown-surfaceoklch(0.972 0.003 250)The tinted background a component in this ramp sits on.no component
--opsin-status-unknown-lineoklch(0.541 0.019 250)The boundary, icon stroke or chart mark. The lightest value in the ramp that clears the non-text floor against `surface`.no component
--opsin-status-unknown-inkoklch(0.362 0.015 250)Text and text-sized icons on `surface`. Clears the text floor against `surface`.no component
--opsin-status-unknown-accentoklch(0.72 0.02 250)The identity fill: a bar fill, a dial track, a legend dot. Chosen for recognition, not for contrast. It must be bounded by `line` or labelled in `ink`; it is never the only thing that carries the meaning.no component

Shape and space

One value each drives a derived scale, which is the whole point of them: --radius produces --radius-sm through --radius-4xl by calculation, and Tailwind derives its entire spacing scale from --spacing. Changing one number moves a whole system consistently, and changing forty numbers does not.

TokenWhat it controlsUsed by
--opsin-radius-base0.875remThe one radius the whole ladder is derived from.accordion, alert-banner, button, callout, card, care-card, combobox, empty-state, field, menu, metric-tile, popover, questionnaire, radio-group, range-bar, reading-input, result-card, scale-input, segmented-control, select, skeleton, symptom-picker, tab-bar, textarea, timeline-entry, toast, tooltip, trend-sparkline
--opsin-radius-none0remFull-bleed media and anything that meets a screen edge.no component
--opsin-radius-xs0.25remThe floor for a nested corner. Tags inside a chip, a swatch inside a legend.popover, range-legend, surface, symptom-picker, term, tooltip
--opsin-radius-sm0.4375remInputs and small controls inside a card.card, checkbox, combobox, consent-sheet, dialog, divider, field, log-sheet, menu, number-field, progress, reading-input, scroll-area, select, sheet, skeleton, slider, switch, tab-bar, tabs, toast, visually-hidden
--opsin-radius-md0.875remThe default. Buttons, chips, cards.alert-banner, button, callout, card, combobox, empty-state, menu, metric-tile, popover, questionnaire, radio-group, result-card, scale-input, segmented-control, select, skeleton, symptom-picker, tab-bar, textarea, toast, tooltip
--opsin-radius-lg1.3125remCards on a phone, where the card is nearly the width of the screen.accordion, card, combobox, disclaimer-note, menu, popover, result-card, scroll-area, segmented-control, select, surface, toast
--opsin-radius-xl1.75remSheets and dialogs. Applied to the leading edge only when the surface meets a screen edge on the other side.alert-banner, dialog, sheet
--opsin-radius-inner-cardmax(calc(0.875rem - 5 * var(--spacing)), var(--opsin-radius-xs))A box inside a card at the comfortable density (card padding p-5).no component
--opsin-radius-inner-card-compactmax(calc(0.875rem - 4 * var(--spacing)), var(--opsin-radius-xs))A box inside a card at the compact density (card padding p-4).no component
--opsin-corner-shapesuperellipse(4)The squircle curvature. Degrades to `round` where corner-shape is unsupported.alert-banner, callout, card, dialog, metric-tile, result-card, sheet
--opsin-border-hairline1pxEvery boundary in the system by default.button, surface
--opsin-border-emphasis2pxThe boundary of a surface carrying `attention` or `urgent`, where the boundary is one of the three non-colour carriers of the status.disclaimer-note, field, surface
--opsin-border-focus2pxThe focus ring. Always 2px with a 2px offset, always in a colour measured against both the surface and the page behind it, and never removed. See /docs/accessibility/keyboard-and-focus.accordion, body-map, button, card, checkbox, combobox, consent-sheet, link, menu, number-field, popover, questionnaire, radio-group, scale-input, scroll-area, segmented-control, select, slider, switch, symptom-picker, tab-bar, table, tabs, term, textarea, toast, tooltip
--opsin-border-focus-offset2pxOffset for the focus boundary.accordion, body-map, button, card, checkbox, combobox, consent-sheet, link, menu, number-field, popover, questionnaire, radio-group, scale-input, scroll-area, segmented-control, select, slider, switch, symptom-picker, tab-bar, table, tabs, term, textarea, toast, tooltip
TokenWhat it controlsUsed by
--opsin-space-00rem0px.no component
--opsin-space-10.25remGap between an icon and its label.badge, button, callout, care-card, checkbox, combobox, dialog, dose-tracker, empty-state, field, goal-ring, log-sheet, menu, questionnaire, radio-group, range-bar, result-card, scale-input, scroll-area, segmented-control, select, sheet, slider, source-citation, status-pill, stepper, surface, symptom-picker, tab-bar, tabs, timeline-entry, toast, tooltip, trend-sparkline
--opsin-space-20.5remGap between tightly related lines. For the separation between two interactive targets see targets.separation, which is the same number and carries its scope.alert-banner, avatar, badge, body-map, button, callout, combobox, consent-sheet, dialog, disclaimer-note, dose-tracker, empty-state, field, icon-button, log-sheet, menu, metric-tile, number-field, popover, progress, questionnaire, radio-group, range-bar, range-legend, reading-input, relative-time, scale-input, score-dial, scroll-area, segmented-control, select, sheet, skeleton, slider, spinner, status-pill, surface, symptom-picker, tab-bar, table, tabs, textarea, timeline-entry, toast, tooltip, trend-sparkline, visually-hidden
--opsin-space-30.75remInner padding of a compact control.accordion, alert-banner, button, card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, divider, empty-state, field, log-sheet, menu, metric-tile, popover, progress, radio-group, reading-input, result-card, scroll-area, segmented-control, select, sheet, slider, status-pill, stepper, surface, symptom-picker, tab-bar, table, tabs, textarea, timeline-entry, toast, tooltip, value
--opsin-space-41remThe default gap between elements inside a card.accordion, avatar, badge, button, callout, care-card, consent-sheet, dialog, divider, empty-state, goal-ring, link, log-sheet, menu, popover, range-legend, result-card, scale-input, score-dial, segmented-control, sheet, surface, switch, tabs, timeline-entry, toast
--opsin-space-51.25remCard inner padding on a phone.avatar, button, consent-sheet, dialog, questionnaire, sheet, slider, switch, symptom-picker
--opsin-space-61.5remCard inner padding on a wide screen; gap between cards.avatar, card, dialog, disclaimer-note, empty-state, field, log-sheet, skeleton, stepper, switch, tab-bar, trend-sparkline
--opsin-space-82remGap between sections within a screen.avatar, body-map, disclaimer-note, popover, range-bar, reading-input, sheet, stepper
--opsin-space-102.5remSpace above a section heading.avatar, sheet, skeleton, switch
--opsin-space-123remGap between major regions of a screen.avatar, trend-sparkline
--opsin-space-164remTop of a screen below the safe area; the space a consent sheet leaves above its first question.dialog, skeleton
--opsin-space-205remEmpty-state vertical rhythm.no component
--opsin-space-246remThe largest step. Beyond this, use a layout, not a gap.no component
--opsin-space-px0.0625remHairline borders only.body-map, trend-sparkline
--opsin-space-0-50.125remOptical nudges. Not a layout step.badge, dose-tracker, radio-group, range-legend, result-card, scroll-area, segmented-control, status-pill, stepper, switch, tab-bar, tabs, term, trend-sparkline
--opsin-target-minimum2.75remThe opsinjs floor for any interactive control, applied to the hit area rather than to the visible box.accordion, body-map, button, card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, field, icon-button, menu, metric-tile, number-field, popover, questionnaire, radio-group, reading-input, scale-input, segmented-control, select, sheet, slider, source-citation, switch, symptom-picker, tab-bar, tabs, textarea, toast, tooltip
--opsin-target-comfortable3remThe default for a primary action in the product theme.dialog
--opsin-target-generous3.5remA single primary action on a consent, escalation or emergency surface, where a mis-tap has a real cost.no component
--opsin-target-separation0.5remThe minimum gap between two adjacent targets whose visible boxes are smaller than 44px. It is a floor for undersized targets only. Two targets that both meet the 44px minimum are outside its scope, and separating those is governed by the differing cost of a mis-tap between them rather than by a fixed gap.alert-banner, card, care-card, consent-sheet, result-card
--opsin-gutter-phone16pxThe screen gutter in the phone responsive mode.no component
--opsin-gutter-tablet24pxThe screen gutter in the tablet responsive mode.no component
--opsin-gutter-wide32pxThe screen gutter in the wide responsive mode.no component
--opsin-measure-tight45chA caption or a legend.dialog, empty-state, goal-ring, popover, tooltip
--opsin-measure-comfortable66chThe maximum line length for prose anywhere in the product, including a disclaimer nobody wants to read.disclaimer-note, result-card, score-dial, sheet, source-citation, term
--opsin-measure-wide80chCode and machine output only.no component
--opsin-graphic-dial17remThe maximum width of the ScoreDial arc. This is 16em against the product body of 1.0625rem, which is 17rem and lands on the 4px grid at 68 steps, and it yields to w-full when the column is narrower.goal-ring, score-dial
--opsin-safe-topenv(safe-area-inset-top, 0px)The top safe-area inset, for installed web apps where browser chrome does not protect the edge.no component
--opsin-safe-rightenv(safe-area-inset-right, 0px)The right safe-area inset, for installed web apps where browser chrome does not protect the edge.sheet
--opsin-safe-bottomenv(safe-area-inset-bottom, 0px)The bottom safe-area inset, for installed web apps where browser chrome does not protect the edge.dialog, sheet
--opsin-safe-leftenv(safe-area-inset-left, 0px)The left safe-area inset, for installed web apps where browser chrome does not protect the edge.sheet

Type

The product theme deliberately declares a platform UI font stack rather than a webfont. A patient-facing app inherits the reader's own text-size setting, and shipping a face that ignores it is the most common way a "200% text" claim stops being true. Dynamic type has the argument in full.

TokenWhat it controlsUsed by
--opsin-font-sanssystem-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serifThe sans family stack.no component
--opsin-font-monoui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospaceThe mono family stack.no component
--opsin-font-numericsystem-ui, -apple-system, 'Segoe UI', Roboto, sans-serifThe numeric family stack.result-card, value
--opsin-text-large-title-size2.125remThe largest step in the scale, reserved for a surface whose entire subject is one number or one word. No component in the system sets a value at this step yet.no component
--opsin-text-large-title-leading1.206Line height for largeTitle, unitless so it scales with the size.no component
--opsin-text-large-title-tracking-0.011emLetter spacing for largeTitle.no component
--opsin-text-large-title-weight700Font weight for largeTitle.no component
--opsin-text-title1-size1.75remA screen title, and the hero treatment a value's number takes at size display.result-card, value
--opsin-text-title1-leading1.214Line height for title1, unitless so it scales with the size.result-card, value
--opsin-text-title1-tracking-0.009emLetter spacing for title1.result-card, value
--opsin-text-title1-weight700Font weight for title1.result-card, value
--opsin-text-title2-size1.375remSection title; the floor for a primary health value inside a card.goal-ring
--opsin-text-title2-leading1.273Line height for title2, unitless so it scales with the size.goal-ring
--opsin-text-title2-tracking-0.006emLetter spacing for title2.goal-ring
--opsin-text-title2-weight600Font weight for title2.goal-ring
--opsin-text-title3-size1.25remA Dialog or Sheet title, and the unit beside a value's number at size display.dialog, questionnaire, reading-input, sheet, value
--opsin-text-title3-leading1.25Line height for title3, unitless so it scales with the size.dialog, questionnaire, reading-input, sheet, value
--opsin-text-title3-tracking-0.004emLetter spacing for title3.dialog, questionnaire, reading-input, sheet, value
--opsin-text-title3-weight600Font weight for title3.dialog, questionnaire, reading-input, sheet, value
--opsin-text-headline-size1.0625remAn emphasised line of body text, such as a status sentence or a question in a form.accordion, alert-banner, avatar, button, callout, card, care-card, empty-state, field, log-sheet, menu, popover, range-bar, reading-input, result-card, scale-input, segmented-control, status-pill, surface, tabs, textarea, timeline-entry, toast, trend-sparkline, value
--opsin-text-headline-leading1.294Line height for headline, unitless so it scales with the size.accordion, alert-banner, avatar, button, callout, card, care-card, empty-state, field, log-sheet, menu, popover, range-bar, reading-input, result-card, scale-input, segmented-control, status-pill, surface, tabs, textarea, timeline-entry, toast, trend-sparkline, value
--opsin-text-headline-tracking-0.003emLetter spacing for headline.accordion, alert-banner, avatar, button, callout, card, care-card, empty-state, field, log-sheet, menu, popover, range-bar, reading-input, result-card, scale-input, segmented-control, status-pill, surface, tabs, textarea, timeline-entry, toast, trend-sparkline, value
--opsin-text-headline-weight600Font weight for headline.accordion, alert-banner, avatar, button, callout, card, care-card, empty-state, field, log-sheet, menu, popover, range-bar, reading-input, result-card, scale-input, segmented-control, status-pill, surface, tabs, textarea, timeline-entry, toast, trend-sparkline, value
--opsin-text-body-size1.0625remEverything a reader reads as prose. The anchor of the scale.accordion, alert-banner, badge, callout, card, care-card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, divider, dose-tracker, empty-state, field, link, menu, number-field, popover, questionnaire, radio-group, range-bar, range-legend, reading-input, relative-time, result-card, score-dial, scroll-area, select, sheet, surface, switch, symptom-picker, table, tabs, term, textarea, timeline-entry, toast, tooltip, trend-sparkline, value, visually-hidden
--opsin-text-body-leading1.294Line height for body, unitless so it scales with the size.accordion, alert-banner, badge, callout, card, care-card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, divider, dose-tracker, empty-state, field, link, menu, number-field, popover, questionnaire, radio-group, range-bar, range-legend, reading-input, relative-time, result-card, score-dial, scroll-area, select, sheet, skeleton, surface, switch, symptom-picker, table, tabs, term, textarea, timeline-entry, toast, tooltip, trend-sparkline, value, visually-hidden
--opsin-text-body-tracking-0.003emLetter spacing for body.accordion, alert-banner, badge, callout, card, care-card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, divider, dose-tracker, empty-state, field, link, menu, number-field, popover, questionnaire, radio-group, range-bar, range-legend, reading-input, relative-time, result-card, score-dial, scroll-area, select, sheet, surface, switch, symptom-picker, table, tabs, term, textarea, timeline-entry, toast, tooltip, trend-sparkline, value, visually-hidden
--opsin-text-body-weight400Font weight for body.accordion, alert-banner, badge, callout, card, care-card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, divider, dose-tracker, empty-state, field, link, menu, number-field, popover, questionnaire, radio-group, range-bar, range-legend, reading-input, relative-time, result-card, score-dial, scroll-area, select, sheet, surface, switch, symptom-picker, table, tabs, term, textarea, timeline-entry, toast, tooltip, trend-sparkline, value, visually-hidden
--opsin-text-callout-size1remSecondary prose inside a card or a callout.dialog, sheet, toast
--opsin-text-callout-leading1.313Line height for callout, unitless so it scales with the size.dialog, sheet, toast
--opsin-text-callout-tracking-0.002emLetter spacing for callout.dialog, sheet, toast
--opsin-text-callout-weight400Font weight for callout.dialog, sheet, toast
--opsin-text-subheadline-size0.9375remA supporting line under a title.button, care-card, empty-state, goal-ring, metric-tile, progress, radio-group, score-dial, segmented-control, slider, spinner, status-pill, stepper, table
--opsin-text-subheadline-leading1.333Line height for subheadline, unitless so it scales with the size.button, care-card, empty-state, goal-ring, metric-tile, progress, radio-group, score-dial, segmented-control, slider, spinner, status-pill, stepper, table
--opsin-text-subheadline-tracking-0.001emLetter spacing for subheadline.button, care-card, empty-state, goal-ring, metric-tile, progress, radio-group, score-dial, segmented-control, slider, spinner, status-pill, stepper, table
--opsin-text-subheadline-weight400Font weight for subheadline.button, care-card, empty-state, goal-ring, metric-tile, progress, radio-group, score-dial, segmented-control, slider, spinner, status-pill, stepper, table
--opsin-text-subheadline-emphasis-size0.9375remThe emphasised twin of subheadline, for a compact control's label, where the control must read as narrower and never as quieter.avatar, button
--opsin-text-subheadline-emphasis-leading1.333Line height for subheadlineEmphasis, unitless so it scales with the size.avatar, button
--opsin-text-subheadline-emphasis-tracking-0.001emLetter spacing for subheadlineEmphasis.avatar, button
--opsin-text-subheadline-emphasis-weight600Font weight for subheadlineEmphasis.avatar, button
--opsin-text-footnote-size0.8125remProvenance: who measured this, when, with what.body-map, card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, divider, dose-tracker, goal-ring, log-sheet, menu, metric-tile, questionnaire, radio-group, range-bar, range-legend, result-card, scale-input, score-dial, sheet, source-citation, status-pill, stepper, switch, symptom-picker, table, timeline-entry, toast, tooltip, trend-sparkline
--opsin-text-footnote-leading1.385Line height for footnote, unitless so it scales with the size.body-map, card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, divider, dose-tracker, goal-ring, log-sheet, menu, metric-tile, questionnaire, radio-group, range-bar, range-legend, result-card, scale-input, score-dial, sheet, source-citation, status-pill, stepper, switch, symptom-picker, table, timeline-entry, toast, tooltip, trend-sparkline
--opsin-text-footnote-tracking0emLetter spacing for footnote.body-map, card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, divider, dose-tracker, goal-ring, log-sheet, menu, metric-tile, questionnaire, radio-group, range-bar, range-legend, result-card, scale-input, score-dial, sheet, source-citation, status-pill, stepper, switch, symptom-picker, table, timeline-entry, toast, tooltip, trend-sparkline
--opsin-text-footnote-weight400Font weight for footnote.body-map, card, checkbox, combobox, consent-sheet, dialog, disclaimer-note, divider, dose-tracker, goal-ring, log-sheet, menu, metric-tile, questionnaire, radio-group, range-bar, range-legend, result-card, scale-input, score-dial, sheet, source-citation, status-pill, stepper, switch, symptom-picker, table, timeline-entry, toast, tooltip, trend-sparkline
--opsin-text-caption1-size0.75remAxis labels, legend text, and legal text.avatar, badge, consent-sheet, dose-tracker, range-bar, skeleton, tab-bar, visually-hidden
--opsin-text-caption1-leading1.333Line height for caption1, unitless so it scales with the size.avatar, badge, consent-sheet, dose-tracker, range-bar, skeleton, tab-bar, visually-hidden
--opsin-text-caption1-tracking0.002emLetter spacing for caption1.avatar, badge, consent-sheet, dose-tracker, range-bar, skeleton, tab-bar, visually-hidden
--opsin-text-caption1-weight400Font weight for caption1.avatar, badge, consent-sheet, dose-tracker, range-bar, skeleton, tab-bar, visually-hidden
--opsin-text-caption2-size0.6875remThe smallest text the system permits, and only for text that repeats a label already present elsewhere. Never the only place a fact appears.no component
--opsin-text-caption2-leading1.182Line height for caption2, unitless so it scales with the size.no component
--opsin-text-caption2-tracking0.005emLetter spacing for caption2.no component
--opsin-text-caption2-weight500Font weight for caption2.no component
--opsin-numeralstabular-numsEvery component that renders a number sets this, so a changing value does not shift its own layout.no component

Tier 1 status ramp

Four ordinal levels, and a fifth stem for the absence of one. Each has a surface, a line, an ink and an accent slot, in light and in dark, with a Display-P3 escalation that raises chroma at identical lightness.

These are listed so you can read them, not so you can set them. The token ids are steady, watch, attention and urgent, plus unknown. unknown is not a fifth level but the state where there is no reading to make a claim about. What each level means, who is allowed to assign it, and what word a reader actually sees are decided in Clinical status semantics, which is canonical. A token id is a machine identifier and is never the label you put in front of a patient: attention names a ramp, "Needs attention" is the word.

The five stems are inside the colour table above, eleven primitive steps and four roles each. Grouped on their own, with each row's light and dark value side by side, they are at Reference → Tokens; their measured contrast is in the report at the foot of this page.

Tier 1 category ramps

Six identity ramps: sleep, heart, activity, nutrition, mind, labs. Deliberately low-chroma, because a category colour carries no verdict and must not be readable as one. Note the asymmetry with the status axis in Tailwind: the bare category-<name> is the accent role, while the bare status-<level> is the line. Getting that backwards puts an identity fill where a boundary belongs, which is the first way a category colour starts reading as a verdict.

The six ramps are inside the colour table above; grouped on their own, with light and dark side by side, they are at Reference → Tokens.

Tier 1 material ladder

Six rungs, from a flat page background to a scrim. Each rung is a background, a blur radius, a border and a shadow, and each collapses to an opaque fallback under prefers-reduced-transparency.

TokenWhat it controlsUsed by
--opsin-material-canvas-tintvar(--opsin-neutral-0)Rung 0 (canvas): the tint over what is behind it. The application background. Nothing is behind it, so nothing shows through it.surface
--opsin-material-canvas-tint-alpha1Rung 0 (canvas): how opaque that tint is.surface
--opsin-material-canvas-blur0pxRung 0 (canvas): backdrop blur radius.surface
--opsin-material-canvas-saturation1Rung 0 (canvas): backdrop saturation multiplier.surface
--opsin-material-canvas-bordernoneRung 0 (canvas): the boundary.surface
--opsin-material-canvas-shadownoneRung 0 (canvas): the shadow that separates it from what is behind.surface
--opsin-material-canvas-scrim0Rung 0 (canvas): the minimum scrim opacity needed for text on this rung to clear the contrast floor.surface
--opsin-material-canvas-opaquevar(--opsin-neutral-0)Rung 0 (canvas): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported.surface
--opsin-material-inset-tintvar(--opsin-neutral-100)Rung 0.5 (inset): the tint over what is behind it. The recessed well inside a card: the explanatory note a Callout draws inside the reading it annotates, and the track a RangeBar lays its range along. Its fill sits below the card fill in both themes, so the well reads as carved into the card rather than lifted off it. In light it is neutral-100, one step under the white card, which is where a Callout already sat. In dark it is a value between the page and the card, below the card so the well is a recess and above the page so a Callout used on its own is still visible.callout, range-bar, surface
--opsin-material-inset-tint-alpha1Rung 0.5 (inset): how opaque that tint is.surface
--opsin-material-inset-blur0pxRung 0.5 (inset): backdrop blur radius.surface
--opsin-material-inset-saturation1Rung 0.5 (inset): backdrop saturation multiplier.surface
--opsin-material-inset-bordernoneRung 0.5 (inset): the boundary.surface
--opsin-material-inset-shadownoneRung 0.5 (inset): the shadow that separates it from what is behind.surface
--opsin-material-inset-scrim0Rung 0.5 (inset): the minimum scrim opacity needed for text on this rung to clear the contrast floor.surface
--opsin-material-inset-opaquevar(--opsin-neutral-100)Rung 0.5 (inset): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported.surface
--opsin-material-card-tintvar(--opsin-neutral-0)Rung 1 (card): the tint over what is behind it. The default home for a health value. Opaque, bounded by a line rather than a shadow, and the rung every ResultCard, MetricTile and RangeBar sits on.surface
--opsin-material-card-tint-alpha1Rung 1 (card): how opaque that tint is.surface
--opsin-material-card-blur0pxRung 1 (card): backdrop blur radius.surface
--opsin-material-card-saturation1Rung 1 (card): backdrop saturation multiplier.surface
--opsin-material-card-bordervar(--opsin-neutral-200)Rung 1 (card): the boundary.surface
--opsin-material-card-shadownoneRung 1 (card): the shadow that separates it from what is behind.surface
--opsin-material-card-scrim0Rung 1 (card): the minimum scrim opacity needed for text on this rung to clear the contrast floor.surface
--opsin-material-card-opaquevar(--opsin-neutral-0)Rung 1 (card): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported.surface
--opsin-material-raised-tintvar(--opsin-neutral-0)Rung 2 (raised): the tint over what is behind it. Menus, popovers, tooltips, a dragged card. Still opaque: the shadow, not translucency, is what says 'above'.surface
--opsin-material-raised-tint-alpha1Rung 2 (raised): how opaque that tint is.surface
--opsin-material-raised-blur0pxRung 2 (raised): backdrop blur radius.surface
--opsin-material-raised-saturation1Rung 2 (raised): backdrop saturation multiplier.surface
--opsin-material-raised-bordervar(--opsin-neutral-200)Rung 2 (raised): the boundary.surface
--opsin-material-raised-shadow0 1px 2px oklch(0 0 0 / 0.06), 0 4px 12px oklch(0 0 0 / 0.06)Rung 2 (raised): the shadow that separates it from what is behind.surface
--opsin-material-raised-scrim0Rung 2 (raised): the minimum scrim opacity needed for text on this rung to clear the contrast floor.surface
--opsin-material-raised-opaquevar(--opsin-neutral-0)Rung 2 (raised): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported.surface
--opsin-material-sheet-tintvar(--opsin-neutral-0)Rung 3 (sheet): the tint over what is behind it. A bottom sheet or side panel the reader can dismiss by dragging. Its tint is now near opaque, so the panel lands close to the card colour and a white control inside it no longer floats above a greyer surface. What keeps the covered page recognisable is the scrim around the panel rather than the page reading through the panel, because at this opacity very little of the page shows through the tint.surface
--opsin-material-sheet-tint-alpha0.93Rung 3 (sheet): how opaque that tint is.surface
--opsin-material-sheet-blur20pxRung 3 (sheet): backdrop blur radius.surface
--opsin-material-sheet-saturation1.6Rung 3 (sheet): backdrop saturation multiplier.surface
--opsin-material-sheet-borderoklch(0 0 0 / 0.08)Rung 3 (sheet): the boundary.surface
--opsin-material-sheet-shadow0 -1px 2px oklch(0 0 0 / 0.05)Rung 3 (sheet): the shadow that separates it from what is behind.sheet, surface
--opsin-material-sheet-scrim0.93Rung 3 (sheet): the minimum scrim opacity needed for text on this rung to clear the contrast floor.surface
--opsin-material-sheet-opaquevar(--opsin-neutral-0)Rung 3 (sheet): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported.surface
--opsin-material-overlay-tintvar(--opsin-neutral-0)Rung 4 (overlay): the tint over what is behind it. A pinned toolbar, a tab bar and a floating action bar are all chrome that content scrolls beneath.surface
--opsin-material-overlay-tint-alpha0.74Rung 4 (overlay): how opaque that tint is.surface
--opsin-material-overlay-blur28pxRung 4 (overlay): backdrop blur radius.surface
--opsin-material-overlay-saturation1.8Rung 4 (overlay): backdrop saturation multiplier.surface
--opsin-material-overlay-borderoklch(0 0 0 / 0.10)Rung 4 (overlay): the boundary.surface
--opsin-material-overlay-shadow0 8px 32px oklch(0 0 0 / 0.12)Rung 4 (overlay): the shadow that separates it from what is behind.combobox, menu, popover, select, surface, toast, tooltip
--opsin-material-overlay-scrim0.74Rung 4 (overlay): the minimum scrim opacity needed for text on this rung to clear the contrast floor.surface
--opsin-material-overlay-opaquevar(--opsin-neutral-0)Rung 4 (overlay): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported.surface
--opsin-material-scrim-tintvar(--opsin-neutral-950)Rung 5 (scrim): the tint over what is behind it. The dimming layer behind a modal dialog or a consent sheet. Its job is to remove the page from consideration, not to look like glass.surface
--opsin-material-scrim-tint-alpha0.44Rung 5 (scrim): how opaque that tint is.surface
--opsin-material-scrim-blur2pxRung 5 (scrim): backdrop blur radius.surface
--opsin-material-scrim-saturation1Rung 5 (scrim): backdrop saturation multiplier.surface
--opsin-material-scrim-bordernoneRung 5 (scrim): the boundary.surface
--opsin-material-scrim-shadownoneRung 5 (scrim): the shadow that separates it from what is behind.surface
--opsin-material-scrim-scrim0.44Rung 5 (scrim): the minimum scrim opacity needed for text on this rung to clear the contrast floor.surface
--opsin-material-scrim-opaqueoklch(0.205 0.006 250 / 0.72)Rung 5 (scrim): the opaque substitute used under prefers-reduced-transparency and where backdrop-filter is unsupported.surface

Tier 1 motion

Two families in one table. The first is a plain scale: the duration-* steps and the ease-standard, ease-enter and ease-exit curves. The second is the springs, where a sampled CSS linear() easing and the settling time measured from that spring's own parameters are emitted as a pair and have to be used together, since a spring easing on somebody else's duration is not that spring any more. Reduced motion is answered per token rather than by one kill switch. In the generated block each spring easing resolves to the plain linear keyword, ease-standard flattens with them, and every spring duration drops to the fallback that spring's own entry in tokens/motion.json declares. That fallback is nothing at all for the springs a reader initiates or that carry a health value, and a short cross-fade for the ones announcing that something arrived, because collapsing those to a single instant would remove the cue that a value changed. The plain duration-* steps have no per-token answer and are flattened to 1ms further down the cascade, in app/product.css; the enter and exit curves are never overridden, having no overshoot to take out.

TokenWhat it controlsUsed by
--opsin-ease-spring-snaplinear(0, 0.0715, 0.2271, 0.4053, 0.5722, 0.7119, 0.8198, 0.8978, 0.9505, 0.9836, 1.0025, 1.0118, 1.015, 1.0147, 1.0126, 1.01, 1.0073, 1.0051, 1.0033, 1.0019, 1)Direct manipulation only: a switch the reader just flipped, a segmented control, a pressed button settling. It overshoots by 1.5%. That is enough to feel physical but not enough to look playful. The 1.5% is the largest overshoot in the system but not the only one: `spring-settle` overshoots by 0.88%. Only `spring-calm` and `spring-sheet` reach their target without passing it.no component
--opsin-duration-spring-snap283msSettle time for the spring-snap spring, measured from its own parameters.no component
--opsin-ease-spring-settlelinear(0, 0.0742, 0.2328, 0.4113, 0.5758, 0.7116, 0.8157, 0.8905, 0.9412, 0.9736, 0.9928, 1.003, 1.0076, 1.0088, 1.0082, 1.0068, 1.0053, 1.0038, 1.0026, 1.0017, 1)The workhorse for chrome: popovers, tooltips, menus, chips appearing and disappearing.no component
--opsin-duration-spring-settle382msSettle time for the spring-settle spring, measured from its own parameters.no component
--opsin-ease-spring-calmlinear(0, 0.0829, 0.2457, 0.4157, 0.5642, 0.6832, 0.774, 0.841, 0.8893, 0.9236, 0.9476, 0.9643, 0.9757, 0.9836, 0.9889, 0.9926, 0.995, 0.9967, 0.9978, 0.9985, 1)A health value that changes while it is already on screen: a bar re-filling from one reading to the next, a dial travelling between two values the reader has already been shown. Never a first paint and never a first reveal. A value arrives at its final figure, with no count-up, no dial sweep and no line drawing itself in (health/motion-in-health-ui rule 2). Slightly overdamped (zeta just over 1) so it never overshoots and never bounces.no component
--opsin-duration-spring-calm550msSettle time for the spring-calm spring, measured from its own parameters.no component
--opsin-ease-spring-sheetlinear(0, 0.0881, 0.2576, 0.431, 0.5798, 0.6971, 0.7854, 0.8497, 0.8958, 0.9282, 0.9508, 0.9664, 0.9771, 0.9845, 0.9895, 0.9929, 0.9952, 0.9967, 0.9978, 0.9985, 1)Large surfaces travelling a long distance: sheets, dialogs, full-screen pushes. Overdamped, because a sheet that bounces at the top of its travel reads as a dropped object.dialog, sheet
--opsin-duration-spring-sheet483msSettle time for the spring-sheet spring, measured from its own parameters.dialog, sheet
--opsin-ease-standardcubic-bezier(0.2, 0, 0, 1)Non-spring transitions where a spring would be overkill: colour, opacity, border. Fast out, slow in.accordion, body-map, button, checkbox, combobox, consent-sheet, dialog, menu, number-field, popover, progress, radio-group, scale-input, scroll-area, segmented-control, select, sheet, skeleton, slider, switch, symptom-picker, tab-bar, tabs, textarea, toast, tooltip
--opsin-ease-entercubic-bezier(0.05, 0.7, 0.1, 1)Something arriving from off-screen or from nothing. Decelerating, because an arrival should feel like it is coming to rest.no component
--opsin-ease-exitcubic-bezier(0.3, 0, 0.8, 0.15)Something leaving. Accelerating and shorter than its enter, because a reader does not need to watch a dismissal finish.dialog
--opsin-duration-instant80msState change with no travel: hover tint, focus ring, checkbox tick.no component
--opsin-duration-fast140msSmall elements moving a small distance.accordion, body-map, button, checkbox, combobox, consent-sheet, dialog, menu, number-field, popover, progress, radio-group, scale-input, scroll-area, segmented-control, select, sheet, skeleton, slider, switch, symptom-picker, tab-bar, tabs, textarea, toast, tooltip
--opsin-duration-base220msThe default for chrome that is not spring-driven.dialog, sheet
--opsin-duration-slow360msLayout change: a list reflowing, a card expanding.no component
--opsin-duration-deliberate560msThe ceiling on a transition a reader is waiting on, such as a first-run reveal or a consent sheet, where the point is that the reader notices. No such transition may exceed it. A looping placeholder period, like the skeleton shimmer, is bounded instead by its iteration count, so a single sweep of it may run longer.no component
--opsin-duration-shimmer1600msOne sweep of a loading placeholder's sheen. This is a period, the time for the sheen to make one pass across the bar, and not a travel time. At 1600ms the movement reads as calm rather than as urgency, which the first motion rule requires of anything that is not a value the reader controls. Its consumer pairs it with a finite iteration count so the total motion stays under the five seconds at which WCAG 2.2 SC 2.2.2 engages.skeleton

Measured contrast

Not a token table: the measured result of every pair the tables above imply, in both themes, against the published floor.

228 token pairs measured, 142 at or above their floor and 74 advisory. The partition records 12 required pairs below floor, but 28 of the advisory pairs measure below their floor too, and 12 of those are non-text marks (accents and hairlines) that sit below the non-text floor of APCA Lc 45 and WCAG 3:1. Read the 12 figure with the 12 beside it.

The gate that fails when one of these figures becomes untrue is pnpm run contrast:verify, which runs in the nightly workflow, not the pull-request check in ci.yml. A green pull request proves the committed numbers match a fresh run, not that every one clears its floor.

PairThemeAPCA LcWCAG 2.2Floor
activity ink on activity surfacelight88.59.61:1Pass
activity ink on activity surfacedark-83.112.58:1Pass
activity line on activity surfacelight67.64.42:1Pass
activity line on activity surfacedark-69.09.93:1Pass
activity accent on activity surfacelight44.12.34:1Below floor
activity accent on activity surfacedark-53.37.26:1Pass
activity accent on the pagelight46.12.41:1Below floor
activity accent on the pagedark-54.68.22:1Pass
activity ink on the pagelight90.59.90:1Pass
activity ink on the pagedark-84.414.24:1Pass
activity line on the bandlight66.14.32:1Pass
activity line on the banddark-67.48.60:1Pass
heart ink on heart surfacelight89.110.51:1Pass
heart ink on heart surfacedark-80.512.55:1Pass
heart line on heart surfacelight70.45.05:1Pass
heart line on heart surfacedark-64.59.46:1Pass
heart accent on heart surfacelight60.33.64:1Pass
heart accent on heart surfacedark-48.26.68:1Pass
heart accent on the pagelight63.23.81:1Pass
heart accent on the pagedark-49.57.27:1Pass
heart ink on the pagelight92.010.97:1Pass
heart ink on the pagedark-81.813.65:1Pass
heart line on the bandlight69.95.01:1Pass
heart line on the banddark-62.87.88:1Pass
labs ink on labs surfacelight88.89.78:1Pass
labs ink on labs surfacedark-83.112.69:1Pass
labs line on labs surfacelight67.94.51:1Pass
labs line on labs surfacedark-68.49.90:1Pass
labs accent on labs surfacelight59.33.49:1Pass
labs accent on labs surfacedark-52.77.21:1Pass
labs accent on the pagelight61.53.60:1Pass
labs accent on the pagedark-53.98.07:1Pass
labs ink on the pagelight90.910.09:1Pass
labs ink on the pagedark-84.314.22:1Pass
labs line on the bandlight66.54.42:1Pass
labs line on the banddark-66.78.49:1Pass
mind ink on mind surfacelight89.910.36:1Pass
mind ink on mind surfacedark-81.312.56:1Pass
mind line on mind surfacelight70.74.94:1Pass
mind line on mind surfacedark-65.49.54:1Pass
mind accent on mind surfacelight60.03.55:1Pass
mind accent on mind surfacedark-49.16.77:1Pass
mind accent on the pagelight62.83.70:1Pass
mind accent on the pagedark-50.07.42:1Pass
mind ink on the pagelight92.710.80:1Pass
mind ink on the pagedark-82.213.77:1Pass
mind line on the bandlight70.04.89:1Pass
mind line on the banddark-63.48.00:1Pass
nutrition ink on nutrition surfacelight89.310.06:1Pass
nutrition ink on nutrition surfacedark-81.912.55:1Pass
nutrition line on nutrition surfacelight69.74.78:1Pass
nutrition line on nutrition surfacedark-66.79.66:1Pass
nutrition accent on nutrition surfacelight39.22.11:1Below floor
nutrition accent on nutrition surfacedark-50.26.88:1Pass
nutrition accent on the pagelight41.82.19:1Below floor
nutrition accent on the pagedark-51.37.63:1Pass
nutrition ink on the pagelight91.910.44:1Pass
nutrition ink on the pagedark-82.913.91:1Pass
nutrition line on the bandlight68.84.71:1Pass
nutrition line on the banddark-64.88.19:1Pass
sleep ink on sleep surfacelight89.610.19:1Pass
sleep ink on sleep surfacedark-81.912.60:1Pass
sleep line on sleep surfacelight70.04.80:1Pass
sleep line on sleep surfacedark-66.49.65:1Pass
sleep accent on sleep surfacelight64.64.05:1Pass
sleep accent on sleep surfacedark-50.46.94:1Pass
sleep accent on the pagelight67.34.22:1Pass
sleep accent on the pagedark-51.47.67:1Pass
sleep ink on the pagelight92.310.60:1Pass
sleep ink on the pagedark-82.913.92:1Pass
sleep line on the bandlight69.14.75:1Pass
sleep line on the banddark-64.58.16:1Pass
attention ink on attention surfacelight89.410.35:1Pass
attention ink on attention surfacedark-81.112.54:1Pass
attention line on attention surfacelight70.04.90:1Pass
attention line on attention surfacedark-65.59.54:1Pass
attention accent on attention surfacelight65.14.18:1Pass
attention accent on attention surfacedark-49.46.80:1Pass
attention ink on the attention bandlight22.42.48:1Below floor
attention ink on the attention banddark-31.01.84:1Below floor
attention accent on the pagelight67.84.35:1Pass
attention accent on the pagedark-50.57.47:1Pass
attention ink on the pagelight92.210.77:1Pass
attention ink on the pagedark-82.313.76:1Pass
steady ink on steady surfacelight88.39.62:1Pass
steady ink on steady surfacedark-82.912.56:1Pass
steady line on steady surfacelight67.54.45:1Pass
steady line on steady surfacedark-68.99.91:1Pass
steady accent on steady surfacelight56.43.21:1Pass
steady accent on steady surfacedark-53.27.25:1Pass
steady ink on the steady bandlight30.03.00:1Below floor
steady ink on the steady banddark-28.81.73:1Below floor
steady accent on the pagelight58.63.31:1Pass
steady accent on the pagedark-54.58.19:1Pass
steady ink on the pagelight90.69.94:1Pass
steady ink on the pagedark-84.214.20:1Pass
unknown ink on unknown surfacelight89.59.98:1Pass
unknown ink on unknown surfacedark-82.212.56:1Pass
unknown line on unknown surfacelight69.24.64:1Pass
unknown line on unknown surfacedark-67.29.72:1Pass
unknown accent on unknown surfacelight43.12.29:1Below floor
unknown accent on unknown surfacedark-51.37.05:1Pass
unknown accent on the pagelight45.62.37:1Below floor
unknown accent on the pagedark-52.37.84:1Pass
unknown ink on the pagelight92.010.34:1Pass
unknown ink on the pagedark-83.113.97:1Pass
urgent ink on urgent surfacelight88.210.58:1Pass
urgent ink on urgent surfacedark-80.312.52:1Pass
urgent line on urgent surfacelight70.05.09:1Pass
urgent line on urgent surfacedark-64.89.52:1Pass
urgent accent on urgent surfacelight72.35.58:1Pass
urgent accent on urgent surfacedark-48.06.63:1Pass
urgent ink on the urgent bandlight13.91.90:1Below floor
urgent ink on the urgent banddark-31.51.89:1Below floor
urgent accent on the pagelight75.35.84:1Pass
urgent accent on the pagedark-49.57.24:1Pass
urgent ink on the pagelight91.211.06:1Pass
urgent ink on the pagedark-81.813.66:1Pass
watch ink on watch surfacelight89.410.02:1Pass
watch ink on watch surfacedark-82.112.58:1Pass
watch line on watch surfacelight69.54.72:1Pass
watch line on watch surfacedark-66.89.68:1Pass
watch accent on watch surfacelight61.73.72:1Pass
watch accent on watch surfacedark-50.76.95:1Pass
watch ink on the watch bandlight25.72.70:1Below floor
watch ink on the watch banddark-30.61.81:1Below floor
watch accent on the pagelight64.23.85:1Pass
watch accent on the pagedark-51.77.71:1Pass
watch ink on the pagelight91.910.38:1Pass
watch ink on the pagedark-83.113.95:1Pass
the band fill on the pagelight0.01.05:1Below floor
the band fill on the pagedark0.01.31:1Below floor
the band edge on the pagelight83.97.42:1Pass
the band edge on the pagedark-80.713.44:1Pass
the band edge on the bandlight80.47.05:1Pass
the band edge on the banddark-77.810.28:1Pass
body text on the pagelight101.617.18:1Pass
body text on the pagedark-100.518.00:1Pass
body text on a cardlight104.717.96:1Pass
body text on a carddark-99.616.32:1Pass
body text on the muted groundlight98.116.32:1Pass
body text on the muted grounddark-97.513.77:1Pass
secondary text on the pagelight83.97.42:1Pass
secondary text on the pagedark-80.713.44:1Pass
secondary text on a cardlight87.07.76:1Pass
secondary text on a carddark-79.812.19:1Pass
secondary text on the muted groundlight80.47.05:1Pass
secondary text on the muted grounddark-77.810.28:1Pass
a hairline boundary on the pagelight19.31.41:1Below floor
a hairline boundary on the pagedark-8.21.90:1Below floor
a hairline boundary on a cardlight22.41.47:1Below floor
a hairline boundary on a carddark-7.31.72:1Below floor
a hairline boundary on the muted groundlight15.81.34:1Below floor
a hairline boundary on the muted grounddark0.01.45:1Below floor
a placeholder boundary on the pagelight69.84.52:1Pass
a placeholder boundary on the pagedark-51.07.62:1Pass
a placeholder boundary on a cardlight72.84.72:1Pass
a placeholder boundary on a carddark-50.16.91:1Pass
a placeholder boundary on the muted groundlight66.24.29:1Pass
a placeholder boundary on the muted grounddark-48.15.83:1Pass
a placeholder fill on the pagelight8.31.18:1Below floor
a placeholder fill on the pagedark-8.21.90:1Below floor
a placeholder fill on a cardlight11.41.23:1Below floor
a placeholder fill on a carddark-7.31.72:1Below floor
a placeholder fill on the muted groundlight0.01.12:1Below floor
a placeholder fill on the muted grounddark0.01.45:1Below floor
a placeholder boundary on its own filllight58.93.84:1Pass
a placeholder boundary on its own filldark-41.54.01:1Below floor
the card hairline on the pagelight69.84.52:1Pass
the card hairline on the pagedark-28.74.19:1Below floor
the card hairline on a cardlight72.84.72:1Pass
the card hairline on a carddark-27.93.80:1Below floor
the focus ring on the pagelight70.04.62:1Pass
the focus ring on the pagedark-47.06.91:1Pass
the focus ring on a cardlight73.14.83:1Pass
the focus ring on a carddark-46.26.27:1Pass
body text on the canvas materiallight104.717.96:1Pass
body text on the canvas materialdark-100.518.00:1Pass
body text on the inset materiallight98.116.32:1Pass
body text on the inset materialdark-100.117.18:1Pass
body text on the card materiallight104.717.96:1Pass
body text on the card materialdark-99.616.32:1Pass
body text on the raised materiallight104.717.96:1Pass
body text on the raised materialdark-97.513.77:1Pass
body text on the sheet material over the darkest backdroplight99.916.76:1Pass
body text on the sheet material over the darkest backdropdark-99.716.47:1Pass
body text on the sheet material over the lightest backdroplight104.717.96:1Pass
body text on the sheet material over the lightest backdropdark-80.86.07:1Pass
body text on the overlay material over the darkest backdroplight85.913.48:1Pass
body text on the overlay material over the darkest backdropdark-99.916.77:1Pass
body text on the overlay material over the lightest backdroplight104.717.96:1Pass
body text on the overlay material over the lightest backdropdark-54.22.69:1Below floor
activity ink on the card materiallight93.610.34:1Pass
activity ink on the card materialdark-83.512.91:1Pass
heart ink on the card materiallight95.111.47:1Pass
heart ink on the card materialdark-80.912.38:1Pass
labs ink on the card materiallight94.010.54:1Pass
labs ink on the card materialdark-83.512.90:1Pass
mind ink on the card materiallight95.711.28:1Pass
mind ink on the card materialdark-81.412.49:1Pass
nutrition ink on the card materiallight95.010.91:1Pass
nutrition ink on the card materialdark-82.012.61:1Pass
sleep ink on the card materiallight95.411.08:1Pass
sleep ink on the card materialdark-82.112.62:1Pass
attention surface on the card materiallight0.01.09:1Below floor
attention surface on the card materialdark0.01.00:1Below floor
steady surface on the card materiallight0.01.08:1Below floor
steady surface on the card materialdark0.01.03:1Below floor
urgent surface on the card materiallight0.01.09:1Below floor
urgent surface on the card materialdark0.01.01:1Below floor
watch surface on the card materiallight0.01.08:1Below floor
watch surface on the card materialdark0.01.01:1Below floor
the focus ring on the attention surfacelight67.34.44:1Pass
the focus ring on the attention surfacedark-45.96.30:1Pass
attention ink on the neutral hover filllight88.610.23:1Pass
attention ink on the neutral hover filldark-79.310.53:1Pass
the focus ring on the steady surfacelight67.84.47:1Pass
the focus ring on the steady surfacedark-45.86.12:1Pass
steady ink on the neutral hover filllight87.09.43:1Pass
steady ink on the neutral hover filldark-81.310.86:1Pass
the focus ring on the urgent surfacelight67.04.42:1Pass
the focus ring on the urgent surfacedark-45.56.34:1Pass
urgent ink on the neutral hover filllight87.710.50:1Pass
urgent ink on the neutral hover filldark-78.910.46:1Pass
the focus ring on the watch surfacelight67.64.46:1Pass
the focus ring on the watch surfacedark-46.06.24:1Pass
watch ink on the neutral hover filllight88.39.86:1Pass
watch ink on the neutral hover filldark-80.110.67:1Pass

Data attributes

Tokens are not the only contract opsinjs stamps into the DOM. It also writes two families of data-* attribute, and they are not the same thing. One family is component anatomy, closed by ADR 0011 and stamped by the components themselves on their parts. The other is theme hooks, read by app/product.css and set on a container or a control by the host document or the preview route, never by a component. The two use different stems, the difference is not decorative, and a consumer who reaches for the wrong stem by analogy gets nothing back and no warning. So the whole vocabulary sits here, in one place.

Component anatomy: four names, closed. Every opsinjs component stamps these on its parts, and ADR 0011 fixes the vocabulary at four. This table copies that ADR by hand, so ADR 0011 is the authority: where the two disagree, ADR 0011 is right and this page is the stale copy.

AttributeWhere it appearsValue
data-slotevery part, without exceptionthe part name, kebab-cased, such as result-card-header
data-statusevery element whose colour, icon or word carries a clinical statussteady, watch, attention, urgent, unknown
data-categoryevery element tinted from the category axisone of the six health categories
data-opsinjs-valueevery element rendering a measurement a reader reads as their ownthe machine-readable value, or empty when the value is absent

Theme hooks. These are read by the product stylesheet, and the host sets them, not a component.

AttributeWhere it goesWhat it does
data-densitya containercompact scales spacing by 0.875; comfortable is the default and carries no attribute
data-text-sizethe root element125, 150 or 200 raises the root font size by that percentage, a real font-size change rather than a transform, so 200% reflows the whole page; 100 is the unstamped default
data-opsin-targeta linkopts a bare link into the tap-target floor that buttons, inputs, selects and text areas already meet

Mind the stem. The anatomy attributes are data-slot, data-status, data-category and the longer data-opsinjs-value, while the theme hooks include data-density and data-opsin-target on the shorter opsin stem, with data-text-size on its own stem. The tabular-figures rule keys off data-opsinjs-value, the anatomy attribute a component actually emits. app/product.css also accepts data-opsin-value as a courtesy alias, so a consumer who writes the theme stem by analogy with data-opsin-target still gets tabular figures rather than losing them without a word. There is deliberately no data-opsin-shape attribute: the squircle is set per component through the corner-shape property, because a fifth anatomy name would break the vocabulary ADR 0011 closes at four.