Buttons

Materialize is a modern responsive CSS framework based on Material Design by Google.

Explore all ten button families from Material 3's All buttons: buttons, toggles, icon buttons, icon toggles, split buttons, standard groups, connected groups, FABs, extended FABs, and FAB menus.

Button styles are included in Materialize CSS. Import initMaterialButtons from kmaterialize for toggles, groups, and menus.

Try a button. Demo actions and selection changes appear here.

Filled

A prominent action, such as saving your work.

<!-- Use native buttons for actions; add an icon only when it helps. -->
<button type="button" class="btn btn-expressive filled" data-action="Save">Save</button>
<button type="button" class="btn btn-expressive filled" data-action="Save with icon">
  <i class="material-icons" aria-hidden="true">save</i>
  Save
</button>
<button
  type="button"
  class="btn btn-expressive filled btn-square"
  data-action="Continue"
>
  <i class="material-icons" aria-hidden="true">arrow_forward</i>
  Continue
</button>

Tonal

A supporting action with a tinted surface.

<!-- Use native buttons for actions; add an icon only when it helps. -->
<button type="button" class="btn btn-expressive tonal" data-action="Save">Save</button>
<button type="button" class="btn btn-expressive tonal" data-action="Save with icon">
  <i class="material-icons" aria-hidden="true">save</i>
  Save
</button>
<button
  type="button"
  class="btn btn-expressive tonal btn-square"
  data-action="Continue"
>
  <i class="material-icons" aria-hidden="true">arrow_forward</i>
  Continue
</button>

Outlined

An alternative action with a visible boundary.

<!-- Use native buttons for actions; add an icon only when it helps. -->
<button type="button" class="btn btn-expressive outlined" data-action="Save">
  Save
</button>
<button type="button" class="btn btn-expressive outlined" data-action="Save with icon">
  <i class="material-icons" aria-hidden="true">save</i>
  Save
</button>
<button
  type="button"
  class="btn btn-expressive outlined btn-square"
  data-action="Continue"
>
  <i class="material-icons" aria-hidden="true">arrow_forward</i>
  Continue
</button>

Elevated

A raised action that separates from a patterned surface.

<!-- Use native buttons for actions; add an icon only when it helps. -->
<button type="button" class="btn btn-expressive elevated" data-action="Save">
  Save
</button>
<button type="button" class="btn btn-expressive elevated" data-action="Save with icon">
  <i class="material-icons" aria-hidden="true">save</i>
  Save
</button>
<button
  type="button"
  class="btn btn-expressive elevated btn-square"
  data-action="Continue"
>
  <i class="material-icons" aria-hidden="true">arrow_forward</i>
  Continue
</button>

Text

A quiet action with no persistent container.

<!-- Use native buttons for actions; add an icon only when it helps. -->
<button type="button" class="btn btn-expressive text" data-action="Save">Save</button>
<button type="button" class="btn btn-expressive text" data-action="Save with icon">
  <i class="material-icons" aria-hidden="true">save</i>
  Save
</button>
<button type="button" class="btn btn-expressive text btn-square" data-action="Continue">
  <i class="material-icons" aria-hidden="true">arrow_forward</i>
  Continue
</button>

Toggle buttons

A toggle remembers an on/off choice. aria-pressed drives the selected color and shape.

<!-- initMaterialButtons() updates aria-pressed when each button is activated. -->
<button
  type="button"
  class="btn btn-expressive tonal btn-toggle"
  aria-pressed="false"
  data-action="Favorite"
>
  <i class="material-icons" aria-hidden="true">favorite_border</i>
  Favorite
</button>
<button
  type="button"
  class="btn btn-expressive filled btn-toggle"
  aria-pressed="true"
  data-action="Following"
>
  <i class="material-icons" aria-hidden="true">check</i>
  Following
</button>
<button
  type="button"
  class="btn btn-expressive outlined btn-toggle"
  aria-pressed="false"
  data-action="Notifications"
>
  <i class="material-icons" aria-hidden="true">notifications</i>
  Notifications
</button>

Icon buttons

