Skip to main content
Mat Expressive

Button

Material Expressive Button with size, shape, and toggle variations. Supports both the .mat-exp-button CSS class approach and the [matExpButton] directive.

Import Statement
import { MatExpButton } from '@ngm-dev/mat-exp';

Pre-requisites

Make sure either you have included mat-exp-all-styles, mat-exp-all-buttons-styles or mat-exp-button-styles in your global SCSS styles.

SCSS
@use '@ngm-dev/mat-exp' as mat-exp;

html {
  @include mat-exp.mat-exp-all-styles();
}

or

SCSS
@use '@ngm-dev/mat-exp' as mat-exp;

html {
  @include mat-exp.mat-exp-all-buttons-styles();
}

or

SCSS
@use '@ngm-dev/mat-exp' as mat-exp;

html {
  @include mat-exp.mat-exp-button-styles();
}

Usage

The styles for Material Expressive Button can be applied in two ways:

  1. Using the .mat-exp-button class with the data-* attributes
  2. Using the matExpButton directive

Using the .mat-exp-button class with the data-\* attributes

app.ts
import { MatButton } from '@angular/material/button';
@Component({
  selector: 'app-root',
  imports: [MatButton],
  template: `
    <button matButton="elevated" class="mat-exp-button" data-size="xs" data-shape="square">
      Elevated
    </button>
    <button matButton="tonal" class="mat-exp-button" data-size="s">Tonal</button>
  `,
})
export class App {}

Using the matExpButton directive

If you do not want to use data-* attributes and the .mat-exp-button class and want full type safety, you can use matExpButton directive.

app.ts
import { MatExpButton } from '@ngm-dev/mat-exp';
@Component({
  selector: 'app-root',
  imports: [MatButton, MatExpButton],
  template: `
    <button matButton="elevated" size="xs" shape="square" matExpButton>Elevated</button>
    <button matButton="tonal" size="s" matExpButton>Tonal</button>
  `,
})
export class App {}

Supported Variations

Mat Expressive Button supports the following variations:

  • Size: xs, s, m, l, xl
  • Shape: round, square
  • Toggle: selected, unselected
  • State: pressed (:active pseudo-selector)

Toggle Behavior

Mat Expressive Button supports a toggle state (selected / unselected), but toggling on click is only automatic inside a <mat-exp-button-group> — the group owns selection state for every button it manages.

Standalone toggle buttons (outside a <mat-exp-button-group>) do not flip toggle on click. This is intentional: a lone button's click handler only has one consumer to satisfy, so the library leaves the state transition up to you rather than guessing what a click should mean. Two-way bind toggle and flip it yourself in your own (click) handler:

app.ts
import { signal } from '@angular/core';
import { MatButton } from '@angular/material/button';
import { MatExpButton } from '@ngm-dev/mat-exp';

@Component({
  selector: 'app-root',
  imports: [MatButton, MatExpButton],
  template: `
    <button
      matButton="tonal"
      matExpButton
      [(toggle)]="favorited"
      (click)="favorited.set(favorited() === 'selected' ? 'unselected' : 'selected')"
    >
      Favorite
    </button>
  `,
})
export class App {
  protected readonly favorited = model<'selected' | 'unselected'>('unselected');
}

Inside a MatExpButtonGroup, don't do this — the group already manages toggle for its projected buttons based on the group's single-/multi-select state, and a manual click handler would fight the group's own state management.

Shape Morph

Mat Expressive Button supports this shape morphing by default.

Pressed State

According to Material 3 Design System Expressive guidelines, when pressed, buttons can morph to become more square. Both round and square buttons should have the same pressed shape.

When selected

In addition to changing shape when pressed, toggle buttons also change the resting shape from round (unselected) to square (selected).

If the resting unselected shape is square, the selected shape should be round.

Accessibility

