Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Button

Button preview

Button — a labelled, activatable action trigger.

Public types

KindName
enumInteractionState — Internal interaction state
enumIconLocation — Where an optional icon is placed relative to the button label
structButton — A labelled action trigger; use Button::new and chain builder methods

Public functions

Button

Detailed description

Button is the primary action surface in Teksilo. It renders a text label (optionally with a leading, trailing, top, or bottom icon), fires a closure on click / Space / Enter / AT click, and advertises seven design-language variants via ButtonVariant. Chrome (fill, border, focus ring, padding) is delegated to the active ButtonStyle; the default RecipeButtonStyle implements the Int UI token ladder.

When to use

  • Primary action: .variant(ButtonVariant::Filled) — one per context.
  • Secondary / cancel: default ButtonVariant::Plain.
  • Danger: ButtonVariant::Destructive (IntUI maps this to Filled).
  • Text-only link: ButtonVariant::Link / ButtonVariant::Ghost.

Touch and pen

The pressed visual is the framework's, not the button's own: the router keeps one press record per contact and Button mirrors it onto its InteractionState (bind_press_interaction). That buys four things a PointerDown / PointerUp pair inside a handler cannot see — a press that slides off its target goes out and comes back on re-entry (WCAG 2.2 SC 2.5.2), a press a pan claimant or an ancestor drag wins is cleared with no release to hang the reset on, a cancel clears it, and a press inside a scrollable withholds the visual for 100 ms so a finger that turns out to be scrolling never flashes a highlight. docs/touch-and-pen.md §7.1.

Activation has always been on_tap, so it already lands on the release. After a mouse or pen release the button rests hovered as it always has; after a finger release it rests idle, because a finger sends no hover-leave to correct a hovered state with.

The whole family — IconButton, CommandLinkButton, every Toolbar command — shares build_interaction_handlers and gets all of this with it.

Label colour

The label and icon take their colour from the interaction state (OnAccent on accent fills, Primary otherwise, Link for links). The active ButtonStyle may redirect it per variant with label_text_role, or per variant and state with label_text_role_for — for example to brighten the label on hover. Both are repaint-only; a per-call Button::text_role wins over them.

Accessibility

Announces as Role::Button with the resolved label as its AT name. Keyboard: Space / Enter activate; the lone-KeyUp guard prevents spurious re-activation when a shortcut consumes the KeyDown and returns focus here.

#![allow(unused)]
fn main() {
use teksilo_widgets::{Button, ButtonVariant};
use teksilo_i18n::lit;
use teksilo_core::Intent;
let _btn = Button::new(lit!("Save"))
    .variant(ButtonVariant::Filled)
    .on_activate_fn(|ctx| ctx.send_intent(Intent::new("app.save")));
}

Density

The picture above is the widget at TargetDensity::Compact, the mouse-and-keyboard ladder. Below is the same subject on the same canvas with only the ladder changed, so what moves is the density and nothing else — where the subject no longer fits, that is what the denser targets cost it at that size. See docs/density-and-targets.md.

Touch

Button at Touch density

API reference

📖 Full rustdoc API for this module

pub enum InteractionState

Import: use teksilo::widgets::button::InteractionState;

Internal interaction state.

#![allow(unused)]
fn main() {
pub enum InteractionState { /* variants */ }
}

Variants

  • Idle
  • Hovered
  • Pressed
  • Focused
  • Disabled

pub enum IconLocation

Import: use teksilo::widgets::IconLocation;

Where an optional icon is placed relative to the button label.

#![allow(unused)]
fn main() {
pub enum IconLocation { /* variants */ }
}

Variants

  • None — No icon (default).
  • IconOnly — Icon only, no label.
  • Leading — Icon to the left of the label.
  • Trailing — Icon to the right of the label.
  • Top — Icon above the label.
  • Bottom — Icon below the label.

pub struct Button

Import: use teksilo::widgets::Button;

A labelled action trigger; use Button::new and chain builder methods.

#![allow(unused)]
fn main() {
pub struct Button { /* fields */ }
}

Methods

pub fn new(label: impl Into<LocalizedString>) -> Self

Construct a button from a LocalizedString label. The label may come from tr!(...) (translated) or lit!(...) (explicit non-translated). When an I18nManager is installed, a tr!(...) label becomes a Prop::Bound that observes the locale version signal, so the inner TextWidget re-renders on a locale switch without rebuilding the Button — matching TextWidget::new. lit!(...) and the no-manager case resolve to a static String.

pub fn current_variant(&self) -> ButtonVariant

Returns the configured visual variant. Used by wrappers like PopoverButton that derive their own chrome colors from the same recipe-resolution path the inner Button uses.

pub fn share_interaction(mut self, signal: Signal<InteractionState>) -> Self

Bind the button's internal interaction state to a caller-owned Signal<InteractionState> instead of letting build() allocate its own. Used by wrapper widgets like PopoverButton whose disclosure caret needs to match the label's color across hover / press / focus / disabled states.

The provided signal never carries Disabled: the arena's enabled-state is the single source of truth since the single-sourced-enabled refactor, so a wrapper mirrors the disabled look from ButtonStyleConfig::is_disabled (or lets its own leaves dim at paint via PaintContext::effective_enabled) rather than from this signal.

pub fn variant(mut self, variant: ButtonVariant) -> Self

Set the Tier-1 design-language variant. The active ButtonStyle decides whether to honour or remap it (the IntUI default RecipeButtonStyle collapses Destructive → Filled, Tinted/Outlined → Plain, Link → Ghost).

pub fn style(mut self, style: impl ButtonStyle) -> Self

Override the active ButtonStyle for this widget instance only. Useful for one-off custom-painted buttons (glassmorphism CTA, Material-3 ripple, etc.) without forking the Button.