Compact actions with an accessible name. Choose standard, filled, tonal, or outlined styling.

<!-- Icon-only buttons always need an accessible name. -->
<button
  type="button"
  class="btn btn-expressive btn-icon-standard btn-icon"
  aria-label="Settings"
  title="Settings"
  data-action="Settings"
>
  <i class="material-icons" aria-hidden="true">settings</i>
</button>
<button
  type="button"
  class="btn btn-expressive filled btn-icon"
  aria-label="Add item"
  title="Add item"
  data-action="Add item"
>
  <i class="material-icons" aria-hidden="true">add</i>
</button>
<button
  type="button"
  class="btn btn-expressive tonal btn-icon"
  aria-label="Share"
  title="Share"
  data-action="Share"
>
  <i class="material-icons" aria-hidden="true">share</i>
</button>
<button
  type="button"
  class="btn btn-expressive outlined btn-icon"
  aria-label="Print"
  title="Print"
  data-action="Print"
>
  <i class="material-icons" aria-hidden="true">print</i>
</button>

Toggle icon buttons

Combine btn-icon and btn-toggle for selectable icon actions.

<!-- Keep the accessible name stable; aria-pressed announces the current state. -->
<button
  type="button"
  class="btn btn-expressive btn-icon-standard btn-icon btn-toggle"
  aria-label="Favorite"
  title="Favorite"
  aria-pressed="false"
  data-action="Favorite"
>
  <i class="material-icons" aria-hidden="true">favorite</i>
</button>
<button
  type="button"
  class="btn btn-expressive filled btn-icon btn-toggle"
  aria-label="Bookmark"
  title="Bookmark"
  aria-pressed="true"
  data-action="Bookmark"
>
  <i class="material-icons" aria-hidden="true">bookmark</i>
</button>
<button
  type="button"
  class="btn btn-expressive tonal btn-icon btn-toggle"
  aria-label="Mute"
  title="Mute"
  aria-pressed="false"
  data-action="Mute"
>
  <i class="material-icons" aria-hidden="true">volume_off</i>
</button>
<button
  type="button"
  class="btn btn-expressive outlined btn-icon btn-toggle"
  aria-label="Pin"
  title="Pin"
  aria-pressed="false"
  data-action="Pin"
>
  <i class="material-icons" aria-hidden="true">push_pin</i>
</button>

Split buttons

The main action and its related menu are separate buttons. Open the menu with a click or arrow key; Escape closes it.

<!-- Use standard dropdown markup and a unique data-target for each menu. -->
<div class="btn-split">
  <!-- Primary action. -->
  <button type="button" class="btn btn-expressive filled" data-action="Send">
    <i class="material-icons" aria-hidden="true">send</i>
    Send
  </button>
  <!-- Dropdown trigger: data-target matches the menu ID. -->
  <button
    type="button"
    class="btn btn-expressive filled btn-icon btn-menu-trigger dropdown-trigger no-autoinit"
    aria-label="More send options"
    aria-haspopup="menu"
    aria-expanded="false"
    aria-controls="send-options"
    data-target="send-options"
  >
    <i class="material-icons" aria-hidden="true">expand_more</i>
  </button>
  <ul id="send-options" class="dropdown-content">
    <li><a href="#!" data-action="Schedule send">Schedule send</a></li>
    <li><a href="#!" data-action="Save draft">Save draft</a></li>
  </ul>
</div>
import { Dropdown } from 'kmaterialize';

// Initialize once. The no-autoinit class opts out of the site's general setup.
Dropdown.init(document.querySelectorAll('.btn-split .dropdown-trigger'), {
  alignment: 'right',
  constrainWidth: false, // Allow menu labels to determine its width.
  coverTrigger: false, // Place the menu beside the trigger, without covering it.
  closeOnClick: true,
});

Standard button groups

Group related actions with space between them. Hovering gives an action a little more room; reduced-motion settings disable the transition.

