Expand description
The Material 3 Expressive button: the catalog’s flagship control.
button builds the default filled button; outlined_button,
tonal_button, elevated_button and text_button are the reference’s
other four named constructors (ButtonView::variant switches between them
after the fact), and button_with_icon is its M3EButton.icon factory —
an icon+label row laid out with the size’s own icon size and gap. Every one
of them fires on_press on release inside its bounds and leaves its own
state alone: a button reports an action, it confirms no value (see
docs/CODE_STANDARDS.md’s Interaction Semantics).
§Variants
Container / label roles, from m3e_button_theme.dart:111 (container) and
:127 (foreground):
| Variant | Container | Label + icon |
|---|---|---|
ButtonVariant::Filled (default) | primary | on_primary |
ButtonVariant::Tonal | secondary_container | on_secondary_container |
ButtonVariant::Elevated | surface_container_low | primary |
ButtonVariant::Outlined | transparent + outline hairline | primary |
ButtonVariant::Text | transparent | primary |
Disabled (m3e_button_constants.dart:27-:33): the label/icon drop to
on_surface at 38%, an opaque container to on_surface at 12%, an
outline to on_surface at 12%; a transparent container stays transparent.
§Sizes
Height / horizontal padding / icon / icon-gap, from
m3e_button_theme.dart:76’s _measurementsTable, and the label type role
from m3e_base_button_state.dart:169:
| Size | Height | Padding | Icon | Gap | Label role |
|---|---|---|---|---|---|
ButtonSize::Xs | 32 | 16 | 20 | 8 | label_small |
ButtonSize::Sm (default) | 40 | 16 | 20 | 8 | label_medium |
ButtonSize::Md | 56 | 24 | 24 | 8 | label_large |
ButtonSize::Lg | 96 | 48 | 32 | 12 | title_medium |
ButtonSize::Xl | 136 | 64 | 40 | 16 | title_large |
A button is never narrower than 48dp (M3EButtonTheme.minWidthFloor,
m3e_button_theme.dart:16). The reference’s M3EButtonSize.custom
per-instance measurement override is deliberately not ported here — this
enum is closed, and the decoration-shaped override surface it belongs to is
the same one the gradient seam below is waiting on.
§Shape and the press morph
ButtonShape::Round (the default) is a pill — height / 2;
ButtonShape::Square takes the per-size token radius. Pressing morphs
the container squarer and hovering morphs it partway there, both
spring-driven (m3e_button_content.dart:40’s _resolveShapes):
| Size | Square | Hovered | Pressed |
|---|---|---|---|
| Xs / Sm | 12 | 10 | 8 |
| Md | 16 | 14 | 12 |
| Lg / Xl | 28 | 22 | 16 |
The square and pressed columns coincide exactly with this crate’s shape
scale, so they resolve from the live frust::Theme (shape.medium/
large/extra_large and shape.small/medium/large) with the literal
table as the unthemed fallback — the same themed-with-fallback treatment
crate::fab gives its own radii. The hovered column sits between
two tokens (each entry is the midpoint of its size’s square and pressed
radii), so it has no token to resolve from and stays literal.
The morph itself — radius and content padding on one spring — is the
motion submodule’s RadiusPaddingMotion, flung along
EXPRESSIVE_SPATIAL_PRESS (380 / 0.55); see that module for the spring
citation and for how a mid-flight retarget stays continuous. A plain
button only ever retargets the radius: its padding target is the per-size
table value and does not move on press (the reference passes a constant
baseInternalPadding, m3e_button_content.dart:7). The padding channel is
nonetheless real and layout-affecting, because the same primitive is what
the connected/toggle families morph their padding with — for a plain
button, though, layout resolves that per-size padding value directly
rather than reading it back off the morph, since the morph’s own copy can
lag a runtime size change by a whole pass (core::ButtonWidget::layout’s
own comment has the full hazard).
§Focus ring
m3e_focus_ring.dart draws a 2dp primary ring 2dp outside the container,
with the container’s own (animating) radius plus that 4dp outset. This port
draws exactly that, gated on frust::authoring::PaintCtx::has_focus —
the authoritative focus read. Honest limitation: frust has no keyboard
focus traversal for a button today (no autofocus, no FocusNode, no
Tab-ring), and this widget deliberately does not claim focus on a press
(a tap-shows-a-focus-ring button is the wrong behavior on touch, and
matches neither the reference nor Material’s own focus-highlight mode). So
the ring is ported, gated and unit-testable, but nothing in a shipping app
reaches it yet — it becomes live the moment focus routing can reach a
button.
§Seams the follow-up tasks fill
Two hook points exist here as documented, no-op-by-default interfaces:
ButtonDecoration— a paint-time fill/overlay/outline triple plus aforeground_brushresolver, called with the morphingButtonSurface, where the reference’sm3eGradientSurfaceBuilder/m3eGradientForegroundBuilderlayers live. ReturningDecorationOutcome::Paintedsuppresses the core’s own solid layer.gradient::GradientButtonDecorationis the built-in implementation that fills every hook with ported gradient paint — see that module’s docs for the full gradient-class inventory and thePaintScenegradient-support finding.OverflowObserver— handed theContentMetricsevery layout resolves, including whether the label’s natural width exceeded the width actually available to it.overflow::OverflowStrategy(installed viaButtonView::overflow) is what now decides what to do about that — scroll or defer to a bottom sheet, calling back into an installedOverflowObserverfor the latter — porting the reference’sM3EOverflowStrategyfamily onto this seam; see that module’s docs for the full mapping (the family is upstream aButtonGroup-level abstraction, not a single button’s own prop).
§Mapping Flutter plumbing that has no frust analogue
| Reference | Here |
|---|---|
WidgetStatesController + WidgetStateProperty resolvers | crate::interaction::InteractionState plus plain per-state resolver fns; there is no external states controller to inject |
FilledButton/OutlinedButton/… delegation | this widget paints its own container, outline, state layer and content |
InkSparkle/splashFactory ripple | crate::state_layer::StateLayer — an M3 state-layer overlay, no ripple engine in this framework |
FocusNode/autofocus/onFocusChange | PaintCtx::has_focus only (see Focus ring above) |
Tooltip, onLongPress, onHover callbacks | not ported in v1: no tooltip host in the catalog, and neither long-press nor a hover callback has a reference-visual attached to it |
enableFeedback (platform click sound) | no analogue; haptics are the crate::interaction::MaterialHaptics hook below |
§Haptics
m3e_button_content.dart:179 fires M3EHaptics.trigger(decoration?.haptic ?? M3EHapticFeedback.none) inside its own onPressed wrapper — i.e. a
button is silent unless its decoration asked for a signal. This port
matches that exactly: ButtonView::haptic defaults to
HapticSignal::None and the fire happens on the same up-inside edge that
runs on_press, through crate::interaction::MaterialHaptics::fire
(a no-op until an app installs a hook).
§Attribution
See plugins/material/NOTICE’s “MIT License — Additional Copyright Holders
(Vendored Components)” section (Mudit Purohit / m3e_buttons) and its Module
Attribution Header Convention.
Re-exports§
pub use self::gradient::GradientAlignment;pub use self::gradient::GradientButtonDecoration;pub use self::gradient::GradientProperty;pub use self::gradient::GradientSpec;pub use self::gradient::GradientStates;pub use self::gradient::LinearGradientSpec;pub use self::gradient::RadialGradientSpec;pub use self::gradient::SweepGradientSpec;pub use self::gradient::constant_gradient;pub use self::gradient::implied_stops;
Modules§
- gradient
- Gradient decoration layers for
super::ButtonWidget’ssuper::ButtonDecorationseam: a portedGradientButtonDecorationimplementation painting fill/overlay/outline/foreground gradients over (or instead of) the core’s solid layers.
Structs§
- Button
Surface - The morphing surface a
ButtonDecorationpaints against: window-space geometry, the live corner radius, and the inks + interaction state the core resolved for this frame. - Button
View - A declarative M3 Expressive button. See the module docs.
- Button
Widget - The retained widget for a
ButtonView. Its layout/paint/event core lives in this module’scoresubmodule. - Content
Metrics - What one layout pass measured of a button’s content — the input an overflow strategy decides on.
Enums§
- Button
Shape - The button’s corner-radius family — the reference’s
M3EButtonShape(m3e_button_enums.dart:45). - Button
Size - The button’s size tier — the reference’s
M3EButtonSize(m3e_button_enums.dart:64). See the module docs’ Sizes table. - Button
Variant - Which container treatment a button paints — the reference’s
M3EButtonStyle(m3e_button_enums.dart:19). See the module docs’ Variants table. - Decoration
Outcome - Whether a
ButtonDecorationhook took over a layer. - Icon
Alignment - Which side of the label an icon sits on — the reference’s
IconAlignmentas consumed by_M3EButtonIconLayout(m3e_button_state.dart:38). - Overflow
Strategy - Which behavior an overflowing label triggers, once a layout pass finds
super::ContentMetrics::overflowstrue. See the module docs.
Traits§
- Button
Decoration - The paint-time decoration seam — where the reference’s
m3eGradientSurfaceBuilderfill/overlay/outline layers (plusm3eGradientForegroundBuilder’s separate content layer) attach. Seesuper::gradientfor the built-inButtonDecorationimplementation that fills every hook with gradient paint. - Overflow
Observer - The layout-time content-measurement seam: handed the
ContentMetricsof every layout pass, including whether the label overflowed.
Functions§
- Button
- PascalCase alias for
button, matching this catalog’s view-fn vocabulary (ButtonGroup,AssistChip, …). - button
- Create a filled (highest-emphasis) button labelled
label, runningon_presson release inside its bounds — the reference’s defaultM3EButton/M3EButton.filled. - button_
with_ icon - Create a filled button laying
iconout besidelabel— the reference’sM3EButton.iconfactory (m3e_buttons.dart:64). - elevated_
button - Create an elevated (medium-emphasis, shadowed) button —
M3EButton.elevated. - filled_
button - Create a filled button —
buttonunder the name the reference’sM3EButton.filledconstructor uses. - outlined_
button - Create an outlined (medium-emphasis, hairline-bordered) button —
M3EButton.outlined. - text_
button - Create a text (lowest-emphasis, chrome-free) button —
M3EButton.text. - tonal_
button - Create a tonal (medium-emphasis,
secondary_container) button —M3EButton.tonal.