`Checkbox` — the one new design-system primitive the role editor needs, and the raw input it replaces
The design asset names exactly one new primitive for this whole story, and states why: packages/design-system/src/components/ui/ ships Switch and MultiSelectPicker and no Checkbox, and the product's only checkbox today is a raw unstyled <input type="checkbox"> inside WorkflowEditor.tsx. Twenty-eight of those on a settings page is not acceptable, and a Switch is the wrong grammar — a switch says this setting is on now, a checkbox says this is part of the set I am composing. This card adds the primitive so the editor card composes it instead of inventing it.
It goes in the PACKAGE, not the app. components/ui/*.tsx in the app root are re-export shims — "this surface moved into @motir/design-system (MOTIR-1527, docs/decisions/design-system-package.md)" — so the component is authored at packages/design-system/src/components/ui/Checkbox.tsx and reached through a one-line components/ui/Checkbox.tsx shim, exactly as Switch.tsx is. motir-core depends on the package as workspace:* and postinstall builds it, so nothing has to be published for the editor to consume this.
The states the design specifies
TWO, not three (Yue, 2026-08-09). A permission is held or it is not:
- not held — an empty box;
- held — an accent-filled box.
Each is a role="checkbox" with aria-checked and a label naming the state — Held / Not held — so the state is never carried by fill colour alone. It is a controlled component with an optional disabled arm (the story's server-refused, non-enforced keys render disabled rather than absent).
⚠️ An earlier revision had a THIRD state: a grey fill for a permission that came with the author's chosen base, distinct from the accent fill of one they added on top. It died with the stored base. The editor still lets an author START FROM a built-in, but nothing records the pick — so on a re-edit there is no base for anything to have come from, and the two fills could not be told apart honestly.
Scope boundary
In: the primitive, its barrel export, the app-root shim, its unit tests, and replacing the raw browser tick box in WorkflowEditor.tsx with it — a migration whose caller set is exactly one file, listed here so it is not left behind. Out: any change to Switch or MultiSelectPicker; a tri-state / indeterminate arm — DECIDED: none. MOTIR-2463 rejected the domain-level bulk toggle on the measurement (28 keys under 15 headings is a median of two rows each), so there is nothing for a tri-state to be tri-state about. Also out: publishing a new @motir/design-system version, which the workspace link makes unnecessary.
Acceptance criteria
packages/design-system/src/components/ui/Checkbox.tsxexports a controlledCheckboxwith, at minimum,checked,onChange,labelanddisabled— and no provenance discriminator, which a test pins so it cannot creep back.- The rendered element carries
role="checkbox",aria-checkedreflectingchecked,aria-disabledwhen disabled, and an accessible name that states the state in words (Held / Not held), so it survives with colour off. - Colour and shape come only from tokens the design names —
--radius-control,--el-border-strong,--el-accentfor the checked fill — with no Tier-0--color-*and no rawrounded-*/p-*/h-*. Bothdata-themestates render. - Keyboard: the control is focusable,
Spacetoggles it, and focus is visible using the same focus treatment the shippedSwitchuses. - It is exported from the package barrel (
packages/design-system/scripts/build-index-barrel.mjspicks it up) and re-exported by acomponents/ui/Checkbox.tsxshim in the app, soimport { Checkbox } from '@/components/ui/Checkbox'resolves. WorkflowEditor.tsx's raw<input type="checkbox">is replaced by the primitive with no behaviour change, and the workflow-settings tests still pass — after this card,git grep 'type="checkbox"' app componentsreturns nothing outside the primitive itself.- Unit tests cover: each of the two states renders its stated accessible name;
disableddoes not fireonChangeon click orSpace; and the toggle round-trips through the controlled prop.
Context refs
design/projects/design-notes.md— § Primitives composed, the rowPermission checkbox— NEW, its token roles, and the paragraph "The one new primitive"; and the colour-and-shape rules listing the three checkbox states and their labels.design/projects/roles-permissions.mock.html— panel 3, where the checkboxes are drawn in situ.packages/design-system/src/components/ui/Switch.tsx— the closest shipped primitive: its prop shape, focus treatment and test file are the pattern.components/ui/Switch.tsx— the one-line shim to copy.packages/design-system/scripts/build-index-barrel.mjs·packages/design-system/package.json— how a new component reaches the barrel and theworkspace:*consumer.app/(authed)/settings/project/workflow/_components/WorkflowEditor.tsx— the sole raw-checkbox call site this migrates.- The design card that specified it.