<!-- Each button remains a separate action. Use role="group" with a descriptive label. -->
<div class="btn-group btn-group-standard" role="group" aria-label="Playback">
  <button type="button" class="btn btn-expressive tonal" data-action="Previous track">
    <i class="material-icons" aria-hidden="true">skip_previous</i>
    Back
  </button>
  <button
    type="button"
    class="btn btn-expressive filled btn-toggle"
    aria-pressed="false"
    data-action="Playback paused"
  >
    <i class="material-icons" aria-hidden="true">pause</i>
    Pause
  </button>
  <button type="button" class="btn btn-expressive tonal" data-action="Next track">
    <i class="material-icons" aria-hidden="true">skip_next</i>
    Next
  </button>
</div>

Connected button groups

Use a connected outline for related choices. These examples show single and multiple selection.

<!-- Single selection: choosing one option clears the others. -->
<div
  class="btn-group btn-group-connected"
  role="group"
  aria-label="Travel mode"
  data-selection="single"
>
  <button
    type="button"
    class="btn btn-expressive tonal btn-toggle"
    aria-pressed="true"
    data-action="Walk"
  >
    <i class="material-icons" aria-hidden="true">directions_walk</i>
    Walk
  </button>
  <button
    type="button"
    class="btn btn-expressive tonal btn-toggle"
    aria-pressed="false"
    data-action="Bike"
  >
    <i class="material-icons" aria-hidden="true">directions_bike</i>
    Bike
  </button>
  <button
    type="button"
    class="btn btn-expressive tonal btn-toggle"
    aria-pressed="false"
    data-action="Drive"
  >
    <i class="material-icons" aria-hidden="true">directions_car</i>
    Drive
  </button>
</div>
<!-- Multiple selection: each setting can be toggled independently. -->
<div
  class="btn-group btn-group-connected"
  role="group"
  aria-label="Text formatting"
  data-selection="multiple"
>
  <button
    type="button"
    class="btn btn-expressive outlined btn-icon btn-toggle"
    aria-label="Bold"
    aria-pressed="true"
    data-action="Bold"
  >
    <i class="material-icons" aria-hidden="true">format_bold</i>
  </button>
  <button
    type="button"
    class="btn btn-expressive outlined btn-icon btn-toggle"
    aria-label="Italic"
    aria-pressed="false"
    data-action="Italic"
  >
    <i class="material-icons" aria-hidden="true">format_italic</i>
  </button>
  <button
    type="button"
    class="btn btn-expressive outlined btn-icon btn-toggle"
    aria-label="Underline"
    aria-pressed="false"
    data-action="Underline"
  >
    <i class="material-icons" aria-hidden="true">format_underlined</i>
  </button>
</div>

Floating action buttons (FABs)

Use a floating action button for a primary action. These examples add container colors and compact, regular, and large sizes.

<!-- The btn-fab class is an opt-in addition to the existing floating button styles. -->
<button
  type="button"
  class="btn btn-expressive btn-fab fab-small"
  aria-label="Add note"
  title="Add note"
  data-action="Add note"
>
  <i class="material-icons" aria-hidden="true">add</i>
</button>
<button
  type="button"
  class="btn btn-expressive btn-fab"
  aria-label="Create note"
  title="Create note"
  data-action="Create note"
>
  <i class="material-icons" aria-hidden="true">edit</i>
</button>
<button
  type="button"
  class="btn btn-expressive btn-fab fab-secondary"
  aria-label="Take photo"
  title="Take photo"
  data-action="Take photo"
>
  <i class="material-icons" aria-hidden="true">photo_camera</i>
</button>
<button
  type="button"
  class="btn btn-expressive btn-fab fab-large fab-tertiary"
  aria-label="Create project"
  title="Create project"
  data-action="Create project"
>
  <i class="material-icons" aria-hidden="true">add</i>
</button>

For the existing fixed-position launcher, see Floating Action Button.

Extended FABs

Add a visible label when an icon alone would be unclear.

<!-- Use btn-fab-extended together with btn-fab. -->
<button
  type="button"
  class="btn btn-expressive btn-fab btn-fab-extended"
  data-action="Compose"
>
  <i class="material-icons" aria-hidden="true">edit</i>
  Compose
