Interaction states
State layers, the gap-then-ring focus indicator, and how disabled is drawn.
Interactive components share one vocabulary of states: enabled, hovered, focused, pressed, dragged, selected, activated, error, loading and disabled. A component’s spec lists the states it supports, and every platform must implement all of them.
Hover, focus, press and drag are shown with a state layer: a translucent overlay that covers the component’s container.
- The layer takes the color of the content, that is the
on-role of whatever it covers. On a filled button it ison-primary; on a text button it is the label color. - Its opacity comes from a token, not from the component.
- Only one layer is visible at a time. When states overlap, the strongest wins: pressed over focused over hovered.
- It fades in over a short effects transition and appears immediately on press.
The quiet selection is a layer too, and the one that stays: a list’s selected option, a menu’s current link item and the tonal tabs’ indicator are drawn in their content color at selected-state-layer-opacity, twice the hover layer, over whatever the item stands on. So a selected item stands as far from a hovered one as a hovered one stands from one at rest, on any surface. The hover, focus and press layers are laid over it, and the selection is never told by the fill alone: a check, a mark or the label says it too.
| Token | Value | Specimen |
|---|---|---|
--tanrei-sys-state-disabled-container-opacity | 0.12 (12%) | AaAa |
--tanrei-sys-state-disabled-content-opacity | 0.38 (38%) | AaAa |
--tanrei-sys-state-dragged-state-layer-opacity | 0.16 (16%) | AaAa |
--tanrei-sys-state-focus-state-layer-opacity | 0.1 (10%) | AaAa |
--tanrei-sys-state-hover-state-layer-opacity | 0.08 (8%) | AaAa |
--tanrei-sys-state-pressed-state-layer-opacity | 0.1 (10%) | AaAa |
--tanrei-sys-state-selected-state-layer-opacity | 0.16 (16%) | AaAa |
Generated from design/tokens/tanrei.light.tokens.json, tokens/sys/state.tokens.json
Hover and press the buttons below to see the layer on different containers:
State layers
Keyboard focus is drawn as a 2px gap in the surface color followed by a 2px ring in the focus-ring role. The gap guarantees that the ring is visible even when the component’s own fill is close to the ring color, and the role is checked for 3:1 contrast against surfaces.
- The ring is shown for keyboard and assistive-technology focus, not for pointer clicks.
- It is never animated.
- It follows the component’s shape, offset outward.
Tanrei does not draw an expanding ripple on press. The pressed state layer is the feedback. A theme may opt into ripple, but no component depends on it.
A disabled component draws its container at the disabled-container-opacity and its content at the disabled-content-opacity of on-surface, shows no state layer, and leaves the focus order. Disabled elements are exempt from contrast requirements, which is a reason to use them sparingly.
Tip
Prefer keeping an action enabled and explaining what is missing when it is used. A disabled button cannot tell anyone why it is disabled.