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

material-web-dx

v0.1.1

Published

A DX library on top of Material Web — functional component API, theme builder, accessibility built-in

Downloads

183

Readme

Material Web DX

A developer experience library on top of Material Web. It provides a default theme, dynamic color generation, dark mode, accessibility helpers, and per-component typed theme directives — so you can use Material Web components with minimal setup.

Install

npm install material-web-dx @material/web lit-html

@material/web and lit-html are peer dependencies.

Quick start

1. Import the base tokens and default theme

// Sizing, shape, typography fixes for Material Web
import "material-web-dx/theme/tokens.css";

// Default color palette (seed #5389df, Fidelity variant)
// Includes both light and dark mode
import "material-web-dx/theme/default.css";

That's it. No JavaScript required for a working theme. All Material Web components will pick up the colors and sizing automatically.

2. Register the Material Web components you need

import "@material/web/button/filled-button.js";
import "@material/web/button/outlined-button.js";
import "@material/web/textfield/outlined-text-field.js";

3. Use them in your HTML

<md-filled-button>Save</md-filled-button>
<md-outlined-text-field placeholder="Email" type="email"></md-outlined-text-field>

Dark mode

The default theme CSS supports dark mode out of the box. It activates via:

| Method | How | |---|---| | OS preference | prefers-color-scheme: dark media query (automatic) | | Explicit class | Add class="dark" to <html> | | Data attribute | Add data-theme="dark" to <html> |

To force light mode even when the OS is set to dark:

<html class="light">

or

<html data-theme="light">

Programmatic dark mode

import { toggleDarkMode, setDarkMode } from "material-web-dx";

// Toggle between light and dark
toggleDarkMode();

// Set explicitly
setDarkMode(true);  // dark
setDarkMode(false); // light

Custom themes

If the default palette doesn't fit your brand, generate a theme from any seed color:

import "material-web-dx/theme/tokens.css";
import { applyTheme } from "material-web-dx";

// Generate and apply a complete M3 color scheme from a single hex color
applyTheme("#6750A4", { variant: "Tonal Spot", dark: false });

Scheme variants

| Variant | Description | |---|---| | Tonal Spot | Balanced, muted tones (Material default) | | Fidelity | Colors stay close to the seed | | Vibrant | Saturated, energetic palette | | Expressive | Bold, dramatic color shifts | | Neutral | Minimal color, mostly greys | | Monochrome | Single-hue greyscale | | Content | Derived from image content colors |

Generate without applying

import { generateTheme } from "material-web-dx";

const tokens = generateTheme("#6750A4", { dark: true, variant: "Vibrant" });
// tokens is a Record<string, string> of color token names to hex values
// e.g. { "primary": "#d0bcff", "on-primary": "#381e72", ... }

Export and use a custom theme file

You can export a generated theme as CSS or SCSS and save it as a static file in your project. This is ideal for production apps where you want zero runtime theme generation — just a plain CSS file checked into your repo.

Step 1: Generate the CSS

import { exportThemeCSS, exportThemeSCSS } from "material-web-dx";

const css = exportThemeCSS("#6750A4", { variant: "Fidelity" });
const scss = exportThemeSCSS("#6750A4", { variant: "Fidelity", dark: true });

// Copy the output and save it as a file
console.log(css);

You can also use the interactive Theme Builder on the docs site — pick your seed color and variant, then click "Export CSS" or "Export SCSS" and copy the output.

Step 2: Save as a file

Save the exported output as my-theme.css (or _my-theme.scss) in your project:

/* my-theme.css — exported from Material Web DX */
:root {
  --md-sys-color-primary: #6750a4;
  --md-sys-color-on-primary: #ffffff;
  --md-sys-color-primary-container: #eaddff;
  /* ... all 35 color tokens ... */
}

Step 3: Import it instead of the default theme

// Base sizing/shape/typography fixes (always needed)
import "material-web-dx/theme/tokens.css";

// Your custom color theme instead of the default
import "./my-theme.css";

That's it — no JavaScript, no runtime color generation. The tokens.css file handles sizing and shape, your custom file handles colors. All Material Web components pick up both automatically.

If you need both light and dark variants, export each separately and combine them:

/* my-theme.css */

/* Light */
:root {
  --md-sys-color-primary: #6750a4;
  /* ... */
}

/* Dark — activates via class, attribute, or OS preference */
:root.dark,
:root[data-theme="dark"] {
  --md-sys-color-primary: #d0bcff;
  /* ... */
}

@media (prefers-color-scheme: dark) {
  :root:not(.light):not([data-theme="light"]) {
    --md-sys-color-primary: #d0bcff;
    /* ... */
  }
}

Listen for theme changes