</button>
<button
  type="button"
  class="btn btn-expressive btn-fab btn-fab-extended fab-secondary"
  data-action="New project"
>
  <i class="material-icons" aria-hidden="true">add</i>
  New project
</button>

FAB menus

A launcher reveals a small set of related actions. Materialize handles positioning, keyboard navigation, and closing after selection or an outside click.

<!-- Render the popup in document.body with the dropdown container option. -->
<div class="btn-fab-menu">
  <!-- Menu appears above the launcher. -->
  <ul id="create-options" class="dropdown-content">
    <li>
      <a href="#!" data-action="New document">
        <i class="material-icons" aria-hidden="true">description</i>
        Document
      </a>
    </li>
    <li>
      <a href="#!" data-action="New folder">
        <i class="material-icons" aria-hidden="true">folder</i>
        Folder
      </a>
    </li>
    <li>
      <a href="#!" data-action="Upload file">
        <i class="material-icons" aria-hidden="true">upload_file</i>
        Upload
      </a>
    </li>
  </ul>
  <button
    type="button"
    class="btn btn-expressive btn-fab btn-menu-trigger dropdown-trigger no-autoinit"
    aria-label="Create"
    aria-haspopup="menu"
    aria-expanded="false"
    aria-controls="create-options"
    data-target="create-options"
  >
    <i class="material-icons" aria-hidden="true">add</i>
  </button>
</div>
import { Dropdown } from 'kmaterialize';

// Initialize once. The no-autoinit class opts out of the site's general setup.
Dropdown.init(document.querySelectorAll('.btn-fab-menu .dropdown-trigger'), {
  container: document.body, // Render outside the FAB container.
  alignment: 'right',
  constrainWidth: false, // Allow menu labels to determine its width.
  coverTrigger: false, // Place the menu beside the trigger, without covering it.
  closeOnClick: true,
});

Sizes, shapes, and icon widths

Use btn-xs, btn-sm, btn-md, btn-lg, or btn-xl. Rounded is the default; btn-square gives a softer rectangular shape.

<!-- Size modifiers are opt-in and do not change the legacy btn-small / btn-large helpers. -->
<button type="button" class="btn btn-expressive tonal btn-xs" data-action="XS button">
  XS
</button>
<button type="button" class="btn btn-expressive tonal btn-sm" data-action="SM button">
  SM
</button>
<button type="button" class="btn btn-expressive tonal btn-md" data-action="MD button">
  MD
</button>
<button type="button" class="btn btn-expressive tonal btn-lg" data-action="LG button">
  LG
</button>
<button type="button" class="btn btn-expressive tonal btn-xl" data-action="XL button">
  XL
</button>
<button
  type="button"
  class="btn btn-expressive outlined btn-icon btn-md btn-icon-narrow"
  aria-label="Narrow back button"
  data-action="Back"
>
  <i class="material-icons" aria-hidden="true">arrow_back</i>
</button>
<button
  type="button"
  class="btn btn-expressive filled btn-icon btn-md btn-icon-wide"
  aria-label="Wide forward button"
  data-action="Forward"
>
  <i class="material-icons" aria-hidden="true">arrow_forward</i>
</button>

Disabled buttons

Use the native disabled attribute so unavailable actions cannot receive clicks or keyboard activation.

<!-- Native disabled state applies to all variants. -->
<button
  type="button"
  class="btn btn-expressive filled"
  disabled
  data-action="Should not run"
>
  Unavailable
</button>
<button
  type="button"
  class="btn btn-expressive tonal"
  disabled
  data-action="Should not run"
>
  Unavailable
</button>
<button
  type="button"
  class="btn btn-expressive outlined"
  disabled
  data-action="Should not run"
>
  Unavailable
</button>
<button
  type="button"
  class="btn btn-expressive elevated"
  disabled
  data-action="Should not run"
>
  Unavailable
</button>
<button
  type="button"
  class="btn btn-expressive text"
  disabled
  data-action="Should not run"
>
  Unavailable
