npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@banegasn/m3-button

v2.2.3

Published

Material Design 3 button web component with expressive styling

Downloads

284

Readme

@banegasn/m3-button

Preview

Material Design 3 Button web component — framework-agnostic, built with Lit.

npm version License: MIT

An accessible M3 Button web component following the Material Design 3 button specifications. Features expressive styling, 5 variants, 5 sizes, shape morphing, loading states, and full accessibility support. Works in Angular, React, Vue, Svelte, or plain HTML — no build step required.

Features

  • 5 Button Variants: Filled, Elevated, Tonal, Outlined, and Text
  • 📐 5 Size Options: Extra-small to Extra-large (Material 3 Expressive)
  • 🔷 2 Shape Styles: Round and Square with dynamic morphing on press
  • 📏 Flexible Padding: Default (24dp) and Small (16dp) options
  • Fully Accessible: WCAG 2.1 compliant with ARIA support and keyboard navigation
  • 🎨 Material Design 3: Follows official M3 specifications and design tokens
  • 🔄 Loading State: Built-in loading spinner with proper ARIA attributes
  • 📱 Touch-friendly: Minimum 48x48px touch target
  • 🎯 Icon Support: Optional leading icons with proper spacing
  • 🌐 Framework-agnostic: Works with React, Angular, Vue, or vanilla JavaScript
  • 🎭 Customizable: CSS custom properties for theming

Installation

npm install @banegasn/m3-button
# or
pnpm add @banegasn/m3-button
# or
yarn add @banegasn/m3-button

