
Security News
Axios Supply Chain Attack Reaches OpenAI macOS Signing Pipeline, Forces Certificate Rotation
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.
material-web-dx
Advanced tools
A DX library on top of Material Web — functional component API, theme builder, accessibility built-in
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.
npm install material-web-dx @material/web lit-html
@material/web and lit-html are peer dependencies.
// 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.
import "@material/web/button/filled-button.js";
import "@material/web/button/outlined-button.js";
import "@material/web/textfield/outlined-text-field.js";
<md-filled-button>Save</md-filled-button>
<md-outlined-text-field placeholder="Email" type="email"></md-outlined-text-field>
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">
import { toggleDarkMode, setDarkMode } from "material-web-dx";
// Toggle between light and dark
toggleDarkMode();
// Set explicitly
setDarkMode(true); // dark
setDarkMode(false); // light
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 });
| 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 |
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", ... }
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;
/* ... */
}
}
import { onThemeChange } from "material-web-dx";
const unsubscribe = onThemeChange((state) => {
console.log(state.seedHex, state.variant, state.dark, state.tokens);
});
// Later: unsubscribe();
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
Material Web DX works with all Material Web components. The base token overrides in tokens.css fix sizing and spacing for the following:
| 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>
| Component | Element |
|---|---|
| Icon button | <md-icon-button> |
<md-icon-button aria-label="Settings">
<md-icon>settings</md-icon>
</md-icon-button>
| 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>
| 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>
| 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>
| 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>
| 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>
| 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>
| 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>
| 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>
| 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>
| 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>
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();
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();
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" }
| 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 |
| 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 |
interface ThemeOptions {
dark?: boolean; // default: false
variant?: string; // default: "Fidelity"
}
MIT
FAQs
A DX library on top of Material Web — functional component API, theme builder, accessibility built-in
The npm package material-web-dx receives a total of 1 weekly downloads. As such, material-web-dx popularity was classified as not popular.
We found that material-web-dx demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
OpenAI rotated macOS signing certificates after a malicious Axios package reached its CI pipeline in a broader software supply chain attack.

Security News
Open source is under attack because of how much value it creates. It has been the foundation of every major software innovation for the last three decades. This is not the time to walk away from it.

Security News
Socket CEO Feross Aboukhadijeh breaks down how North Korea hijacked Axios and what it means for the future of software supply chain security.