</button>
<button
  type="button"
  class="btn btn-expressive tonal btn-icon btn-toggle"
  aria-label="Favorite unavailable"
  aria-pressed="true"
  disabled
>
  <i class="material-icons" aria-hidden="true">favorite</i>
</button>

Hierarchy and placement

Give the key action more visual emphasis and keep related alternatives nearby. These arrangements mirror the combinations on the reference page.

<!-- Primary action with a quieter alternative. -->
<div class="btn-group" role="group" aria-label="Save changes">
  <button type="button" class="btn btn-expressive text" data-action="Cancel">
    Cancel
  </button>
  <button type="button" class="btn btn-expressive filled" data-action="Save">
    Save
  </button>
</div>
<!-- Outline and fill distinguish two important actions. -->
<div class="btn-group" role="group" aria-label="Review changes">
  <button type="button" class="btn btn-expressive outlined" data-action="Review">
    Review
  </button>
  <button type="button" class="btn btn-expressive filled" data-action="Confirm">
    Confirm
  </button>
</div>
<!-- Two supporting actions. -->
<div class="btn-group" role="group" aria-label="More information">
  <button type="button" class="btn btn-expressive text" data-action="Learn more">
    Learn more
  </button>
  <button type="button" class="btn btn-expressive outlined" data-action="View all">
    View all
  </button>
</div>
<!-- One clear primary action is enough for a simple step. -->
<button type="button" class="btn btn-expressive filled" data-action="Continue">
  Continue
</button>

Initialization & classes

Native buttons already support Enter and Space. Initialize once per container for toggles and menus. Split buttons and FAB menus use the standard Materialize Dropdown for positioning and keyboard navigation. The initializer below already calls Dropdown.init; the standalone snippets above are alternatives. New selection controls use aria-pressed; menu triggers use aria-expanded.

// Import once, after the site's Materialize stylesheet.
import { initMaterialButtons } from 'kmaterialize';

const root = document.querySelector<HTMLElement>('#button-examples')!;
const destroy = initMaterialButtons(root);

// Application actions stay outside the reusable UI behavior.
root.addEventListener('buttonaction', (event) => {
  const { action, pressed } = (event as CustomEvent).detail;
  console.log(action, pressed);
});

// When removing the view:
// destroy();
ClassPurpose
btn-expressiveRounded layout, inline icons, focus and pressed states.
btn-xs / btn-sm / btn-md / btn-lg / btn-xlExplicit 32 / 40 / 56 / 96 / 136px heights.
btn-squareSoft rectangular shape.
btn-toggleSelection driven by aria-pressed.
btn-icon / btn-icon-standardIcon-only geometry and unfilled styling.
btn-icon-narrow / btn-icon-wideNarrow and wide icon containers.
btn-group / btn-group-standardSpaced related actions.
btn-group-connectedConnected single- or multi-select choices.
btn-split / btn-menu-triggerPrimary action paired with a standard dropdown.
btn-fab / btn-fab-extendedContainer-color FABs, with or without labels.
fab-small / fab-large / fab-secondary / fab-tertiaryFAB sizes and color alternatives.
btn-fab-menuFAB launcher for a standard dropdown.
SCSS — reusable button classes
// Add after Materialize. Opt-in classes preserve existing .btn styling elsewhere.
.btn.btn-expressive {
  --button-height: 40px;
  --button-padding: 24px;
  --button-icon: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: var(--button-height);
  min-height: var(--button-height);
  padding: 0 var(--button-padding);
  border-radius: 999px;
  font-size: 14px;
  line-height: 1.2;
  text-transform: none;
  white-space: nowrap;
  transition:
    border-radius 180ms ease,
    background-color 180ms ease,
    flex-grow 180ms ease;
  > .material-icons {
    position: static;
    transform: none;
    font-size: var(--button-icon);
    line-height: 1;
    flex-shrink: 0;
  }
  &:focus-visible {
    outline: 3px solid var(--md-sys-color-on-surface);
    outline-offset: 3px;
  }
  &:active:not(:disabled) {
    border-radius: 12px;
  }
  &.btn-xs {
    --button-height: 32px;
    --button-padding: 12px;
  }
  &.btn-sm {
    --button-height: 40px;
  }
  &.btn-md {
    --button-height: 56px;
    --button-icon: 24px;
    font-size: 16px;
  }
  &.btn-lg {
    --button-height: 96px;
    --button-padding: 40px;
    --button-icon: 32px;
    font-size: 24px;
  }
  &.btn-xl {
    --button-height: 136px;
    --button-padding: 48px;
    --button-icon: 40px;
    font-size: 32px;
  }
  &.btn-square {
    border-radius: calc(var(--button-height) * 0.28);
  }
}

