Designs and implements Primer-style interaction styling in Slint using explicit state dimensions, mutex groups, and `states [ ]` blocks instead of nested ternary expressions. Use when adding or refactoring Primer components under packages/primer-slint/, complex controls (hover, pressed, disabled, selected, focus), or when the user asks for Checkbox-like state handling or token-driven visuals.
packages/primer-slint/AGENTS.md — token globals, barrel exports, verification commands.packages/primer-slint/Checkbox/checkbox.slint — uses states [ … ] on a named sub-element (check-visuals) with conditions like filled-pressed when !root.disabled && root.filled && touch.pressed.disabled, pointer hover / pressed, selected, , (if modeled), theme ().checkedfocusColorSchemedisabled vs enabled interaction chain; rest vs hover vs pressed only when enabled).states [ … ] with named states and property overrides only — avoid repeating full ternary trees on every color: / background: in the tree.CheckboxTokens, ButtonTokens, PrimerColors, LayoutTokens per AGENTS; do not scatter new hex literals in components.Treat disabled as a top-level branch: when disabled is true, hover/pressed styling for pointers must not apply (or is undefined — pick one and keep it consistent).
When enabled, pointer interaction is typically mutually exclusive along one axis: rest → hover → pressed (only one “active” interaction state at a time for a given pointer).
Selection (e.g. list row selected, checkbox checked) is often orthogonal to hover/pressed: you combine “selected” with “hover” for visuals — in Slint, express that either as:
states list with combined predicates (selected-hover, selected-rest, …), orstates where the predicate orders from most specific to least (match Checkbox: disabled first, then combined branches).Order Slint state branches from most specific to least where the language requires it; put disabled early so it wins over hover.
stateDiagram-v2
[*] --> Enabled
[*] --> Disabled
Disabled --> Enabled : disabled becomes false
Enabled --> Disabled : disabled becomes true
Disabled and enabled interaction are mutually exclusive for pointer styling.
stateDiagram-v2
[*] --> Rest
Rest --> Hover : has-hover
Hover --> Rest : not has-hover
Hover --> Pressed : pressed
Rest --> Pressed : pressed
Pressed --> Hover : pressed and has-hover
Pressed --> Rest : not pressed
Selection does not replace the need for hover feedback; combine them in named states entries (e.g. selected-hover, selected-rest, unselected-hover) instead of duplicating long ternary chains.
flowchart TB
subgraph disabledBranch [Disabled]
D[row disabled styling]
end
subgraph enabledBranch [Enabled]
S[selected?]
S -->|yes| SH[selected + hover or rest or pressed]
S -->|no| UH[unselected + hover or rest or pressed]
end
rectangle := Rectangle {
border-color: /* rest default */;
background: /* rest default */;
states [
disabled when root.disabled: { /* … */ }
selected-pressed when !root.disabled && root.selected && ta.pressed: { /* … */ }
selected-hover when !root.disabled && root.selected && !ta.pressed && ta.has-hover: { /* … */ }
selected-rest when !root.disabled && root.selected && !ta.pressed && !ta.has-hover: { /* … */ }
/* … unselected variants … */
]
}
Use a TouchArea (ta) for has-hover / pressed when applicable.
states or small private property <bool> helpers, not inline in unrelated components.primer-port-orchestratorprimer-port-pr-sequentialprimer-port-upstream-researchprimer-port-slint-researchtokens.slint: primer-slint-token-layersprimer-port-variant-matrixprimer-slint-icons-registryFrom monorepo root: pnpm autofix and ensure app/src/ui/main.slint loads (see AGENTS.md).