Expand description
The shared M3 interaction-state overlay: a fill painted over a widget’s shape, in the widget’s own content color, at an opacity selected by its current hover/focus/pressed/dragged interaction state.
Source: androidx Compose Material3 StateTokens (v0_210, retrieval
2026-07-17) — hover 8%, focus 10%, pressed 10%, dragged 16%. This
supersedes the older material-web v0.192 table (hover 8%, focus/pressed
12%, dragged 16%); v0_210 is the table this helper ships.
StateLayer carries no theme dependency and no interaction-detection
logic of its own — a widget’s own event handler calls the set_*
setters to record state transitions (each returns whether the flag
actually changed, so a caller can gate a redraw request on it), and its
paint calls StateLayer::paint once per frame with the already
theme-resolved content color (the 3-tier explicit/theme/fallback
precedence stays the caller’s responsibility — see
docs/CODE_STANDARDS.md’s Theming conventions).
When more than one state is active at once (e.g. a focused and pressed widget), the overlay opacity is the maximum of the active states’ opacities — M3 does not stack multiple overlays additively, it shows the strongest one.
§Relationship to crate::interaction
This type now wraps crate::interaction::InteractionState and its
opacity() method below delegates to
InteractionState::max_active_opacity
— the exact max-of-active behavior this module has always documented,
preserved unchanged for this module’s existing consumers
(list_item/card/chips/fab/switch). crate::interaction also
hosts a precedence-based resolver
(dragged > pressed > focused > hovered, porting the reference’s
M3EInteractionState.opacity exactly) as its new documented default for
any future consumer — see that module’s doc for why the two resolvers
agree numerically on this crate’s own token table today, and why they
are still kept as two distinct functions rather than one. This module’s
own public constants and StateLayer API are unchanged by that
addition; every existing call site here keeps its current behavior.
§Live vs. aspirational states
Of the four interaction states this helper models, pressed and hovered
are wired by a shipping widget ([super::list_item], the reference
consumer) — the max-of-active-states rule above describes the full M3 design,
but two of its four inputs still have no live signal source:
hovered— wireable, and wired. There is still no Enter/Leave phase (frust::authoring::PointerPhaseremains Down/Move/Up/Cancel), because hover is a claim, not a phase: a widget callsfrust::authoring::EventCtx::claim_hoverfrom its uncapturedMovearm once it has hit-tested the pointer inside its own bounds, and the framework records that claim down the pod chain. The claim is per-pass, so the pointer moving anywhere else — including onto a widget this one never hears about — drops the link with no leave event to deliver, which is exactly what a widget could not do for itself before. Three rules bind a consumer: it keeps its own hover flag and gates itsrequest_redrawonStateLayer::set_hovered’s changed-return (the frame source for hover gain —claim_hoverasks for none);frust::authoring::PaintCtx::is_hoveredis the authoritative read, so the widget re-syncsset_hoveredfrom it every paint (the pointer’s departure never reaches itsevent); and a claim from a captured pointer is refused by construction, so a drag never tints the widget under the finger. That refusal is structural for a captured pointer only — nothing tells a touch contact from a mouse, so an uncaptured touch drag over a non-capturing consumer does tint, transiently, until theUpat lift ends the link (docs/LIMITATIONS.md’shover-window-leave-standing).focused— the focus-routing prerequisite landed (material::dialog/sheet,cupertino::alert_dialog/action_sheetall now callEventCtx::request_focuson aDownand dismiss on a focus-routedKey(Escape)), but still unwired here: none of those four modals paint their ownStateLayer-shaped actionable surface — each is a plain scrim + panel modal barrier (fills/hairlines), not an M3 interactive surface with a content color a state layer would tint. There was deliberately nothing to wireset_focusedinto without inventing chrome no design calls for. A future widget that both participates in focus routing and paints its ownStateLayer(e.g. a focusable list item, chip, or button) is what will make this state live.dragged— unwired. No catalog widget reports a drag intoStateLayer::set_draggedyet.
The set_focused/set_dragged setters remain the stable API for when those
upstream signals exist; they are deliberately kept, not dead-stripped, so a
widget can adopt each state the moment its source lands without re-plumbing
this helper — which is what let set_hovered be adopted above with no change
to this file’s own code.
Re-exports§
pub use crate::interaction::DRAGGED_OPACITY;pub use crate::interaction::FOCUS_OPACITY;pub use crate::interaction::HOVER_OPACITY;
Structs§
- State
Layer - Tracks a widget’s hover/focus/pressed/dragged interaction state and paints the M3 state-layer overlay for it. See the module docs.
Constants§
- PRESSED_
OPACITY - Pressed-state overlay opacity (source: androidx Compose Material3
StateTokensv0_210, retrieved 2026-07-17 — supersedes material-web v0.192’s 12%).