// Toggle state belongs in aria-pressed, so visual and accessible states agree.
.btn.btn-toggle[aria-pressed='true'] {
  background: var(--md-sys-color-primary);
  color: var(--md-sys-color-on-primary);
  border-color: transparent;
  border-radius: 16px;
}
.btn.btn-icon {
  width: var(--button-height);
  min-width: var(--button-height);
  padding: 0;
}
.btn.btn-icon-standard {
  background: transparent;
  color: var(--md-sys-color-on-surface-variant);
  box-shadow: none;
}
.btn.btn-icon-standard:hover:not(:disabled) {
  background: var(--md-sys-color-surface-variant);
}
.btn.btn-icon.btn-toggle[aria-pressed='true'] {
  background: var(--md-sys-color-primary);
  color: var(--md-sys-color-on-primary);
}
.btn.btn-icon.btn-icon-narrow {
  width: calc(var(--button-height) * 0.8);
  min-width: calc(var(--button-height) * 0.8);
}
.btn.btn-icon.btn-icon-wide {
  width: calc(var(--button-height) * 1.5);
}

// Standard groups keep gaps; connected groups share the outer silhouette.
.btn-group {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  max-width: 100%;
}
.btn-group > .btn {
  flex: 1 1 0;
  min-width: 0;
}
.btn-group-standard {
  width: 100%;
  max-width: 480px;
}
.btn-group-standard > .btn:hover:not(:disabled) {
  flex-grow: 1.2;
}
.btn-group-connected {
  gap: 2px;
}
.btn-group-connected > .btn.btn-expressive {
  border-radius: 4px;
}
.btn-group-connected > .btn.btn-expressive:first-child {
  border-start-start-radius: 999px;
  border-end-start-radius: 999px;
}
.btn-group-connected > .btn.btn-expressive:last-child {
  border-start-end-radius: 999px;
  border-end-end-radius: 999px;
}
// Keep the connected silhouette unchanged during selection and pointer presses.
.btn-group-connected > .btn.btn-expressive:active:not(:disabled) {
  border-radius: 4px;
  &:first-child {
    border-start-start-radius: 999px;
    border-end-start-radius: 999px;
  }
  &:last-child {
    border-start-end-radius: 999px;
    border-end-end-radius: 999px;
  }
}

// Split controls expose a separate primary action and menu action.
.btn-split {
  display: inline-flex;
  gap: 2px;
  position: relative;
  vertical-align: middle;
}
.btn-split > .btn:first-child {
  border-start-end-radius: 4px;
  border-end-end-radius: 4px;
}
.btn-split > .btn-menu-trigger {
  border-start-start-radius: 4px;
  border-end-start-radius: 4px;
}
.btn-menu-trigger > .material-icons {
  transition: transform 180ms ease;
}
.btn-menu-trigger[aria-expanded='true'] > .material-icons {
  transform: rotate(180deg);
}
// FABs use container colors and elevation; extended FABs also carry a label.
.btn.btn-fab {
  --button-height: 56px;
  --button-icon: 24px;
  width: var(--button-height);
  padding: 0;
  border-radius: 16px;
  background: var(--md-sys-color-primary-container);
  color: var(--md-sys-color-on-primary-container);
  box-shadow: 0 3px 8px color-mix(in srgb, var(--md-sys-color-shadow) 20%, transparent);
  &.fab-secondary {
    background: var(--md-sys-color-secondary-container);
    color: var(--md-sys-color-on-secondary-container);
  }
  &.fab-tertiary {
    background: var(--md-sys-color-tertiary-container);
    color: var(--md-sys-color-on-tertiary-container);
  }
  &.fab-small {
    --button-height: 40px;
    border-radius: 12px;
  }
  &.fab-large {
    --button-height: 96px;
    --button-icon: 36px;
    border-radius: 28px;
  }
  &.btn-fab-extended {
    width: auto;
    padding: 0 20px;
  }
}
.btn-fab-menu {
  position: relative;
  display: inline-flex;
}
.btn-fab-menu .btn-menu-trigger[aria-expanded='true'] > .material-icons {
  transform: rotate(45deg);
}

