Skip to main content brightness_auto motion_photos_off palette download more_vert
toc

Use with shadcn/ui

Install the md-gb elements as React components with the shadcn CLI, themed by your shadcn variables.

This site is a shadcn registry at /3.0/r/. npx shadcn@latest add copies a file into your project, adds the npm dependencies, and merges the theme:

  • A React wrapper per component family, such as md-button or md-menu (a menu with its group and item). The file is components/ui/md-<name>.tsx. It registers the md-gb-* elements and wraps each one with @lit/react: typed props from the element class, and the events as onChange, onClose and so on.
  • md-theme, installed with every item: it loads the M3 styles and maps your shadcn variables to the M3 color and shape roles.
  • md-theme-m3, optional: the reverse, the M3 baseline as shadcn variables, so shadcn components match the md-gb elements.

The registry lists every item in registry.json.

The plain URL form needs no setup:

npx shadcn@latest add https://material-web.dev/3.0/r/md-button.json

To use the short form, add the registry to components.json once. The keys of registries start with @, and {name} is the item:

{
  "registries": {
    "@material-web": "https://material-web.dev/3.0/r/{name}.json"
  }
}
npx shadcn@latest add @material-web/md-button @material-web/md-menu

The command installs @material/web and @lit/react with your package manager. Each component page has this command after its first example.

Use the component like any other. The JSON and TSX blocks on this page are plain blocks and not live demos: the TSX needs React, which this page does not load, and the JSON is configuration with no visible effect.

import {MdButton} from '@/components/ui/md-button';
import {MdMenu, MdMenuItem} from '@/components/ui/md-menu';

export function Actions() {
  return (
    <>
      <MdButton color="filled" popoverTarget="file-menu">
        File
      </MdButton>
      <MdMenu id="file-menu">
        <MdMenuItem onChange={(event) => console.log(event.type)}>Open</MdMenuItem>
      </MdMenu>
    </>
  );
}

md-theme adds @import "@material/web/labs/gb/styles/m3.css" to your global CSS, then declares the --md-sys-* variables in :root and in .dark from your shadcn variables. The M3 styles are in a cascade layer and the bridge is not, so the bridge wins regardless of the order of the imports. Change your shadcn theme, or toggle .dark, and the elements follow. It also sets color-scheme for :root and .dark.

Containers stay with their on- colors: each pair maps to a pair that shadcn already keeps readable together. The *-fixed roles, scrim and shadow keep the M3 baseline.

M3 role shadcn value Why
--md-sys-color-primary var(--primary) Same role.
--md-sys-color-on-primary var(--primary-foreground) Same pair.
--md-sys-color-primary-container var(--accent) shadcn has no primary container; accent is its tinted fill.
--md-sys-color-on-primary-container var(--accent-foreground) Pairs with accent.
--md-sys-color-secondary var(--muted-foreground) The mid-tone solid; text on it is --background.
--md-sys-color-on-secondary var(--background) Pairs with the solid above.
--md-sys-color-secondary-container var(--secondary) shadcn secondary is a tonal fill, like an M3 container.
--md-sys-color-on-secondary-container var(--secondary-foreground) Pairs with secondary.
--md-sys-color-tertiary var(--primary) shadcn has no tertiary; it follows primary.
--md-sys-color-on-tertiary var(--primary-foreground) Pairs with primary.
--md-sys-color-tertiary-container var(--accent) Follows the primary container.
--md-sys-color-on-tertiary-container var(--accent-foreground) Pairs with accent.
--md-sys-color-error var(--destructive) Same role.
--md-sys-color-on-error var(--destructive-foreground, oklch(1 0 0)) shadcn v4 has no destructive-foreground; white.
--md-sys-color-error-container color-mix(in oklab, var(--destructive) 14%, var(--background)) A tint of destructive on the background.
--md-sys-color-on-error-container var(--destructive) Destructive on its tint.
--md-sys-color-background var(--background) Same role.
--md-sys-color-on-background var(--foreground) Same pair.
--md-sys-color-surface var(--background) The page surface.
--md-sys-color-on-surface var(--foreground) Same pair.
--md-sys-color-surface-variant var(--muted) Muted fill.
--md-sys-color-on-surface-variant var(--muted-foreground) Muted text.
--md-sys-color-surface-tint var(--primary) Same as primary.
--md-sys-color-surface-dim var(--muted) Muted fill.
--md-sys-color-surface-bright var(--background) The page surface.
--md-sys-color-surface-container-lowest var(--background) Lowest tone: the page.
--md-sys-color-surface-container-low var(--card) Cards.
--md-sys-color-surface-container var(--popover) Menus and popovers.
--md-sys-color-surface-container-high var(--muted) Muted fill.
--md-sys-color-surface-container-highest var(--accent) Accent fill (the highest tone in both schemes).
--md-sys-color-outline var(--muted-foreground) M3 needs 3:1 against the surface; shadcn --border is too faint for field and button outlines.
--md-sys-color-outline-variant var(--border) Dividers and decorative borders.
--md-sys-color-inverse-surface var(--foreground) Inverse of the page.
--md-sys-color-inverse-on-surface var(--background) Pairs with the inverse surface.
--md-sys-color-inverse-primary var(--primary-foreground) Primary-side color that reads on the inverse surface.

