Skip to content

Actions

Icon button

Compact buttons for actions with a clear icon, in four styles, five sizes and three widths, optionally toggling.

Examples

Color configurations

Standard (default) takes the surrounding content color, as in Compose; filled, tonal and outlined have their own. Icon-only controls need an aria-label.

React HTML
import { Icon, IconButton } from '@kyralabs/m3exp-web/react';

export default function Variants() {
  return (
    <>
      <IconButton variant="standard" size="m" aria-label="Settings">
        <Icon name="settings" />
      </IconButton>
      <IconButton variant="filled" size="m" aria-label="Settings">
        <Icon name="settings" />
      </IconButton>
      <IconButton variant="tonal" size="m" aria-label="Settings">
        <Icon name="settings" />
      </IconButton>
      <IconButton variant="outlined" size="m" aria-label="Settings">
        <Icon name="settings" />
      </IconButton>
    </>
  );
}
<button
  type="button"
  class="m3e-icon-button m3e-icon-button--standard m3e-icon-button--size-m"
  aria-label="Settings"
>
  <span class="m3e-icon" aria-hidden="true">settings</span>
</button>
<button
  type="button"
  class="m3e-icon-button m3e-icon-button--filled m3e-icon-button--size-m"
  aria-label="Settings"
>
  <span class="m3e-icon" aria-hidden="true">settings</span>
</button>
<button
  type="button"
  class="m3e-icon-button m3e-icon-button--tonal m3e-icon-button--size-m"
  aria-label="Settings"
>
  <span class="m3e-icon" aria-hidden="true">settings</span>
</button>
<button
  type="button"
  class="m3e-icon-button m3e-icon-button--outlined m3e-icon-button--size-m"
  aria-label="Settings"
>
  <span class="m3e-icon" aria-hidden="true">settings</span>
</button>

Run the HTML version (CSS classes only).

Sizes and widths

Extra small to extra large, each narrow, default (uniform) or wide.

React HTML
import { Icon, IconButton } from '@kyralabs/m3exp-web/react';

const SIZES = ['xs', 's', 'm', 'l', 'xl'] as const;

export default function Sizes() {
  return (
    <>
      {SIZES.map((size) => (
        <IconButton key={size} variant="filled" size={size} aria-label={`Add (${size})`}>
          <Icon name="add" />
        </IconButton>
      ))}
      <IconButton variant="tonal" size="m" width="narrow" aria-label="Add (narrow)">
        <Icon name="add" />
      </IconButton>
      <IconButton variant="tonal" size="m" width="wide" aria-label="Add (wide)">
        <Icon name="add" />
      </IconButton>
    </>
  );
}
<button
  type="button"
  class="m3e-icon-button m3e-icon-button--filled m3e-icon-button--size-xs"
  aria-label="Add (xs)"
>
  <span class="m3e-icon" aria-hidden="true">add</span>
</button>
<button
  type="button"
  class="m3e-icon-button m3e-icon-button--filled m3e-icon-button--size-s"
  aria-label="Add (s)"
>
  <span class="m3e-icon" aria-hidden="true">add</span>
</button>
<button
  type="button"
  class="m3e-icon-button m3e-icon-button--filled m3e-icon-button--size-m"
  aria-label="Add (m)"
>
  <span class="m3e-icon" aria-hidden="true">add</span>
</button>
<button
  type="button"
  class="m3e-icon-button m3e-icon-button--filled m3e-icon-button--size-l"
  aria-label="Add (l)"
>
  <span class="m3e-icon" aria-hidden="true">add</span>
</button>
<button
  type="button"
  class="m3e-icon-button m3e-icon-button--filled m3e-icon-button--size-xl"
  aria-label="Add (xl)"
>
  <span class="m3e-icon" aria-hidden="true">add</span>
</button>
<button
  type="button"
  class="m3e-icon-button m3e-icon-button--tonal m3e-icon-button--size-m m3e-icon-button--width-narrow"
  aria-label="Add (narrow)"
>
  <span class="m3e-icon" aria-hidden="true">add</span>
</button>
<button
  type="button"
  class="m3e-icon-button m3e-icon-button--tonal m3e-icon-button--size-m m3e-icon-button--width-wide"
  aria-label="Add (wide)"