The shape-morph transition (border-radius, padding-left/padding-right, box-shadow) automatically stops under prefers-reduced-motion: reduce — Angular Material's own matButton host, which matExpButton is always applied alongside, detects the setting and neutralizes the transition. See Reduced Motion for how this works across the library.

Playground

API

Data Attributes with .mat-exp-button class

Use these data-* attributes with the .mat-exp-button class to style the Angular Material Button component:

Attribute Default Possible values
data-size s xs, s, m, l, xl
data-shape round round, square
data-toggle unselected selected, unselected

`MatExpButton`

Standalone directive, selector [matExpButton], exported as matExpButton.

You can view the generated API for MatExpButton here.

Host

Attribute / binding Value
data-size From size() input
data-shape From shape() input
data-toggle From toggle() input
data-menu-open true when a [matMenuTriggerFor] on the same host has its menu open
aria-pressed 'true' / 'false' derived from toggle(); omitted entirely when toggle() is undefined
class mat-exp-button
(click) Delegates the click to the parent MatExpButtonGroup, if any

Inputs

Input Type Default Description
size MatExpButtonSize 's' Size of the button.
shape MatExpButtonShape 'round' Shape of the button.
toggle MatExpButtonToggle | undefined undefined Toggle state. Only auto-managed inside a MatExpButtonGroup — see Toggle Behavior above.
value unknown undefined Value read by a parent MatExpButtonGroup when computing its own form value.

size, shape, toggle, and value are all declared with model(), so they're two-way bindable (e.g. [(size)]="mySize").

Types:

  • MatExpButtonSize'xs' | 's' | 'm' | 'l' | 'xl'
  • MatExpButtonShape'round' | 'square'
  • MatExpButtonToggle'selected' | 'unselected'

Note

MatExpButton also exposes plain appearance / disabled accessors that read and write the underlying Angular Material matButton directive's own appearance / disabled inputs directly — bind those through matButton as usual (e.g. matButton="tonal", [disabled]).


Options and provider

`MatExpButtonOptions`

TypeScript
interface MatExpButtonOptions {
  readonly size?: MatExpButtonSize;
  readonly shape?: MatExpButtonShape;
  readonly toggle?: MatExpButtonToggle;
}

`MAT_EXP_BUTTON_DEFAULT_OPTIONS`

Default object used when no provider overrides values:

  • size: 's'
  • shape: 'round'

`MAT_EXP_BUTTON_OPTIONS`

InjectionToken for the resolved options object; used internally by the directive.

`provideMatExpButtonOptions`

Angular TS
import { provideMatExpButtonOptions } from '@ngm-dev/mat-exp';

export const appConfig: ApplicationConfig = {
  providers: [
    provideMatExpButtonOptions({
      size: 'm',
      shape: 'square',
    }),
  ],
};

You may pass a partial static object or a factory () => Partial<MatExpButtonOptions>.

Styling

Usage

SCSS
@use '@ngm-dev/mat-exp' as mat-exp;

html {
  @include mat-exp.mat-exp-button-styles($options);
}

Options

Option Type Default Description
skip-html-element-styles boolean false If true, the mixin won't apply styles to the underlying HTML elements.
sizes list of 'xs' | 's' | 'm' | 'l' | 'xl' null (all sizes emitted) Restricts the emitted CSS to only the given sizes. Use this to cut the CSS payload when your app only uses a subset of sizes — see Reducing the CSS Payload.

`skip-html-element-styles`

Setting this to true means the following won't work as expected:

  • Icon sizes
  • Shape morphing
SCSS
@use '@ngm-dev/mat-exp' as mat-exp;

html {
  @include mat-exp.mat-exp-button-styles(
    (
      skip-html-element-styles: true,
    )
  );
}

`sizes`

Drops the rest of the size × shape × state × toggle combination matrix at compile time.

SCSS
@use '@ngm-dev/mat-exp' as mat-exp;

html {
  @include mat-exp.mat-exp-button-styles(
    (
      sizes: ('s', 'm'),
    )
  );
}