Get Started
@sandlada/mdc ships Material Design 3 and Material Expressive as framework-agnostic web components. Install the library, load the design tokens, register the elements, then use plain <mdc-*> tags anywhere.
Install
Add the component library and the CSS design tokens it is styled with. Both are plain ESM packages with no build step required.
npm i @sandlada/mdc @sandlada/material-design-cssLoad the design tokens
Every component is styled through --md-sys-* custom properties supplied by@sandlada/material-design-css. Import the preset once, then choose a prebuilt color scheme.
@import '@sandlada/material-design-css/preset.css';
/* Pick a prebuilt color scheme (ships light and dark). */
@import '@sandlada/material-design-css/prebuilt-colors/tonal-spot/h0-2025.css';
/* Optional: Tailwind utilities such as bg-surface / text-on-surface. */
@import '@sandlada/material-design-css/color/tw.css';Toggle the dark attribute on <html> to switch themes, and thelow-contrast / high-contrast attributes to adjust contrast.
document.documentElement.toggleAttribute('dark', isDark);Register the components
Importing a component module registers its custom element as a side effect. Each component is its own entry point under @sandlada/mdc/components/<name>/index, so import exactly the elements you use - nothing else is pulled into your bundle.
// Importing a component module registers its <mdc-*> element.
import '@sandlada/mdc/components/button/index';// Import every element you need - each component is its own entry point.
import '@sandlada/mdc/components/button/index';
import '@sandlada/mdc/components/icon/index';
import '@sandlada/mdc/components/text-field/index';Use the components
With the elements registered, use them like any other HTML tag - in plain HTML or inside any framework template.
<mdc-button variant="filled"><span>Save</span></mdc-button>
<mdc-button variant="outlined"><span>Cancel</span></mdc-button>Live example
Code
<mdc-button variant="filled"><span>Filled</span></mdc-button>
<mdc-button variant="filled-tonal"><span>Filled tonal</span></mdc-button>
<mdc-button variant="outlined"><span>Outlined</span></mdc-button>
<mdc-button variant="text"><span>Text</span></mdc-button>Configure global behaviors
Ripple, focus ring, and elevation behaviors can be tuned once for the whole document. Attach the provider before your app renders; it is optional, and components fall back to sensible defaults without it.
import { GlobalMDCContextProvider } from '@sandlada/mdc/utils/context-provider';
// Attach once, before your app renders.
GlobalMDCContextProvider.attach();
GlobalMDCContextProvider.setConfig({
ripple: { disableHoverStateLayer: false },
focusRing: {},
elevation: { disabled: false },
});Framework notes
Web components work natively everywhere. A little configuration tells a compiler to leave mdc-* tags alone rather than treating them as unknown components.
Vue
// vite.config.ts
import vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag.startsWith('mdc-'),
},
},
}),
],
});Angular
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}React
React 19 renders custom elements natively. On older versions, set properties through a ref instead of passing them as attributes.