>
  <span class="m3e-icon" aria-hidden="true">add</span>
</button>

Run the HTML version (CSS classes only).

Toggles

With isSelected or defaultSelected an icon button toggles (aria-pressed): the icon fills and the shape morphs.

React HTML
import { Icon, IconButton } from '@kyralabs/m3exp-web/react';

export default function Toggle() {
  return (
    <>
      <IconButton variant="standard" size="m" aria-label="Favorite" defaultSelected>
        <Icon name="favorite" />
      </IconButton>
      <IconButton variant="filled" size="m" aria-label="Favorite" defaultSelected={false}>
        <Icon name="favorite" />
      </IconButton>
      <IconButton variant="tonal" size="m" aria-label="Bookmark" defaultSelected>
        <Icon name="bookmark" />
      </IconButton>
      <IconButton
        variant="outlined"
        size="m"
        shape="square"
        aria-label="Bookmark"
        defaultSelected={false}
      >
        <Icon name="bookmark" />
      </IconButton>
    </>
  );
}
<button
  type="button"
  class="m3e-icon-button m3e-icon-button--standard m3e-icon-button--size-m"
  aria-label="Favorite"
  aria-pressed="true"
>
  <span class="m3e-icon" aria-hidden="true">favorite</span>
</button>
<button
  type="button"
  class="m3e-icon-button m3e-icon-button--filled m3e-icon-button--size-m"
  aria-label="Favorite"
  aria-pressed="false"
>
  <span class="m3e-icon" aria-hidden="true">favorite</span>
</button>
<button
  type="button"
  class="m3e-icon-button m3e-icon-button--tonal m3e-icon-button--size-m"
  aria-label="Bookmark"
  aria-pressed="true"
>
  <span class="m3e-icon" aria-hidden="true">bookmark</span>
</button>
<button
  type="button"
  class="m3e-icon-button m3e-icon-button--outlined m3e-icon-button--size-m m3e-icon-button--square"
  aria-label="Bookmark"
  aria-pressed="false"
>
  <span class="m3e-icon" aria-hidden="true">bookmark</span>
</button>

Run the HTML version (CSS classes only).

Usage

Compact actions with a clear system icon; tooltip on hover (m3.material.io Icon buttons).

API

React

<IconButton>

Icon button (COMPONENT_SPEC §2). With isSelected or defaultSelected it is an IconToggleButton.

Prop Type Default Description
children required ReactNode The icon, e.g. <Icon name="favorite" />.
aria-label string Required unless aria-labelledby names the button (icon-only control).
variant "filled" | "tonal" | "outlined" | "standard" standard Color configuration. Default standard (inherits the surrounding content color).
vibrant boolean | undefined Token colors for standard/outlined instead of the inherited color (Compose "vibrant").
size "xs" | "s" | "m" | "l" | "xl" s Default s (40dp).
width "default" | "narrow" | "wide" default Default default (uniform).
shape "round" | "square" round Default round.
type 'button' | 'submit' | 'reset'
isSelected boolean
defaultSelected boolean
className string
isDisabled boolean
onChange (isSelected: boolean) => void
onPress (e: PressEvent) => void

Also accepts HTML attributes not listed here.

<IconToggleButton>

Icon toggle button (COMPONENT_SPEC §2): aria-pressed; the selected icon is filled (FILL 1). Press and selection morph on the default effects spring (IconButton.kt).

Prop Type Default Description
children required ReactNode The icon, e.g. <Icon name="favorite" />.
aria-label string Required unless aria-labelledby names the button (icon-only control).
variant "filled" | "tonal" | "outlined" | "standard" standard Color configuration. Default standard (inherits the surrounding content color).
vibrant boolean | undefined Token colors for standard/outlined instead of the inherited color (Compose "vibrant").
size "xs" | "s" | "m" | "l" | "xl" s Default s (40dp).
width "default" | "narrow" | "wide" default Default default (uniform).
shape "round" | "square" round Default round.
isSelected boolean
defaultSelected boolean
className string
isDisabled boolean
onChange (isSelected: boolean) => void
onPress (e: PressEvent) => void

Also accepts HTML attributes not listed here.

iconButtonClassName()

Classes for an icon button (the m3e-css contract).

CSS

icon-button.css