import { onThemeChange } from "material-web-dx";

const unsubscribe = onThemeChange((state) => {
  console.log(state.seedHex, state.variant, state.dark, state.tokens);
});

// Later: unsubscribe();

Semantic color variables

Access M3 system colors as CSS variable references:

import { colors } from "material-web-dx";

// colors.primary       → "var(--md-sys-color-primary)"
// colors.onPrimary     → "var(--md-sys-color-on-primary)"
// colors.surface       → "var(--md-sys-color-surface)"
// colors.error         → "var(--md-sys-color-error)"
// ... all 35 M3 system colors

Supported components

Material Web DX works with all Material Web components. The base token overrides in tokens.css fix sizing and spacing for the following:

Buttons

| Component | Element | |---|---| | Filled button | <md-filled-button> | | Outlined button | <md-outlined-button> | | Text button | <md-text-button> | | Filled tonal button | <md-filled-tonal-button> |

<md-filled-button>Save</md-filled-button>
<md-outlined-button>Cancel</md-outlined-button>
<md-text-button>Learn more</md-text-button>
<md-filled-tonal-button>Draft</md-filled-tonal-button>

<!-- With icons -->
<md-filled-button>
  <md-icon slot="icon">add</md-icon>
  Create
</md-filled-button>

Icon button

| Component | Element | |---|---| | Icon button | <md-icon-button> |

<md-icon-button aria-label="Settings">
  <md-icon>settings</md-icon>
</md-icon-button>

Text fields

| Component | Element | |---|---| | Filled text field | <md-filled-text-field> | | Outlined text field | <md-outlined-text-field> |

<md-filled-text-field placeholder="First name"></md-filled-text-field>
<md-outlined-text-field placeholder="Email" type="email">
  <md-icon slot="leading-icon">mail</md-icon>
</md-outlined-text-field>

<!-- With validation -->
<md-filled-text-field
  placeholder="Required field"
  error
  error-text="This field is required"
></md-filled-text-field>

Select

| Component | Element | |---|---| | Filled select | <md-filled-select> | | Outlined select | <md-outlined-select> |

<md-outlined-select>
  <md-select-option selected value="">
    <div slot="headline">Choose a country</div>
  </md-select-option>
  <md-select-option value="us">
    <div slot="headline">United States</div>
  </md-select-option>
  <md-select-option value="uk">
    <div slot="headline">United Kingdom</div>
  </md-select-option>
</md-outlined-select>

Selection controls

| Component | Element | |---|---| | Checkbox | <md-checkbox> | | Switch | <md-switch> | | Radio | <md-radio> | | Slider | <md-slider> |

<!-- Checkbox -->
<label><md-checkbox checked touch-target="wrapper"></md-checkbox> Notifications</label>

<!-- Switch -->
<label><md-switch selected></md-switch> Wi-Fi</label>

<!-- Radio group -->
<label><md-radio name="size" value="s" checked></md-radio> Small</label>
<label><md-radio name="size" value="m"></md-radio> Medium</label>
<label><md-radio name="size" value="l"></md-radio> Large</label>

<!-- Slider -->
<md-slider value="40"></md-slider>
<md-slider range value-start="20" value-end="70"></md-slider>

Chips

| Component | Element | |---|---| | Chip set | <md-chip-set> | | Filter chip | <md-filter-chip> | | Assist chip | <md-assist-chip> |

<md-chip-set>
  <md-filter-chip label="Running" selected></md-filter-chip>
  <md-filter-chip label="Cycling"></md-filter-chip>
</md-chip-set>

<md-chip-set>
  <md-assist-chip label="Share">
    <md-icon slot="icon">share</md-icon>
  </md-assist-chip>
</md-chip-set>

Tabs

| Component | Element | |---|---| | Tabs container | <md-tabs> | | Primary tab | <md-primary-tab> | | Secondary tab | <md-secondary-tab> |

<md-tabs>
  <md-primary-tab>
    <md-icon slot="icon">flight</md-icon>
    Flights
  </md-primary-tab>
  <md-primary-tab>
    <md-icon slot="icon">hotel</md-icon>
    Hotels
  </md-primary-tab>
</md-tabs>

<md-tabs>
  <md-secondary-tab>Overview</md-secondary-tab>
  <md-secondary-tab>Specs</md-secondary-tab>
  <md-secondary-tab>Reviews</md-secondary-tab>
</md-tabs>

List

| Component | Element | |---|---| | List | <md-list> | | List item | <md-list-item> | | Divider | <md-divider> |