CDN Usage (no build step)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>M3 Button Demo</title>
  <script type="module" src="https://cdn.jsdelivr.net/npm/@banegasn/m3-button/+esm"></script>
  <style>
    body { font-family: Roboto, sans-serif; padding: 32px; background: #fef7ff; }
    .row { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; margin-bottom: 24px; }
  </style>
</head>
<body>
  <div class="row">
    <m3-button variant="filled">Filled</m3-button>
    <m3-button variant="elevated">Elevated</m3-button>
    <m3-button variant="tonal">Tonal</m3-button>
    <m3-button variant="outlined">Outlined</m3-button>
    <m3-button variant="text">Text</m3-button>
  </div>
  <div class="row">
    <m3-button variant="filled" size="extra-large" shape="round">Get Started</m3-button>
    <m3-button variant="outlined" size="small" disabled>Disabled</m3-button>
    <m3-button variant="tonal" id="loading-btn">Click to load</m3-button>
  </div>

  <script>
    const btn = document.getElementById('loading-btn');
    btn.addEventListener('button-click', async () => {
      btn.loading = true;
      await new Promise(r => setTimeout(r, 2000));
      btn.loading = false;
    });
  </script>
</body>
</html>

Button Variants

Material Design 3 provides five button types for different emphasis levels:

Filled Button (Default)

The highest emphasis button for primary actions.

<m3-button variant="filled">Filled Button</m3-button>
<!-- or simply -->
<m3-button>Filled Button</m3-button>

Elevated Button

Medium-high emphasis with subtle elevation shadow.

<m3-button variant="elevated">Elevated Button</m3-button>

Tonal Button

Medium emphasis with tinted background.

<m3-button variant="tonal">Tonal Button</m3-button>

Outlined Button

Medium emphasis with border outline.

<m3-button variant="outlined">Outlined Button</m3-button>

Text Button

Low emphasis for less important actions.

<m3-button variant="text">Text Button</m3-button>

Material 3 Expressive Features

Button Sizes

Choose from five size options to match your design needs:

<!-- Extra Small (32px) - Compact layouts -->
<m3-button size="extra-small">Extra Small</m3-button>

<!-- Small (40px) - Default size -->
<m3-button size="small">Small</m3-button>
<m3-button>Small (default)</m3-button>

<!-- Medium (48px) - Increased prominence -->
<m3-button size="medium">Medium</m3-button>

<!-- Large (56px) - High emphasis -->
<m3-button size="large">Large</m3-button>

<!-- Extra Large (64px) - Hero actions -->
<m3-button size="extra-large">Extra Large</m3-button>

Button Shapes

Round (default) or square corners with dynamic morphing animation:

<!-- Round shape with fully rounded corners (default) -->
<m3-button shape="round">Round Button</m3-button>
<m3-button>Round (default)</m3-button>

<!-- Square shape with minimal rounding -->
<m3-button shape="square">Square Button</m3-button>

Shape Morphing: Buttons dynamically morph their shape when pressed:

  • Round buttons become less round (60% of original radius)
  • Square buttons become more round (150% of original radius)

Button Padding

Choose between default and compact padding:

<!-- Default padding (24dp) - Traditional spacing -->
<m3-button padding="default">Default Padding</m3-button>
<m3-button>Default (default)</m3-button>

<!-- Small padding (16dp) - Recommended for new designs -->
<m3-button padding="small">Small Padding</m3-button>

Combining Expressive Features

Mix and match size, shape, and padding for maximum expressiveness:

<!-- Hero CTA -->
<m3-button variant="filled" size="extra-large" shape="round" padding="small">
  Get Started
</m3-button>

<!-- Modern card action -->
<m3-button variant="tonal" size="medium" shape="square" padding="small">
  Continue
</m3-button>

<!-- Compact toolbar button -->
<m3-button icon-only size="extra-small" shape="square" padding="small" aria-label="Edit">
  <svg slot="icon" viewBox="0 0 24 24" width="16" height="16">...</svg>
</m3-button>

Usage Examples

With Icon

Add an icon using the icon slot:

<m3-button>
  <svg slot="icon" viewBox="0 0 24 24" width="18" height="18">
    <path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
  </svg>
  Add Item
</m3-button>

Icon-Only Button

For buttons with only an icon (requires aria-label for accessibility):

<m3-button icon-only aria-label="Add item">
  <svg slot="icon" viewBox="0 0 24 24" width="18" height="18">
    <path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
  </svg>
</m3-button>

Loading State

Show loading spinner while processing:

<m3-button id="submit-btn">Submit</m3-button>

<script>
  const btn = document.getElementById('submit-btn');
  btn.addEventListener('button-click', async () => {
    btn.loading = true;
    try {
      await submitForm();
    } finally {
      btn.loading = false;
    }
  });
</script>

Disabled State

<m3-button disabled>Disabled Button</m3-button>

Full Width

<m3-button full-width>Full Width Button</m3-button>

Form Integration

<form id="my-form">
  <input type="text" name="username" required />
  <m3-button type="submit" form="my-form">Submit</m3-button>
  <m3-button type="reset" variant="text">Reset</m3-button>
</form>

API Reference

Properties

| Property | Type | Default | Description | |----------|------|---------|-------------| | variant | 'filled' \| 'elevated' \| 'tonal' \| 'outlined' \| 'text' | 'filled' | Button style variant | | size | 'extra-small' \| 'small' \| 'medium' \| 'large' \| 'extra-large' | 'small' | Button size (M3 Expressive) | | shape | 'round' \| 'square' | 'round' | Button corner shape with morphing on press | | padding | 'default' \| 'small' | 'default' | Horizontal padding (24dp or 16dp) | | disabled | boolean | false | Disables the button | | loading | boolean | false | Shows loading spinner and disables interaction | | full-width | boolean | false | Makes button full width | | icon-only | boolean | false | Button contains only an icon (hides label) | | type | 'button' \| 'submit' \| 'reset' | 'button' | Button type for form handling | | aria-label | string | undefined | Accessible label (required for icon-only) | | name | string | undefined | Name for form submission | | value | string | undefined | Value for form submission | | form | string | undefined | Associates button with a form by ID |

Events

| Event | Detail | Description | |-------|--------|-------------| | button-click | { variant: string, size: string, shape: string, padding: string, name?: string, value?: string } | Fired when button is clicked (not fired when disabled or loading) |

Slots

| Slot | Description | |------|-------------| | (default) | Button label text | | icon | Optional icon (18x18px recommended) |

Methods

| Method | Description | |--------|-------------| | focus() | Programmatically focus the button | | blur() | Remove focus from the button |

CSS Custom Properties

| Property | Default | Description | |----------|---------|-------------| | --md-button-container-height | Varies by size* | Height of the button | | --md-button-container-shape | Varies by shape/size* | Border radius | | --md-button-label-text-size | Varies by size* | Font size of label | | --md-button-label-text-weight | 500 | Font weight of label | | --md-button-icon-size | Varies by size* | Size of the icon | | --md-button-spacing | 24dp or 16dp* | Horizontal padding | | --md-sys-color-primary | #6750a4 | Primary color | | --md-sys-color-on-primary | #ffffff | Text color on primary | | --md-sys-color-secondary-container | #e8def8 | Secondary container color | | --md-sys-color-on-secondary-container | #1d192b | Text on secondary container | | --md-sys-color-surface-container-low | #f7f2fa | Surface color for elevated | | --md-sys-color-outline | #79747e | Border color for outlined |

Size-based defaults:

  • Extra Small: 32px height, 16px icon, 12px text
  • Small (default): 40px height, 18px icon, 14px text
  • Medium: 48px height, 20px icon, 16px text
  • Large: 56px height, 24px icon, 18px text
  • Extra Large: 64px height, 28px icon, 20px text

Shape-based border radius:

  • Round (default): Fully rounded (50% of height)
  • Square: Minimal rounding (4-14px based on size)

Accessibility

This component follows WCAG 2.1 Level AA guidelines and Material Design 3 accessibility standards:

Keyboard Support

  • Enter/Space: Activates the button
  • Tab: Moves focus to/from the button
  • Focus indicator visible via :focus-visible

Screen Reader Support

  • Proper button role and label
  • Loading state announced via aria-busy
  • Disabled state properly conveyed
  • Icon-only buttons require aria-label

Touch Targets

  • Minimum 48x48px touch target (includes invisible padding)
  • Visual feedback on hover and press states
  • No pointer events when disabled

Best Practices

<!-- ✅ Good: Icon-only with aria-label -->
<m3-button icon-only aria-label="Close dialog">
  <svg slot="icon">...</svg>
</m3-button>

<!-- ❌ Bad: Icon-only without aria-label -->
<m3-button icon-only>
  <svg slot="icon">...</svg>
</m3-button>

<!-- ✅ Good: Descriptive text -->
<m3-button>Delete item</m3-button>

<!-- ❌ Bad: Ambiguous text -->
<m3-button>Click here</m3-button>

Theming

Customize the button appearance with CSS custom properties:

/* Global theme */
:root {
  --md-sys-color-primary: #0066cc;
  --md-sys-color-on-primary: #ffffff;
  --md-sys-color-secondary-container: #d6e3ff;
  --md-sys-color-on-secondary-container: #001a41;
}

/* Per-component customization */
m3-button {
  --md-button-container-height: 48px;
  --md-button-container-shape: 24px;
  --md-button-label-text-size: 16px;
}

/* Specific instance */
m3-button.large {
  --md-button-container-height: 56px;
  --md-button-spacing: 32px;
}

Examples by Framework

Vanilla JavaScript

<m3-button id="my-btn">Click me</m3-button>

<script type="module">
  import '@banegasn/m3-button';
  
  const btn = document.getElementById('my-btn');
  btn.addEventListener('button-click', (e) => {
    console.log('Button clicked!', e.detail);
  });
</script>

React

import '@banegasn/m3-button';

function App() {
  const handleClick = (e) => {
    console.log('Clicked!', e.detail);
  };

  return (
    <m3-button 
      variant="filled"
      onbutton-click={handleClick}
    >
      Click me
    </m3-button>
  );
}

Angular

// app.component.ts
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import '@banegasn/m3-button';

@Component({
  selector: 'app-root',
  template: `
    <m3-button 
      variant="filled"
      (button-click)="handleClick($event)"
    >
      Click me
    </m3-button>
  `,
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppComponent {
  handleClick(event: CustomEvent) {
    console.log('Clicked!', event.detail);
  }
}

Vue

<template>
  <m3-button 
    variant="filled"
    @button-click="handleClick"
  >
    Click me
  </m3-button>
</template>

<script setup>
import '@banegasn/m3-button';

const handleClick = (event) => {
  console.log('Clicked!', event.detail);
};
</script>

Svelte

<script>
  import '@banegasn/m3-button';
  
  function handleClick(event) {
    console.log('Clicked!', event.detail);
  }
</script>

<m3-button 
  variant="filled"
  on:button-click={handleClick}
>
  Click me
</m3-button>

Complete Example

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>M3 Button Demo</title>
  <style>
    body {
      font-family: Roboto, system-ui, sans-serif;
      padding: 24px;
      background: #fef7ff;
    }
    
    .demo-section {
      margin-bottom: 32px;
    }
    
    .button-group {
      display: flex;
      gap: 16px;
      flex-wrap: wrap;
      align-items: center;
    }
    
    h2 {
      margin-bottom: 16px;
      color: #1d1b20;
    }
  </style>
  <script type="module">
    import '@banegasn/m3-button';
  </script>
</head>
<body>
  <h1>Material Design 3 Button Demo</h1>

  <div class="demo-section">
    <h2>Button Variants</h2>
    <div class="button-group">
      <m3-button variant="filled">Filled</m3-button>
      <m3-button variant="elevated">Elevated</m3-button>
      <m3-button variant="tonal">Tonal</m3-button>
      <m3-button variant="outlined">Outlined</m3-button>
      <m3-button variant="text">Text</m3-button>
    </div>
  </div>

  <div class="demo-section">
    <h2>With Icons</h2>
    <div class="button-group">
      <m3-button variant="filled">
        <svg slot="icon" viewBox="0 0 24 24" width="18" height="18">
          <path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
        </svg>
        Add Item
      </m3-button>
      
      <m3-button variant="elevated">
        <svg slot="icon" viewBox="0 0 24 24" width="18" height="18">
          <path fill="currentColor" d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/>
        </svg>
        Confirm
      </m3-button>
    </div>
  </div>

  <div class="demo-section">
    <h2>Icon-Only Buttons</h2>
    <div class="button-group">
      <m3-button icon-only aria-label="Add" variant="filled">
        <svg slot="icon" viewBox="0 0 24 24" width="18" height="18">
          <path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
        </svg>
      </m3-button>
      
      <m3-button icon-only aria-label="Edit" variant="tonal">
        <svg slot="icon" viewBox="0 0 24 24" width="18" height="18">
          <path fill="currentColor" d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04c.39-.39.39-1.02 0-1.41l-2.34-2.34c-.39-.39-1.02-.39-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"/>
        </svg>
      </m3-button>
    </div>
  </div>

  <div class="demo-section">
    <h2>States</h2>
    <div class="button-group">
      <m3-button disabled>Disabled</m3-button>
      <m3-button id="loading-btn">Loading Demo</m3-button>
    </div>
  </div>

  <div class="demo-section">
    <h2>Full Width</h2>
    <m3-button full-width variant="filled">Full Width Button</m3-button>
  </div>

  <script>
    // Add click handlers
    document.querySelectorAll('m3-button').forEach(btn => {
      btn.addEventListener('button-click', (e) => {
        console.log('Button clicked:', e.detail);
      });
    });

    // Loading demo
    const loadingBtn = document.getElementById('loading-btn');
    loadingBtn.addEventListener('button-click', async () => {
      loadingBtn.loading = true;
      await new Promise(resolve => setTimeout(resolve, 2000));
      loadingBtn.loading = false;
    });
  </script>
</body>
</html>

Browser Support

Works in all modern browsers that support:

  • Web Components (Custom Elements v1)
  • Shadow DOM v1
  • ES Modules

Design Resources

Related Components

Resources

License

MIT