Migrate from @material/web
lit-material reads the same Material 3 system tokens as @material/web, so a theme carries over as it is. A codemod rewrites elements, attributes, slots and imports, and lists by file and line what it left for you.
What stays and what changes
- The same tokens. Components read the
--md-sys-*custom properties (colour roles, typescale, shape, elevation, state), the names a Material Theme Builder export sets. Keep your theme and link@lit-material/tokens/css/index.cssonce for the baseline. The.md-typescale-*classes are in@lit-material/tokens/css/typography.cssunder the same names. - One package per component.
import "@material/web/button/filled-button.js"becomesimport "@lit-material/button", which registers every button variant. - Variants are attributes. One element per component:
<md-outlined-text-field>is<lit-material-text-field variant="outlined">,<md-filled-tonal-button>is<lit-material-button variant="tonal">. Labels that were attributes (labelon chips and FABs) are text content. - Component tokens differ. Per-component tokens such as
--md-filled-button-container-colorhave no counterparts. Each lit-material element lists its own--lit-material-*properties and CSS parts on its page; colours and shapes come from the system tokens. - Forms. Text fields, selects, checkboxes, radios, switches, sliders and buttons are form-associated through
ElementInternals:FormData, constraint validation and reset work as with native controls. - Server rendering. Every component is tested against
@lit-labs/ssr, and@lit-material/viterenders whole apps on the server. See Server rendering. - More components. Data table, date and time pickers, navigation drawer, rail and bar, snackbar, tooltip, stepper and the rest of the catalogue, which @material/web doesn't have.
Run the codemod
Commit first: the codemod rewrites files in place. A dry run prints every change and warning and writes nothing:
npx @lit-material/codemod md-web src --dry-run
npx @lit-material/codemod md-web src
It reads HTML (with parse5, a spec-compliant parser), Lit templates in .ts/.js, JSX and TSX, and CSS, and edits the text in place, so formatting, comments and expressions stay as they were. Binding syntax is kept: .prefixText=${p} becomes .prefix=${p}, Angular's [selected] becomes [checked]. It ends with the packages to install:
changed src/app.ts:12: <md-outlined-text-field> → <lit-material-text-field variant="outlined">
changed src/app.ts:12: <md-outlined-text-field> prefix-text → prefix
warning src/app.ts:31: <md-slider> range: lit-material-slider is single-value; a two-thumb range is not supported
Install what the rewritten files use: npm install @lit-material/tokens @lit-material/button @lit-material/text-field …
Anything without a confident mapping is a warning with its file and line, never a silent change: unknown md-* elements, attributes with no counterpart, bound values that would need mapping, class imports (import { MdDialog }), md-* names in selectors and strings, and component tokens. Running it twice changes nothing the second time. The same rewrite is a function, for your own tooling: import { transform } from "@lit-material/codemod".
Element by element
This table is the codemod's own mapping, so it is what the codemod does. Attributes not listed carry over unchanged.
| @material/web | lit-material | Package | Rewritten |
|---|---|---|---|
md-filled-button | lit-material-button variant="filled" | @lit-material/button | has-icon removedReported, left as is: trailing-icon, soft-disabled, download>> |
md-outlined-button | lit-material-button variant="outlined" | @lit-material/button | has-icon removedReported, left as is: trailing-icon, soft-disabled, download>> |
md-text-button | lit-material-button variant="text" | @lit-material/button | has-icon removedReported, left as is: trailing-icon, soft-disabled, download>> |
md-elevated-button | lit-material-button variant="elevated" | @lit-material/button | has-icon removedReported, left as is: trailing-icon, soft-disabled, download>> |
md-filled-tonal-button | lit-material-button variant="tonal" | @lit-material/button | has-icon removedReported, left as is: trailing-icon, soft-disabled, download>> |
md-icon-button | lit-material-icon-button | @lit-material/icon-button | slot="selected" → slot="selected-icon", unslotted md-icon → slot="icon"Reported, left as is: soft-disabled, flip-icon-in-rtl, aria-label-selected>> |
md-filled-icon-button | lit-material-icon-button variant="filled" | @lit-material/icon-button | slot="selected" → slot="selected-icon", unslotted md-icon → slot="icon"Reported, left as is: soft-disabled, flip-icon-in-rtl, aria-label-selected>> |
md-filled-tonal-icon-button | lit-material-icon-button variant="tonal" | @lit-material/icon-button | slot="selected" → slot="selected-icon", unslotted md-icon → slot="icon"Reported, left as is: soft-disabled, flip-icon-in-rtl, aria-label-selected>> |
md-outlined-icon-button | lit-material-icon-button variant="outlined" | @lit-material/icon-button | slot="selected" → slot="selected-icon", unslotted md-icon → slot="icon"Reported, left as is: soft-disabled, flip-icon-in-rtl, aria-label-selected>> |
md-fab | lit-material-fab | @lit-material/fab | variant → color, size: medium → regular, label → text + extended, adds color="surface"Reported, left as is: lowered>> |
md-branded-fab | lit-material-fab | @lit-material/fab | size: medium → regular, label → text + extended, adds color="surface"Reported, left as is: lowered>> |
md-checkbox | lit-material-checkbox | @lit-material/checkbox | none Reported, left as is: touch-target, input event>> |
md-radio | lit-material-radio | @lit-material/radio | none Reported, left as is: touch-target, input event>> |
md-switch | lit-material-switch | @lit-material/switch | selected → checkedReported, left as is: icons, show-only-selected-icon, input event>> |
md-slider | lit-material-slider | @lit-material/slider | none Reported, left as is: range, value-start, value-end, name-start, name-end, aria-label-start, aria-label-end, value-label, value-label-start, value-label-end, labeled, ticks>> |
md-filled-text-field | lit-material-text-field variant="filled" | @lit-material/text-field | type="textarea" → lit-material-textarea, prefix-text → prefix, suffix-text → suffix, input-mode → inputmode, has-leading-icon removed, has-trailing-icon removedReported, left as is: no-spinner, text-direction, cols, multiple, select event>> |
md-outlined-text-field | lit-material-text-field variant="outlined" | @lit-material/text-field | type="textarea" → lit-material-textarea, prefix-text → prefix, suffix-text → suffix, input-mode → inputmode, has-leading-icon removed, has-trailing-icon removedReported, left as is: no-spinner, text-direction, cols, multiple, select event>> |
md-filled-select | lit-material-select variant="filled" | @lit-material/select | quick removedReported, left as is: selected-index, display-text, has-leading-icon, menu-positioning, clamp-menu-width, menu-align, typeahead-delay, input event, opening event, opened event, closing event, closed event, slot="leading-icon">> |
md-outlined-select | lit-material-select variant="outlined" | @lit-material/select | quick removedReported, left as is: selected-index, display-text, has-leading-icon, menu-positioning, clamp-menu-width, menu-align, typeahead-delay, input event, opening event, opened event, closing event, closed event, slot="leading-icon">> |
md-select-option | lit-material-select-option | @lit-material/select | slot="headline" → default slot, slot="start" → slot="leading"Reported, left as is: display-text, slot="end", slot="supporting-text", slot="trailing-supporting-text", slot="overline">> |
md-chip-set | none | lit-material has no chip set: put the chips in a plain container (a flex row with a gap); arrow-key movement between chips isn't provided | |
md-assist-chip | lit-material-chip variant="assist" | @lit-material/chip | label → text, slot="icon" → slot="leading-icon"Reported, left as is: always-focusable>> |
md-filter-chip | lit-material-chip variant="filter" | @lit-material/chip | label → text, slot="icon" → slot="leading-icon"Reported, left as is: always-focusable, has-selected-icon>> |
md-input-chip | lit-material-chip variant="input" | @lit-material/chip | label → text, adds removable, slot="icon" → slot="leading-icon"Reported, left as is: always-focusable, avatar, remove-only>> |
md-suggestion-chip | lit-material-chip variant="suggestion" | @lit-material/chip | label → text, slot="icon" → slot="leading-icon"Reported, left as is: always-focusable>> |
md-dialog | lit-material-dialog | @lit-material/dialog | quick removed, closed event → close, slot="content" → default slotReported, left as is: no-focus-trap, type, return-value, open event, opened event>> |
md-menu | lit-material-menu | @lit-material/menu | quick removed, closed event → closeReported, left as is: positioning, has-overflow, x-offset, y-offset, anchor-corner, menu-corner, typeahead-delay, stay-open-on-outside-click, stay-open-on-focusout, skip-restore-focus, default-focus, is-submenu, opening event, opened event, closing event, close-menu event>> |
md-menu-item | lit-material-list-item interactive | @lit-material/list | type removed, slot="headline" → default slot, slot="start" → slot="leading", slot="end" → slot="trailing", slot="trailing-supporting-text" → slot="trailing"Reported, left as is: keep-open>> |
md-sub-menu | none | lit-material-menu has no submenus: flatten the menu, or open a second lit-material-menu anchored to the item | |
md-list | lit-material-list | @lit-material/list | none> |
md-list-item | lit-material-list-item | @lit-material/list | type="text" → removed, type="link" → removed, type="button" → interactive, slot="headline" → default slot, slot="start" → slot="leading", slot="end" → slot="trailing", slot="trailing-supporting-text" → slot="trailing"> |
md-tabs | lit-material-tabs | @lit-material/tabs | active-tab-index → selected, auto-activate removed> |
md-primary-tab | lit-material-tab | @lit-material/tabs | has-icon removedReported, left as is: active, icon-only, inline-icon>> |
md-secondary-tab | lit-material-tab | @lit-material/tabs | has-icon removedReported, left as is: active, icon-only>> |
md-linear-progress | lit-material-linear-progress | @lit-material/progress | none Reported, left as is: buffer, four-color>> |
md-circular-progress | lit-material-circular-progress | @lit-material/progress | none Reported, left as is: four-color>> |
md-divider | lit-material-divider | @lit-material/divider | inset → inset-start inset-end> |
md-icon | lit-material-icon | @lit-material/icon | none> |
md-ripple | none | lit-material components draw their own ripple, focus ring and elevation (style them with ::part(ripple), ::part(focus-ring) or the component's tokens); remove this element | |
md-focus-ring | none | lit-material components draw their own ripple, focus ring and elevation (style them with ::part(ripple), ::part(focus-ring) or the component's tokens); remove this element | |
md-elevation | none | lit-material components draw their own ripple, focus ring and elevation (style them with ::part(ripple), ::part(focus-ring) or the component's tokens); remove this element |
Imports follow the elements: @material/web/<folder>/<file>.js becomes the element's package, once per file. @material/web/all.js is reported with the imports to use instead, and left for you to replace.
What needs a person
- Icons.
<md-icon>settings</md-icon>draws a Material Symbols ligature from an icon font.lit-material-iconloads no font, so the word shows as text: register the SVGs withregisterIcons()and usename, or slot an<svg>. The codemod reports each ligature. - Script code. The codemod changes markup and imports. Code that reads
switch.selected(nowchecked),tabs.activeTabIndex(nowselected), queriesmd-*selectors, or imports md-web classes is reported, not rewritten. - Labels. A
<label for>pointing atlit-material-checkbox,-radioor-switchdoesn't toggle it (checked in Chromium); wrapping the control in the<label>does. Prefer the element'slabelattribute, which also names it for assistive technology. - Component tokens. Each
--md-<component>-*token is reported with the lit-material element's own properties and parts to use instead. - Missing pieces. No chip set, submenus, two-thumb slider, linear progress buffer, secondary tab style, or standalone ripple, focus ring and elevation elements (components draw their own). The table lists every attribute and event left in place.