@import '@kyralabs/m3e-css/icon-button.css';

Icon button and icon toggle button (COMPONENT_SPEC §2). No JavaScript needed.

<button class="m3e-icon-button m3e-icon-button--filled" aria-label="Favorite">
  <span class="m3e-icon" aria-hidden="true">favorite</span>
</button>

Variants: --standard (default) --filled --tonal --outlined, plus --vibrant for standard and outlined. Sizes: --size-xs --size-s (default) --size-m --size-l --size-xl. Widths: --width-narrow --width-default (default) --width-wide. Shapes: --round (default) --square. aria-pressed makes a toggle; selected icons switch to FILL 1 (COMPONENT_SPEC §2).

66 custom properties it reads
Custom property Default
--m3e-comp-filled-icon-button-color var(--m3e-sys-color-on-primary)
--m3e-comp-filled-icon-button-color-disabled var(--m3e-sys-color-on-surface)
--m3e-comp-filled-icon-button-color-selected var(--m3e-sys-color-on-primary)
--m3e-comp-filled-icon-button-color-unselected var(--m3e-sys-color-on-surface-variant)
--m3e-comp-filled-icon-button-container-color var(--m3e-sys-color-primary)
--m3e-comp-filled-icon-button-container-color-selected var(--m3e-sys-color-primary)
--m3e-comp-filled-icon-button-container-color-unselected var(--m3e-sys-color-surface-container)
--m3e-comp-filled-tonal-icon-button-color var(--m3e-sys-color-on-secondary-container)
--m3e-comp-filled-tonal-icon-button-color-disabled var(--m3e-sys-color-on-surface)
--m3e-comp-filled-tonal-icon-button-color-selected var(--m3e-sys-color-on-secondary)
--m3e-comp-filled-tonal-icon-button-color-unselected var(--m3e-sys-color-on-secondary-container)
--m3e-comp-filled-tonal-icon-button-container-color var(--m3e-sys-color-secondary-container)
--m3e-comp-filled-tonal-icon-button-container-color-selected var(--m3e-sys-color-secondary)
--m3e-comp-large-icon-button-container-height 96px
--m3e-comp-large-icon-button-container-shape-pressed var(--m3e-sys-shape-corner-large)
--m3e-comp-large-icon-button-container-shape-square var(--m3e-sys-shape-corner-extra-large)
--m3e-comp-large-icon-button-icon-size 32px
--m3e-comp-large-icon-button-narrow-leading-space 16px
--m3e-comp-large-icon-button-outlined-outline-width 2px
--m3e-comp-large-icon-button-uniform-leading-space 32px
--m3e-comp-large-icon-button-wide-leading-space 48px
--m3e-comp-medium-icon-button-container-height 56px
--m3e-comp-medium-icon-button-container-shape-pressed var(--m3e-sys-shape-corner-medium)
--m3e-comp-medium-icon-button-container-shape-square var(--m3e-sys-shape-corner-large)
--m3e-comp-medium-icon-button-default-leading-space 16px
--m3e-comp-medium-icon-button-icon-size 24px
--m3e-comp-medium-icon-button-narrow-leading-space 12px
--m3e-comp-medium-icon-button-outlined-outline-width 1px
--m3e-comp-medium-icon-button-wide-leading-space 24px
--m3e-comp-minimum-interactive-component-size 48px
--m3e-comp-outlined-icon-button-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-outlined-icon-button-color-disabled var(--m3e-sys-color-on-surface)
--m3e-comp-outlined-icon-button-color-selected var(--m3e-sys-color-inverse-on-surface)
--m3e-comp-outlined-icon-button-color-unselected var(--m3e-sys-color-on-surface-variant)
--m3e-comp-outlined-icon-button-container-color-selected var(--m3e-sys-color-inverse-surface)
--m3e-comp-outlined-icon-button-opacity-disabled 0.38
--m3e-comp-outlined-icon-button-outline-color var(--m3e-sys-color-outline-variant)
--m3e-comp-outlined-icon-button-outline-color-unselected var(--m3e-sys-color-outline-variant)
--m3e-comp-small-icon-button-container-height 40px
--m3e-comp-small-icon-button-container-shape-pressed var(--m3e-sys-shape-corner-small)
--m3e-comp-small-icon-button-container-shape-square var(--m3e-sys-shape-corner-medium)
--m3e-comp-small-icon-button-default-leading-space 8px
--m3e-comp-small-icon-button-icon-size 24px
--m3e-comp-small-icon-button-narrow-leading-space 4px
--m3e-comp-small-icon-button-outlined-outline-width 1px
--m3e-comp-small-icon-button-wide-leading-space 14px
--m3e-comp-standard-icon-button-color var(--m3e-sys-color-on-surface-variant)
--m3e-comp-standard-icon-button-color-disabled var(--m3e-sys-color-on-surface)
--m3e-comp-standard-icon-button-color-selected var(--m3e-sys-color-primary)
--m3e-comp-standard-icon-button-color-unselected var(--m3e-sys-color-on-surface-variant)
--m3e-comp-x-large-icon-button-container-height 136px
--m3e-comp-x-large-icon-button-container-shape-pressed var(--m3e-sys-shape-corner-large)
--m3e-comp-x-large-icon-button-container-shape-square var(--m3e-sys-shape-corner-extra-large)
--m3e-comp-x-large-icon-button-default-leading-space 48px
--m3e-comp-x-large-icon-button-icon-size 40px
--m3e-comp-x-large-icon-button-narrow-leading-space 32px
--m3e-comp-x-large-icon-button-outlined-outline-width 3px
--m3e-comp-x-large-icon-button-wide-leading-space 72px
--m3e-comp-x-small-icon-button-container-height 32px
--m3e-comp-x-small-icon-button-container-shape-pressed var(--m3e-sys-shape-corner-small)
--m3e-comp-x-small-icon-button-container-shape-square var(--m3e-sys-shape-corner-medium)
--m3e-comp-x-small-icon-button-default-leading-space 6px
--m3e-comp-x-small-icon-button-icon-size 20px
--m3e-comp-x-small-icon-button-narrow-leading-space 4px
--m3e-comp-x-small-icon-button-outlined-outline-width 1px
--m3e-comp-x-small-icon-button-wide-leading-space 10px

