Expand description
The Material 3 Expressive icon button: 4 variants × 5 sizes × round/
square × 3 widths, with toggle (selected-icon swap), a badge slot, and
the same press/hover shape morph crate::button uses.
icon_button builds the default standard icon button;
filled_icon_button, tonal_icon_button and outlined_icon_button
are the reference’s other three named constructors
(IconButtonView::variant switches between them after the fact,
mirroring crate::button’s own named-constructor shape). Every one
fires on_press on release inside its bounds and leaves its own props
alone — is_selected is a caller-owned prop, never self-mutated
(docs/CODE_STANDARDS.md’s Interaction Semantics; see Toggle below).
§Relationship to frust::icon_button
Baseline frust-widgets ships its own, much simpler
frust::icon_button — a single-variant, transparent-at-rest pressable
glyph with no size tiers, no toggle, no badge. This module reuses the
same plain icon_button/IconButton names in frust_material on
purpose: the established cross-crate naming precedent
crate::checkbox/crate::radio already set against their
own baseline counterparts. An app picks exactly one by import path
(frust_material::icon_button vs. frust::icon_button), never both at
the same call site.
§Variants
Container / content roles, from m3e_icon_button_build.dart:153
(_variantColors) — the only table where is_selected changes a
color, and only for IconButtonVariant::Standard:
| Variant | Container | Content |
|---|---|---|
IconButtonVariant::Standard (default) | transparent | primary if selected else on_surface_variant |
IconButtonVariant::Filled | primary | on_primary |
IconButtonVariant::Tonal | secondary_container | on_secondary_container |
IconButtonVariant::Outlined | transparent + outline hairline | primary |
Verified from source, not obvious: unlike crate::button’s
resolve_colors, _variantColors carries no disabled branch at all
— an icon button built with enabled(false) paints the exact same inks
as an enabled one (_morphButtonStyle’s foregroundColor/
backgroundColor resolvers are state-independent constants, bypassing
Flutter’s own IconButton disabled dimming). This port matches that
exactly: enabled only gates input handling (see Enabled/disabled),
never color.
§Sizes and widths
Icon glyph size, and the visual (painted) box vs. the tap-target box, by
size × width — m3e_icon_button_theme.dart:39-:101. Visual and
target are deliberately distinct measurements: on IconButtonSize::Xs
and IconButtonSize::Sm the painted box is smaller than the 48dp
minimum touch target, so the outer hit region pads out to 48dp while the
visible container stays smaller — from IconButtonSize::Md up, the two
already coincide (or the wide variant’s target already exceeds 48dp), so
nothing pads further. IconButtonWidget::layout returns the target
size and centers the visual box inside it; both are pinned per size in
this module’s tests.
| Size | Icon | Visual (default/narrow/wide) | Target (default/narrow/wide) |
|---|---|---|---|
IconButtonSize::Xs | 20 | 32×32 / 28×32 / 40×32 | 48×48 / 48×48 / 48×48 |
IconButtonSize::Sm (default) | 24 | 40×40 / 32×40 / 52×40 | 48×48 / 48×48 / 52×48 |
IconButtonSize::Md | 24 | 56×56 / 48×56 / 72×56 | 56×56 / 48×56 / 72×56 |
IconButtonSize::Lg | 32 | 96×96 / 64×96 / 128×96 | 96×96 / 64×96 / 128×96 |
IconButtonSize::Xl | 40 | 136×136 / 104×136 / 184×136 | 136×136 / 104×136 / 184×136 |
IconButtonWidth::Standard is the reference’s defaultWidth (renamed
to avoid colliding with Rust’s Default trait); IconButtonWidth::Narrow
and IconButtonWidth::Wide adjust only the box width, never its height.
§Shape and the press/hover morph
IconButtonShape::Round (the default) and IconButtonShape::Square
each resolve a per-size resting radius (m3e_icon_button_theme.dart:103-
:117); pressing morphs toward a squarer radius and hovering morphs
partway there (m3e_icon_button_shapes.dart:42’s effectiveRadius,
:119-:134’s pressed/hovered tables):
| Size | Round (rest) | Square (rest) | Hovered | Pressed |
|---|---|---|---|---|
| Xs | 16 | 8 | 10 | 6 |
| Sm | 20 | 10 | 12 | 8 |
| Md | 28 | 14 | 16 | 11 |
| Lg | 48 | 24 | 28 | 19 |
| Xl | 68 | 34 | 40 | 27 |
Unlike crate::button’s own radius tables, none of these four
coincide cleanly across every size with frust::Theme’s shape scale
(shape_scale()’s small/medium/large/… tokens) — the round table
matches at four of five sizes but breaks at Xl (68 has no token), so
this module keeps every radius a literal per-size constant rather than a
partial, size-inconsistent theme resolution.
The morph itself is [crate::button::motion::RadiusPaddingMotion],
reused directly from crate::button — see this module’s
RadiusPaddingMotion reuse section — flung along the same named spring
[mod@crate::button::motion] uses,
crate::tokens::MaterialSpring::EXPRESSIVE_SPATIAL_PRESS (380/0.55).
§RadiusPaddingMotion reuse
crate::button’s RadiusPaddingMotion is the reference’s shared
morph primitive (M3ERadiusAndPaddingMotion), used by both the Dart
button and icon-button families via one imported file
(m3e_icon_buttons.dart:14). This crate’s own port of that primitive
lives at button::motion::RadiusPaddingMotion, pub(crate) and now
declared inside a pub(crate) mod motion; in button/mod.rs, so this
sibling module imports it directly rather than carrying a parallel copy.
An earlier version of this module did carry a copy (RadiusMorph) —
button/mod.rs’s mod motion; was still private then, and a same-wave
task held button/ read-only, so widening it was out of reach; a later
task (owning button/ for real) closed that gap by widening the
declaration and doing this swap. Every icon-button width/size/variant
passes internalLeft/Right/Top/Bottom: 0 to M3ERadiusAndPaddingMotion
(m3e_icon_button_build.dart:191-:194), so the padding channel the
button family needs never actually moves here — this module pins it to
ContentPadding::symmetric(0.0) on every retarget and never reads
RadiusPaddingMotion::padding back. press_spring_matches_expressive_spatial_press
(this module’s tests) still pins the spring constant this module flings
along against crate::button::motion::PRESS_SPRING, now the same
constant both families share rather than two copies kept equal by hand.
§Toggle
IconButtonView::selected and IconButtonView::selected_icon
together are the reference’s optional toggle pair
(isToggle = widget.isSelected != null || widget.selectedIcon != null,
m3e_icon_button_build.dart:12-13): a plain icon button sets neither.
When toggling, IconButtonShape flips while selected
(m3e_icon_button_shapes.dart:16-27’s restVariant) and the active
icon becomes selected_icon (falling back to icon if unset) —
is_selected is read every build/rebuild as a prop, never written by
this widget: a press only ever fires on_press, exactly like every
other controlled component in this crate (docs/CODE_STANDARDS.md’s
Interaction Semantics).
§Enabled/disabled
IconButtonView::enabled (default true) gates input handling only —
see the Variants section above for why colors never dim. A button
disabled mid-press drops both the visual press and the pointer capture
it was holding, mirroring crate::button’s own rule.
§Badge
IconButtonView::badge ports what _wrapWithBadge/_buildBadge
actually paint for this family (m3e_icon_button_build.dart:293-:350)
— not the separate, not-yet-built shared badges family this catalog
will eventually carry. BadgeValue::Count rounds/clamps to 0..=999_999; 0
paints a small unlabeled dot ([DOT_DIAMETER], the reference’s
smallSize: 8); anything else paints a labeled pill.
BadgeValue::Text paints a labeled pill directly, or nothing at all
for an empty string (v.isEmpty, :332). The pill height/padding are
Flutter’s own Badge widget defaults (largeSize/padding, not
vendored into this repo’s ported tree, so not line-cited the way every
other constant here is) rather than an m3e-specific token. The badge is
purely decorative paint, positioned at the visual box’s top-end corner
(PositionedDirectional(top: 0, end: 0), :306) — it never hit-tests
and never affects layout of anything else.
§Icon color: caller-supplied, not ambient-retinted
The Dart widget wraps its active icon in IconTheme.merge(color: colors.fg, ...) so any descendant Icon inherits the resolved content
ink ambiently. This framework has no ambient icon-theme propagation, and
crate::button’s own icon child has exactly the same gap (it lays
its icon: Option<ChildPod> out at the size token without recoloring
it) — this port matches that established precedent rather than inventing
one: the icon/selected_icon views a caller supplies are expected to
carry their own ink (e.g. frust_material::icons::CHECK built through
frust::icon(..).color(..)).
§Not ported in v1
tooltip (no tooltip host in the catalog — the same gap
crate::button’s own module doc records), visualSize (a
decoration-shaped per-instance override with no builder surface yet),
and [M3EIconButtonDecoration]’s gradient fill/overlay/outline seam (the
same follow-up shape crate::button’s ButtonDecoration fills for
the button family, not yet extended here).
§Attribution
See plugins/material/NOTICE’s “MIT License — Additional Copyright
Holders (Vendored Components)” section (Mudit Purohit / icon_button_m3e)
and its Module Attribution Header Convention.
Structs§
- Icon
Button View - A declarative M3 Expressive icon button. See the module docs.
- Icon
Button Widget - The retained widget for an
IconButtonView. See the module docs.
Enums§
- Badge
Value - A badge value for
IconButtonView::badge— the reference’sObject? badgeValue(anumor aString,m3e_icon_button_build.dart:311-:350), given a closed, typed shape instead. - Icon
Button Shape - The icon button’s resting shape family — the reference’s
M3EIconButtonShapeVariant(m3e_icon_button_enums.dart:32-:38). - Icon
Button Size - The icon button’s size tier — the reference’s
M3EIconButtonSize(m3e_icon_button_enums.dart:2-:17). See the module docs’ Sizes and widths table. - Icon
Button Variant - Which container treatment an icon button paints — the reference’s
M3EIconButtonVariant(m3e_icon_button_enums.dart:41-:53). See the module docs’ Variants table. - Icon
Button Width - The icon button’s container width tier — the reference’s
M3EIconButtonWidth(m3e_icon_button_enums.dart:20-:29).Self::Standardis the reference’sdefaultWidth, renamed to avoid colliding with Rust’sDefaulttrait (see the module docs).
Functions§
- Icon
Button - PascalCase alias for
icon_button, matching this catalog’s view-fn vocabulary (Button,AssistChip, …). - filled_
icon_ button - Create a filled icon button —
icon_buttonwithIconButtonVariant::Filled. - icon_
button - Create a standard (transparent,
on_surface_variant) icon button paintingicon, runningon_presson release inside its bounds — the reference’s defaultM3EIconButton. - outlined_
icon_ button - Create an outlined icon button —
icon_buttonwithIconButtonVariant::Outlined. - tonal_
icon_ button - Create a tonal icon button —
icon_buttonwithIconButtonVariant::Tonal.