<md-list>
  <md-list-item>
    <md-icon slot="start">person</md-icon>
    <div slot="headline">Alice Johnson</div>
    <div slot="supporting-text">Product Designer</div>
    <md-icon slot="end">chevron_right</md-icon>
  </md-list-item>
  <md-divider></md-divider>
  <md-list-item>
    <md-icon slot="start">person</md-icon>
    <div slot="headline">Bob Smith</div>
    <div slot="supporting-text">Engineer</div>
    <md-icon slot="end">chevron_right</md-icon>
  </md-list-item>
</md-list>

Dialog

| Component | Element | |---|---| | Dialog | <md-dialog> |

<md-dialog id="my-dialog">
  <div slot="headline">Save changes?</div>
  <form slot="content" method="dialog">
    Your unsaved changes will be lost.
  </form>
  <div slot="actions">
    <md-text-button @click=${() => dialog.close()}>Discard</md-text-button>
    <md-filled-button @click=${() => dialog.close()}>Save</md-filled-button>
  </div>
</md-dialog>

FAB (Floating Action Button)

| Component | Element | |---|---| | FAB | <md-fab> |

<!-- Icon only -->
<md-fab><md-icon slot="icon">edit</md-icon></md-fab>

<!-- Extended with label -->
<md-fab label="Compose"><md-icon slot="icon">create</md-icon></md-fab>

<!-- Sizes: small, medium (default), large -->
<md-fab size="small"><md-icon slot="icon">add</md-icon></md-fab>
<md-fab size="large"><md-icon slot="icon">add</md-icon></md-fab>

<!-- Color variants: primary (default), secondary, tertiary -->
<md-fab variant="secondary" label="Navigate">
  <md-icon slot="icon">navigation</md-icon>
</md-fab>

Progress indicators

| Component | Element | |---|---| | Linear progress | <md-linear-progress> | | Circular progress | <md-circular-progress> |

<!-- Determinate -->
<md-linear-progress value="0.6"></md-linear-progress>
<md-circular-progress value="0.7"></md-circular-progress>

<!-- Indeterminate -->
<md-linear-progress indeterminate></md-linear-progress>
<md-circular-progress indeterminate></md-circular-progress>

Menu

| Component | Element | |---|---| | Menu | <md-menu> | | Menu item | <md-menu-item> |

<div style="position: relative">
  <md-filled-button id="anchor">Open menu</md-filled-button>
  <md-menu anchor="anchor">
    <md-menu-item>
      <div slot="headline">Cut</div>
    </md-menu-item>
    <md-menu-item>
      <div slot="headline">Copy</div>
    </md-menu-item>
    <md-menu-item>
      <div slot="headline">Paste</div>
    </md-menu-item>
  </md-menu>
</div>

Accessibility helpers

Focus trap

Trap keyboard focus inside a container (useful for modals and dialogs):

import { focusTrap } from "material-web-dx";

const release = focusTrap(dialogElement);

// When the dialog closes:
release();

Keyboard navigation

Add arrow-key navigation to lists, menus, or tabs:

import { keyboardNav } from "material-web-dx";

const cleanup = keyboardNav(listElement, {
  direction: "vertical",  // "horizontal" | "vertical" | "both"
  wrap: true,              // wrap around at edges
  selector: "md-list-item" // custom selector for focusable items
});

// Later: cleanup();

ARIA helpers

import { uniqueId, spreadAttrs } from "material-web-dx";

const id = uniqueId("dialog"); // "dialog-1", "dialog-2", ...

const attrs = spreadAttrs({
  "aria-label": "Close",
  "aria-hidden": undefined,  // omitted
  "aria-expanded": false,    // omitted
});
// { "aria-label": "Close" }

What's included

| Import path | What it does | |---|---| | material-web-dx/theme/tokens.css | Base sizing, shape, typography fixes | | material-web-dx/theme/default.css | Default color theme (light + dark) | | material-web-dx | Theme API, directives, components, a11y | | material-web-dx/directives | Per-component typed theme directives | | material-web-dx/a11y | Focus trap, keyboard nav, ARIA helpers |

Theme API reference

| Function | Description | |---|---| | applyTheme(seed, options?) | Generate and apply a color scheme to the document | | generateTheme(seed, options?) | Generate a color scheme without applying | | applyColorTokens(tokenMap) | Apply a custom set of color tokens | | toggleDarkMode() | Toggle between light and dark mode | | setDarkMode(isDark) | Set dark mode on or off | | onThemeChange(listener) | Subscribe to theme state changes; returns unsubscribe fn | | exportThemeCSS(seed, options?) | Export theme as a CSS string | | exportThemeSCSS(seed, options?) | Export theme as an SCSS string |

ThemeOptions

interface ThemeOptions {
  dark?: boolean;    // default: false
  variant?: string;  // default: "Fidelity"
}

License

MIT