Corners follow --radius (the px values are the M3 ones at shadcn's default 0.625rem). --md-sys-shape-corner-value-* get the same values.

M3 corner shadcn value
--md-sys-shape-corner-xs calc(var(--radius) * 0.4)
--md-sys-shape-corner-sm calc(var(--radius) * 0.8)
--md-sys-shape-corner-md calc(var(--radius) * 1.2)
--md-sys-shape-corner-lg calc(var(--radius) * 1.6)
--md-sys-shape-corner-lg-increased calc(var(--radius) * 2)
--md-sys-shape-corner-xl calc(var(--radius) * 2.8)
--md-sys-shape-corner-xl-increased calc(var(--radius) * 3.2)
--md-sys-shape-corner-xxl calc(var(--radius) * 4.8)

shadcn has no tertiary role, so it follows primary. outline is the muted foreground and not --border, because M3 needs 3:1 against the surface for the outline of a field or an outlined button; --border is the outline-variant (dividers).

Match shadcn to the M3 baseline

Link to “Match shadcn to the M3 baseline”

md-theme-m3 goes the other way. It writes the baseline colors of M3 (light and dark) into the shadcn variables:

npx shadcn@latest add https://material-web.dev/3.0/r/md-theme-m3.json
shadcn variable M3 baseline role
--background --md-sys-color-surface
--foreground --md-sys-color-on-surface
--card --md-sys-color-surface-container-low
--card-foreground --md-sys-color-on-surface
--popover --md-sys-color-surface-container
--popover-foreground --md-sys-color-on-surface
--primary --md-sys-color-primary
--primary-foreground --md-sys-color-on-primary
--secondary --md-sys-color-secondary-container
--secondary-foreground --md-sys-color-on-secondary-container
--muted --md-sys-color-surface-container-high
--muted-foreground --md-sys-color-on-surface-variant
--accent --md-sys-color-surface-container-highest
--accent-foreground --md-sys-color-on-surface
--destructive --md-sys-color-error
--border --md-sys-color-outline-variant
--input --md-sys-color-outline
--ring --md-sys-color-primary
--chart-1 --md-sys-color-primary
--chart-2 --md-sys-color-tertiary
--chart-3 --md-sys-color-secondary
--chart-4 --md-sys-color-primary-fixed-dim
--chart-5 --md-sys-color-tertiary-fixed-dim
--sidebar --md-sys-color-surface-container-low
--sidebar-foreground --md-sys-color-on-surface
--sidebar-primary --md-sys-color-primary
--sidebar-primary-foreground --md-sys-color-on-primary
--sidebar-accent --md-sys-color-secondary-container
--sidebar-accent-foreground --md-sys-color-on-secondary-container
--sidebar-border --md-sys-color-outline-variant
--sidebar-ring --md-sys-color-primary

--radius is 0.75rem, the M3 medium corner.

It is independent of md-theme. With both installed, the bridge points the M3 roles at shadcn variables that now hold the M3 colors, so the two agree.

The elements import their styles as CSS module scripts (import styles from './menu.css' with {type: 'css'}). Browsers support that, but Vite, webpack and Node do not without help, and the build fails with "default" is not exported by "…/focus-ring.css". Every stylesheet ships next to a Lit CSSResult module (menu.cssresult.js), so a one-time plugin that points the imports there is enough. For Vite (a plain block; it is configuration with no visible effect):

// vite.config.ts
import {defineConfig, type Plugin} from 'vite';

function mdGbCssModules(): Plugin {
  return {
    name: 'md-gb-css-modules',
    enforce: 'pre',
    transform(code, id) {
      if (!/[\\/]@material[\\/]web[\\/]/.test(id) || !id.endsWith('.js')) return;
      return code.replace(
        /import\s+(\w+)\s+from\s+'(\.[^']*)\.css'\s+with\s*\{\s*type:\s*'css'\s*\};?/g,
        "import {styles as $1} from '$2.cssresult.js';",
      );
    },
  };
}

export default defineConfig({
  plugins: [mdGbCssModules() /* , react(), tailwindcss() */],
  optimizeDeps: {exclude: ['@material/web']},
});
  • Tailwind classes style only the host. The elements use shadow DOM, so className="bg-red-500" colors the <md-gb-button> box, not its inside. Customize with the --md-* custom properties and ::part() selectors listed in each component's API reference. They work in a plain CSS file or in Tailwind's arbitrary properties, such as [--container-color:red].
  • Client only. The wrappers start with 'use client', and the elements cannot be imported in Node (they use CSS module scripts, customElements and document). In Next.js the server renders client components too, so import the file with next/dynamic(() => import('...'), {ssr: false}) or render it after mount, unless you set up Lit SSR.
  • Labs. The elements come from @material/web/labs/gb/. The registry keeps @material/web as the dependency, and the path needs a version that ships labs/gb.
  • Typography, motion and icons are not bridged. Load the Material Symbols font as in the quick start.
  • Local testing. When this site runs from the dev server the registry files point to http://localhost:8000. Set MWC_REGISTRY_URL to build them for another origin.

The registry files are generated by scripts/m3/gb-shadcn.mjs from the element sources, so the props and events match the API reference. For agents, the same information is in llms.txt and custom-elements.json.

Install Material Web 3.0

Your browser did not offer to install the docs as an app. Install them from the browser instead:

Close
Send feedback
Bug Documentation Idea

Opens a new GitHub issue for material-components/material-web in a new tab, with this filled in.

Cancel Open issue