Button is the control for one explicit, immediate action. It owns its intrinsic width and height, its material and shape, focus and press motion, the leading icon or avatar slot, label expansion and truncation, the disabled treatment, and tooltip integration. Supply content and a handler, and let the component own its appearance, size, and motion so interaction states stay synchronized.alwaysShowText, rests expanded. Put peer actions in ButtonRail or ButtonGroup rather than in a flex row of your own, so spacing, focus travel, and overflow are handled for you.Button is built on Container, which is built on InteractableBase. Read those pages for the full interaction, material, and tooltip contract.import { Button, ButtonRail, IconTintColor } from '@wearables-ui-toolkit/mrbd';
import type { IconSource } from '@wearables-ui-toolkit/mrbd';
type ComposeActionsProps = {
replyIcon: IconSource;
reactIcon: IconSource;
deleteIcon: IconSource;
canDelete: boolean;
onReply: () => void;
onReact: () => void;
onDelete: () => void;
};
export function ComposeActions({
replyIcon,
reactIcon,
deleteIcon,
canDelete,
onReply,
onReact,
onDelete,
}: ComposeActionsProps) {
return (
<ButtonRail>
<Button icon={replyIcon} title="Reply" onClick={onReply} />
<Button icon={reactIcon} title="React" onClick={onReact} />
<Button
icon={deleteIcon}
title="Delete"
iconTintColor={IconTintColor.NEGATIVE}
disabled={!canDelete}
onClick={onDelete}
/>
</ButtonRail>
);
}
alwaysShowText keeps the name visible while unfocused:<Button
avatarSrc={contact.photoUrl}
avatarAlt={contact.name}
title={contact.name}
subtitle="Call"
alwaysShowText
onClick={() => call(contact)}
/>
import { Button, TooltipMode } from '@wearables-ui-toolkit/mrbd';
<Button
icon={moreIcon}
ariaLabel="More options"
tooltipText="More options"
tooltipMode={TooltipMode.FOCUSED}
onClick={openMenu}
/>;
trailingTag marks the action as an in-development capability:import { Button, TrailingTag } from '@wearables-ui-toolkit/mrbd';
<Button
icon={translateIcon}
title={feature.displayName}
trailingTag={TrailingTag.BETA}
enforceMaxWidth
onClick={openFeature}
/>;
<Button
icon={sendIcon}
title="Send"
disabled={!isDraftValid}
tooltipText="Add a message first"
tooltipMode={TooltipMode.DISABLED_CLICK}
onClick={send}
/>
ButtonProps. It extends the Container prop type, minus the props the button owns itself.| Prop | Type | Default | Description |
|---|---|---|---|
title | string | — | Primary label. Hidden while collapsed unless alwaysShowText is set or there is no leading accessory. |
subtitle | string | — | Secondary line rendered under the title, in the secondary text role. Follows the same show/hide rules as the title. |
alwaysShowText | boolean | false | Keep the label visible in the default (unfocused) state, so the button rests expanded. |
icon | IconSource | — | Leading icon. Ignored when an avatar prop is also supplied. |
iconTintColor | IconTintColor | IconTintColor.PRIMARY | Semantic tint applied to the icon when applyIconTinting is true. |
applyIconTinting | boolean | true | Tint the icon with the resolved color role. Set false for full-color artwork. |
showIconActiveIndicator | boolean | false | Show the small active dot under the icon. The dot is reserved in layout either way, so toggling it does not shift the glyph. |
iconRotation | number | 0 | Icon rotation, in degrees. |
animateIconRotation | boolean | false | Animate changes to iconRotation instead of applying them immediately. |
iconRotationDuration | number | 1000 | Duration of the rotation transition, in milliseconds. |
avatarSrc | string | — | Avatar image URL. Supplying it switches the leading slot to avatar mode. |
avatarPrimaryContent | ReactNode | — | Content for the avatar primary slot. Also switches the leading slot to avatar mode. The avatar owns the slot size. |
avatarAlt | string | — | Accessible label for the avatar image. |
statusIndicator | StatusIndicatorType | — | Status dot rendered on the avatar. |
statusIndicatorIcon | IconSource | — | Glyph drawn over the avatar status dot. |
avatarBadgeSrc | string | — | Image URL for the avatar badge slot. |
avatarBadgeContent | ReactNode | — | Content for the avatar badge slot. The avatar owns the badge size. |
placeholderStyle | PlaceholderStyle | PlaceholderStyle.AVATAR | Placeholder treatment used by the avatar when the slot has neither an image nor primary content. |
trailingTag | TrailingTag | TrailingTag.NONE | Design-system-owned tag rendered after the title. The label is fixed; there is no caller-supplied tag text. |
enforceMaxWidth | boolean | false | Cap the expanded width and truncate the title and subtitle with an ellipsis. |
width | number \| string | — | Explicit width. Only a numeric value is honored; a string falls back to the component’s minimum width. Overrides intrinsic sizing, so avoid it. |
| Prop | Type | Default | Description |
|---|---|---|---|
onClick | (event: MouseEvent) => void | — | Activation handler. Receives a real DOM click event for pointer and keyboard activation. |
onActivate | (event: InteractableActivationEvent) => void | — | Called once per activation; keyboard activation receives the originating keyboard event. |
onLongPress | (event) => void | — | Called when the press is held past the long-press threshold. |
onStateChange | (prev: InteractionState, next: InteractionState) => void | — | Called on every default / focused / pressed / disabled transition. |
disabled | boolean | false | Dim the button and suppress activation. The button stays focusable. |
interactive | boolean | true | Master switch for focusable, pressable, and clickable. |
focusable | boolean | inherits interactive | Whether the button takes focus. |
pressable | boolean | inherits interactive | Whether the button enters the pressed state. |
clickable | boolean | inherits interactive | Whether the button activates on click. |
initialFocusEligible | boolean | true | Whether the button may receive initial focus when a route mounts. |
ariaLabel | string | — | Accessible name. Required for icon-only or avatar-only buttons. A standard aria-label prop takes precedence over it. |
tooltipText | string | — | Tooltip copy. |
tooltipContent | ReactNode | — | Rich tooltip body, used instead of tooltipText. |
tooltipMode | TooltipMode | TooltipMode.NONE | When the tooltip appears: on focus, after a dwell delay, on disabled activation, or always. |
tooltipPosition | TooltipPosition | TooltipPosition.ANCHORED | Where the tooltip is placed relative to the button. |
tooltipShowTail | boolean | true | Whether the tooltip draws a tail pointing at the button. |
partialFocusSupportedAxis | PartialFocusSupportedAxis | PartialFocusSupportedAxis.XY | Which axes give partial-focus rubberband feedback when directional focus cannot move. |
onInvalidFocusDirection | (direction: InvalidFocusDirection) => void | — | Called when directional focus input cannot move focus out of the button in the requested direction. |
material | ContainerMaterial | the button material | Material used for the background and stroke layers. |
shapeProvider | ShapeProvider | full-radius rounded rectangle | Geometry used for clipping and every material path. |
className | string | '' | Extra class on the root element. |
style | CSSProperties | — | Extra inline styles on the root element. They merge over the component’s own transform and width constraints, so use them for placement, not for restyling. |
Button does not accept children or height: content comes from the props above, and the height is component-owned. The role and the container’s clipping are component-owned too — the button never clips its own box, so its focus glow can render outside it.IconTintColor| Member | Resolves to |
|---|---|
PRIMARY | The primary icon color role (default) |
SECONDARY | The secondary icon color role, composited with a lighten blend |
ACCENT | The accent icon color role |
ON_CHECKED | The primary-button background color role, used as the glyph color on a checked control |
ACTION | The persistent action color role |
POSITIVE | The persistent positive color role |
WARNING | The persistent warning color role |
NEGATIVE | The persistent negative color role |
INFO | The persistent info color role |
getIconTintCSSVariable(tint) returns a var() reference to the matching color token, and getIconTintBlendMode(tint) returns the CSS mix-blend-mode a tint requires ('lighten' for SECONDARY, otherwise undefined). Both are exported for components that build their own icon slots.TrailingTag| Member | Description |
|---|---|
NONE | No trailing tag (default) |
BETA | Renders the design-system “Beta” tag after the title |
Button forwards a ButtonHandle.| Method | Description |
|---|---|
animateActionTransition(options: ButtonActionTransitionOptions): void | Morphs the button to a completed-action state: the current icon shrinks away, the ending icon (and optionally an ending title) is swapped in while the glyph is invisible, then the new icon springs back to full size. A no-op unless endingIcon is supplied. |
getElement(): HTMLDivElement \| null | Returns the root element, or null before mount and after unmount. Use it for focus restoration and measurement. |
ButtonActionTransitionOptions has two optional members:| Member | Type | Description |
|---|---|---|
endingIcon | IconSource | Icon to display once the transition completes, such as a “sent” checkmark. Required for the call to do anything. |
endingTitle | string | Title to swap in partway through the transition. An empty string is treated the same as omitting it. |
import { useRef } from 'react';
import { Button } from '@wearables-ui-toolkit/mrbd';
import type { ButtonHandle } from '@wearables-ui-toolkit/mrbd';
const buttonRef = useRef<ButtonHandle>(null);
async function send() {
await postMessage();
buttonRef.current?.animateActionTransition({
endingIcon: checkIcon,
endingTitle: 'Sent',
});
}
<Button ref={buttonRef} icon={sendIcon} title="Send" onClick={send} />;
icon or title prop clears the corresponding override, so props stay the source of truth even while a transition is in flight. Under reduced motion the swap is applied immediately with no animation.avatarSrc or avatarPrimaryContent) wins over icon; with neither, the button is text-only. Do not supply both. The avatar is rendered at AvatarSize.LARGE — the slot size is component-owned, so the avatar props that would change it are not exposed here.alwaysShowText buttons rest expanded and paint at full width on the first frame. While collapsed, the icon or avatar counter-scales against the container so the glyph stays visually steady.ButtonGroup and ButtonRail read as horizontal margins, so the gaps between actions stay even as it expands and collapses. It also publishes the geometry it will settle at in the default and focused states, which is what lets an enclosing ButtonRail project the final row layout on the frame focus changes and move its scroll in step with the expansion instead of chasing it.enforceMaxWidth, the expanded width is capped at a maximum that depends on the leading mode (widest for text-only, narrower with an icon, narrowest with an avatar) and the labels truncate with an ellipsis. Truncation only engages once the measured content actually exceeds that cap, so short labels are never constrained.alwaysShowText hides its material entirely while unfocused, so the avatar reads as a standalone element, and the material returns on focus or press.iconRotation rotates the icon slot by the given number of degrees. With animateIconRotation, changes ease out over iconRotationDuration; otherwise, and whenever reduced motion is requested, the new angle is applied immediately.--uit-disabled-opacity, keeps its focus stop, exposes aria-disabled, and suppresses activation — it still reports focus and blur state changes. Pair it with TooltipMode.DISABLED_CLICK when someone needs to know why the action is unavailable.role="button", takes a focus stop (tabIndex is 0 by default), and activates on Enter, Space, and pointer click.title and subtitle text. For a button with no text — icon-only or avatar-only — you must supply ariaLabel or the standard aria-label; when both are present, aria-label wins.avatarAlt labels the avatar image; pass a localized string.aria-disabled rather than disappearing from the focus order.subtitle is part of the accessible name, so keep it meaningful rather than using it for decoration.enforceMaxWidth when the label comes from data outside application control.disabled for unavailable actions instead of silently dropping the handler.ButtonGroup and ButtonRail provide the expected spacing, focus travel, and overflow behavior.showIconActiveIndicator as a supplement to an accessible label rather than a replacement for one.animateActionTransition for ordinary declarative state updates. It is intended for imperative action changes.