Skip to main content

Module button

Module button 

Source
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):

VariantContainerLabel + icon
ButtonVariant::Filled (default)primaryon_primary
ButtonVariant::Tonalsecondary_containeron_secondary_container
ButtonVariant::Elevatedsurface_container_lowprimary
ButtonVariant::Outlinedtransparent + outline hairlineprimary
ButtonVariant::Texttransparentprimary

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:

SizeHeightPaddingIconGapLabel role
ButtonSize::Xs3216208label_small
ButtonSize::Sm (default)4016208label_medium
ButtonSize::Md5624248label_large
ButtonSize::Lg96483212title_medium
ButtonSize::Xl136644016title_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):

SizeSquareHoveredPressed
Xs / Sm12108
Md161412
Lg / Xl282216

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 a foreground_brush resolver, called with the morphing ButtonSurface, where the reference’s m3eGradientSurfaceBuilder/m3eGradientForegroundBuilder layers live. Returning DecorationOutcome::Painted suppresses the core’s own solid layer. gradient::GradientButtonDecoration is the built-in implementation that fills every hook with ported gradient paint — see that module’s docs for the full gradient-class inventory and the PaintScene gradient-support finding.
  • OverflowObserver — handed the ContentMetrics every layout resolves, including whether the label’s natural width exceeded the width actually available to it. overflow::OverflowStrategy (installed via ButtonView::overflow) is what now decides what to do about that — scroll or defer to a bottom sheet, calling back into an installed OverflowObserver for the latter — porting the reference’s M3EOverflowStrategy family onto this seam; see that module’s docs for the full mapping (the family is upstream a ButtonGroup-level abstraction, not a single button’s own prop).

§Mapping Flutter plumbing that has no frust analogue

ReferenceHere
WidgetStatesController + WidgetStateProperty resolverscrate::interaction::InteractionState plus plain per-state resolver fns; there is no external states controller to inject
FilledButton/OutlinedButton/… delegationthis widget paints its own container, outline, state layer and content
InkSparkle/splashFactory ripplecrate::state_layer::StateLayer — an M3 state-layer overlay, no ripple engine in this framework
FocusNode/autofocus/onFocusChangePaintCtx::has_focus only (see Focus ring above)
Tooltip, onLongPress, onHover callbacksnot 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’s super::ButtonDecoration seam: a ported GradientButtonDecoration implementation painting fill/overlay/outline/foreground gradients over (or instead of) the core’s solid layers.

Structs§

ButtonSurface
The morphing surface a ButtonDecoration paints against: window-space geometry, the live corner radius, and the inks + interaction state the core resolved for this frame.
ButtonView
A declarative M3 Expressive button. See the module docs.
ButtonWidget
The retained widget for a ButtonView. Its layout/paint/event core lives in this module’s core submodule.
ContentMetrics
What one layout pass measured of a button’s content — the input an overflow strategy decides on.

Enums§

ButtonShape
The button’s corner-radius family — the reference’s M3EButtonShape (m3e_button_enums.dart:45).
ButtonSize
The button’s size tier — the reference’s M3EButtonSize (m3e_button_enums.dart:64). See the module docs’ Sizes table.
ButtonVariant
Which container treatment a button paints — the reference’s M3EButtonStyle (m3e_button_enums.dart:19). See the module docs’ Variants table.
DecorationOutcome
Whether a ButtonDecoration hook took over a layer.
IconAlignment
Which side of the label an icon sits on — the reference’s IconAlignment as consumed by _M3EButtonIconLayout (m3e_button_state.dart:38).
OverflowStrategy
Which behavior an overflowing label triggers, once a layout pass finds super::ContentMetrics::overflows true. See the module docs.

Traits§

ButtonDecoration
The paint-time decoration seam — where the reference’s m3eGradientSurfaceBuilder fill/overlay/outline layers (plus m3eGradientForegroundBuilder’s separate content layer) attach. See super::gradient for the built-in ButtonDecoration implementation that fills every hook with gradient paint.
OverflowObserver
The layout-time content-measurement seam: handed the ContentMetrics of 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, running on_press on release inside its bounds — the reference’s default M3EButton/M3EButton.filled.
button_with_icon
Create a filled button laying icon out beside label — the reference’s M3EButton.icon factory (m3e_buttons.dart:64).
elevated_button
Create an elevated (medium-emphasis, shadowed) button — M3EButton.elevated.
filled_button
Create a filled button — button under the name the reference’s M3EButton.filled constructor 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.