// Explicitly retain native disabled behavior across all custom variants.
.btn.btn-expressive:disabled,
.btn.btn-expressive.btn-toggle:disabled {
  background: color-mix(in srgb, var(--md-sys-color-on-surface) 12%, transparent);
  color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
  box-shadow: none;
  cursor: default;
}
@media (pointer: coarse) {
  .btn.btn-expressive::after {
    content: '';
    position: absolute;
    inset: -4px 0;
    min-height: 48px;
  }
  .btn-group {
    gap: 8px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .btn.btn-expressive,
  .btn-menu-trigger > .material-icons {
    transition: none;
  }
}
TypeScript — toggles, groups, and menus
import { Dropdown } from 'kmaterialize';

/** Initialize Materialize dropdowns and the additional button selection states. */
export function initMaterialButtons(root: HTMLElement): () => void {
  const events = new AbortController();

  const triggers = Array.from(root.querySelectorAll<HTMLElement>('.dropdown-trigger'));
  // Remember ownership before Dropdown moves a FAB menu into document.body.
  const menus = new Set(
    triggers.map((trigger) => document.getElementById(trigger.dataset.target!)!),
  );
  const owns = (element: Element) =>
    root.contains(element) || Array.from(menus).some((menu) => menu.contains(element));

  const dropdowns = triggers.map((trigger) =>
    Dropdown.init(trigger, {
      alignment: 'right',
      constrainWidth: false,
      coverTrigger: false,
      closeOnClick: true,
      // FAB popups escape their demo container; split menus keep their default parent.
      ...(trigger.closest('.btn-fab-menu') ? { container: document.body } : {}),
    }),
  );

  // Dropdown activates its focused item on Enter. Suppress the native follow-up
  // click when focus returns to a button trigger, which would reopen the menu.
  document.addEventListener(
    'keydown',
    (event) => {
      if (
        event.key === 'Enter' &&
        owns(event.target as Element) &&
        (event.target as Element).closest('.dropdown-content')
      ) {
        event.preventDefault();
      }
    },
    { capture: true, signal: events.signal },
  );

  document.addEventListener(
    'click',
    (event) => {
      const action = (event.target as Element).closest<HTMLElement>(
        '[data-action], .btn-toggle',
      );
      if (!action || !owns(action) || action.matches(':disabled')) return;
      // Demo menu actions are links, following the standard dropdown markup.
      if (action.matches('a[data-action]')) event.preventDefault();

      if (action.classList.contains('btn-toggle')) {
        const group = action.closest<HTMLElement>('[data-selection]');
        const selected = action.getAttribute('aria-pressed') === 'true';
        if (group?.dataset.selection === 'single') {
          group.querySelectorAll('.btn-toggle').forEach((item) => {
            item.setAttribute('aria-pressed', String(item === action));
          });
        } else {
          action.setAttribute('aria-pressed', String(!selected));
        }
      }

      // Keep application behavior separate from selection and dropdown behavior.
      if (action.dataset.action) {
        root.dispatchEvent(
          new CustomEvent('buttonaction', {
            bubbles: true,
            detail: {
              action: action.dataset.action,
              pressed: action.getAttribute('aria-pressed'),
            },
          }),
        );
      }
    },
    { signal: events.signal },
  );

  return () => {
    dropdowns.forEach((dropdown) => dropdown.destroy());
    events.abort();
  };
}