Accessibility

<button aria-label>; toggle aria-pressed.

toggle shows FILL 0 unselected, FILL 1 selected.

Every interactive part has a target of at least 48 × 48 px, a visible focus ring for keyboard focus only, and works under forced-colors and prefers-reduced-motion.

Sources

Values come from androidx Compose Material3 1.5.0-beta01 at 19991f2c and from m3.material.io, as cited below from COMPONENT_SPEC §2 and the m3e-css sources.

m3.material.io/components/icon-buttons/overview; *IconButtonTokens.kt.

Verified (generated): {XSmall…XLarge}IconButtonTokens.kt@19991f2c; width = icon + leading + trailing space (IconButtonDefaults.*ContainerSize). Selected: round → SelectedContainerShapeRound, square → SelectedContainerShapeSquare (CornerFull). Colors: standard and outlined inherit the content color (Compose LocalContentColor); --vibrant uses StandardIconButtonTokens / OutlinedIconButtonTokens; filled = FilledIconButtonTokens; tonal = FilledTonalIconButtonTokens; selected outlined toggles drop the border.

icon-button.css sources

(compose/material3 @ 19991f2c):

  • Size: IconButtonDefaults.*ContainerSize (IconButtonDefaults.kt): height = ContainerHeight, width = IconSize + leading + trailing space of the width option (Uniform uses the default leading space twice; LargeIconButtonTokens names it UniformLeadingSpace).
  • Shapes: {XSmall,Small,Medium,Large,XLarge}IconButtonTokens. Round = CircleShape (half the smaller side). Selected: round → SelectedContainerShapeRound, square → SelectedContainerShapeSquare (CornerFull = round). Press and selection both morph on the default effects spring (IconButton.kt shapeForInteraction: "intentional here to prevent any bounce").
  • Colors: IconButtonDefaults. Standard and outlined use LocalContentColor (here: the inherited color) for content and the outline; --vibrant uses StandardIconButtonTokens / OutlinedIconButtonTokens instead (iconButtonVibrantColors). Filled: FilledIconButtonTokens; tonal: FilledTonalIconButtonTokens. Selected outlined toggles drop the border.
  • Touch target: ≥ LocalMinimumInteractiveComponentSize in both axes via ::after.