pub fn label(mut self, label: impl Into<teksilo_core::signal::Prop<String>>) -> Self

Bind the button's label to a reactive source — replaces the static label captured at new(...). Accepts any impl Into<Prop<String>>: a Signal<String> for live updates, or a plain String (which is the same as constructing the button with that string). Mirrors TextWidget::text. The inner label TextWidget is built with the bound prop, so the visible text refreshes without rebuilding the Button. The AT node's set_name reads the current value via Prop::get.

Translation note: derive the signal with state.map(|s| tr!(status_label(value = s)).resolve_now()) for translated reactive labels — Button only sees the resolved String.

pub fn on_activate_fn(mut self, f: impl Fn(&mut EventContext) + 'static) -> Self

Closure invoked on activation. Use ctx.send_intent(...) to route activation through the Action/Intent system, or inline the behavior directly.

pub fn tooltip(mut self, text: impl Into<LocalizedString>) -> Self

Attach a tooltip that appears after a hover delay.

pub fn rich_tooltip(mut self, key: impl Into<String>) -> Self

Attach a rich tooltip resolved from the app-wide tooltip registry. The key is looked up via TooltipRegistry at build time; the resolved body text supports inline markup (label, *italic*, **bold**) and the entry's shortcut / long-form "more" fields are rendered automatically.

Overrides any previously set plain .tooltip(...) text.

pub fn rich_tooltip_content(mut self, content: crate::tooltip::TooltipContent) -> Self

Attach a rich tooltip driven by inline TooltipContent — for one-off tooltips that aren't worth registering in the central catalog. Overrides any previously set plain .tooltip(...).

pub fn composite_tooltip( mut self, content: impl teksilo_core::widget::Widget + 'static, ) -> Self

Attach a composite tooltip — third tier, hosting an arbitrary widget tree (Crusader Kings 3 style: tabbed sections, charts, progress bars, conditional rows). Promotes to a focusable Role::Dialog after the user dwells for the standard promotion threshold. Overrides any plain or rich tooltip previously set on this button.

pub fn enabled(mut self, enabled: impl Into<Prop<bool>>) -> Self

Set the enabled state, statically or reactively. Disabled buttons ignore input and dim their content (the framework's PaintContext::effective_enabled propagates through to the label/icon leaves). Forwarded into the arena via ctx.enabled_when(self_id, self.enabled.clone()) at build time — a bound signal updates live as it changes.

pub fn text_role(mut self, role: impl Into<teksilo_core::color_prop::ColorProp>) -> Self

Override the label and icon's tint with a static ColorProp. When set, the button ignores its style and the auto-derived idle/hover/press text-role cascade — both the label text and any icon are bound directly to this prop instead. Use for chrome whose host enforces a single text role across all of its sub-widgets (e.g. tab-bar overflow-dropdown triggers that must match the strip's idle_text_role regardless of hover state). Accepts Color, TextRole, Signal<Color>, or Signal<TextRole>.

pub fn text_style(mut self, style: impl Into<teksilo_core::color_prop::TextStyleProp>) -> Self

Override the label's text style (font, size, weight). By default the label uses the inner TextWidget's default style; pass a TextStyleRole (e.g. TextStyleRole::BodyBold), a TextStyle, or a Signal of either to change it — e.g. to make the label bold. Orthogonal to Button::text_role, which only sets the color.

pub fn icon(mut self, icon: IconWidget, location: IconLocation) -> Self

Add an icon to the button at the specified location.

pub fn icon_keeps_color(mut self) -> Self

Keep the icon's own colour instead of tinting it to the label's.

The mirror of MenuItem::icon_keeps_color, and it exists for the same reason: an icon whose colour is the information. A filter chip carrying a user-chosen tag colour, a legend swatch, a status disc — tinting those to the label's foreground destroys the one thing they carry, while tinting is exactly right for a glyph that merely repeats the label.

Two consequences worth knowing, both inherited from ColorProp's own rules rather than special-cased here:

  • The colour must clear contrast against every fill the button takes — an accent-filled selected state as well as the resting surface.
  • A literal colour does not dim when the button is disabled. An icon that should dim wants a role instead, and then it does not need this.

pub fn has_popup(mut self, kind: teksilo_core::accesskit::HasPopup) -> Self

Declare that this button is a disclosure trigger for a popup (menu, dialog, listbox, tree, grid). Surfaced via set_has_popup in the a11y node so screen readers announce it as leading into the named popup kind.

pub fn expanded_when(mut self, signal: impl Into<Prop<bool>>) -> Self

Bind a signal reporting whether this button's popup is currently visible. The Popover / Dialog wrapper owns the signal and flips it on show / dismiss; Button reads it in accessibility() to publish set_expanded. Only meaningful alongside .has_popup(...).

pub fn leading(mut self, widget: impl Widget + 'static) -> Self

Insert a widget at the leading edge of the button's content (left in LTR, right in RTL). Composes with .icon(...): the final order is [leading_slot, icon+label, trailing_slot], separated by btn::BUTTON_ICON_LABEL_GAP. Single-slot — calling .leading(...) again replaces the previous slot. Stack multiple widgets with an explicit HStack.

The slot widget paints itself and emits its own a11y. Button does not retint it (so e.g. a ColorSwatch keeps its own color through every interaction state). If the slot widget declares an AT role of its own — ColorSwatch is the canonical case (Role::ColorWell) — pass widget.access_hidden(true) so the trigger reads as a single Button node instead of a Button containing a redundant ColorWell child.

pub fn trailing(mut self, widget: impl Widget + 'static) -> Self

Same as leading but at the trailing edge (right in LTR, left in RTL). Common uses: chevron-down hint on disclosure triggers, clear-X on search fields, status badges on segmented control segments.