AWC UI vs @material/web
Both libraries implement Material Design 3 as framework-agnostic web
components, and both register md-* custom element tags. That overlap makes
the comparison unusually direct — and makes the two mutually exclusive on a
single page (see the tag-collision FAQ below).
Side by side
Section titled “Side by side”| Criterion | @material/web | AWC UI |
|---|---|---|
| Component count | ~15 component families (buttons, checkbox, chips, dialog, fields, icon buttons, lists, menus, progress, radio, select, slider, switch, tabs, …) | 56 components across forms, navigation, containment, data display, pickers, and charts |
| SSR / declarative shadow DOM | Client-side rendering; built on Lit, where SSR support lives in the experimental @lit-labs/ssr package | First-party hydrate module (@awc-ui/core/hydrate) renders declarative shadow DOM on the server; components hydrate in place |
| Charts | None | md-line-chart, md-bar-chart, md-area-chart, md-pie-chart, md-sparkline — see the charts gallery |
| Data table | None | Full md-table family: sorting, pagination, row expansion, selection, toolbar |
| Pickers | None shipped (a date picker was planned but never released) | md-date-picker, md-time-picker, md-color-picker |
| Accessibility in CI | Components built to Material and ARIA guidance by the team that wrote them | The same ARIA discipline, plus an axe-core audit of every Storybook story runs in CI on every pull request |
| Maintenance status | In maintenance mode since June 2024, per the project README: existing components are maintained, new component work is paused | Actively developed |
| Provenance | Google’s Material Design team — the canonical M3 web implementation | Independent implementation of the public M3 spec |
| Install base | Large, with years of production use across Google properties and the wider community | Younger and smaller |
| Framework story | Framework-agnostic custom elements | Framework-agnostic custom elements, plus published React, Angular, Vue, and Svelte wrapper packages |
Where @material/web is genuinely ahead
Section titled “Where @material/web is genuinely ahead”An honest comparison concedes the other side’s strengths, and
@material/web has real ones:
- Provenance. It is written by the same organization that writes the Material Design spec. When the spec is ambiguous, their reading is the authoritative one.
- Maturity. Its core components have shipped in high-traffic production surfaces for years. That kind of hardening only comes from install base.
- Stability as a feature. Maintenance mode also means a frozen API surface — if your product only needs the components it already has, “no new components” can read as “no churn.”
What AWC UI adds
Section titled “What AWC UI adds”The clearest difference is the catalog. Every component below is one
@material/web never shipped — each demo is live.
Date picker
Section titled “Date picker”Show code for each technology
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-date-picker variant="docked" label="Due date" clearable style="max-width: 280px;"></md-date-picker>import { MdDatePicker } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdDatePicker variant="docked" label="Due date" clearable style={{ maxWidth: '280px' }}></MdDatePicker>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-date-picker variant="docked" label="Due date" clearable style="max-width: 280px;"></md-date-picker><script setup>
import '@awc-ui/core/define';
</script>
<template>
<md-date-picker variant="docked" label="Due date" clearable style="max-width: 280px;"></md-date-picker>
</template><script>
import '@awc-ui/core/define';
</script>
<md-date-picker variant="docked" label="Due date" clearable style="max-width: 280px;"></md-date-picker>Snackbar
Section titled “Snackbar”Show code for each technology
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-button variant="tonal" data-archive>Archive message</md-button>
<md-snackbar message="Message archived" action="Undo"></md-snackbar>
<script type="module">
const snackbar = document.querySelector('md-snackbar');
document.querySelector('[data-archive]').addEventListener('click', () => snackbar.show());
snackbar.addEventListener('mdAction', () => snackbar.hide('action'));
</script>import { useEffect } from 'react';
import { MdButton, MdSnackbar } from '@awc-ui/react';
export function Demo() {
useEffect(() => {
const snackbar = document.querySelector('md-snackbar');
document.querySelector('[data-archive]').addEventListener('click', () => snackbar.show());
snackbar.addEventListener('mdAction', () => snackbar.hide('action'));
}, []);
return (
<>
<MdButton variant="tonal" data-archive>Archive message</MdButton>
<MdSnackbar message="Message archived" action="Undo"></MdSnackbar>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
// app.component.ts
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';
@Component({
selector: 'app-demo',
templateUrl: './app.component.html',
})
export class DemoComponent implements AfterViewInit {
ngAfterViewInit() {
const snackbar = document.querySelector('md-snackbar');
document.querySelector('[data-archive]').addEventListener('click', () => snackbar.show());
snackbar.addEventListener('mdAction', () => snackbar.hide('action'));
}
}
<!-- app.component.html -->
<md-button variant="tonal" data-archive>Archive message</md-button>
<md-snackbar message="Message archived" action="Undo"></md-snackbar><script setup>
import { onMounted } from 'vue';
import '@awc-ui/core/define';
onMounted(() => {
const snackbar = document.querySelector('md-snackbar');
document.querySelector('[data-archive]').addEventListener('click', () => snackbar.show());
snackbar.addEventListener('mdAction', () => snackbar.hide('action'));
});
</script>
<template>
<md-button variant="tonal" data-archive>Archive message</md-button>
<md-snackbar message="Message archived" action="Undo"></md-snackbar>
</template><script>
import { onMount } from 'svelte';
import '@awc-ui/core/define';
onMount(() => {
const snackbar = document.querySelector('md-snackbar');
document.querySelector('[data-archive]').addEventListener('click', () => snackbar.show());
snackbar.addEventListener('mdAction', () => snackbar.hide('action'));
});
</script>
<md-button variant="tonal" data-archive>Archive message</md-button>
<md-snackbar message="Message archived" action="Undo"></md-snackbar>Tooltip
Section titled “Tooltip”Show code for each technology
<!-- index.html <head> — the icon font the components draw from -->
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap">
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-tooltip text="Download report">
<md-icon-button icon="download" aria-label="Download report"></md-icon-button>
</md-tooltip>// Icons need the Material Symbols stylesheet in index.html — see Installation.
import { MdIconButton, MdTooltip } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdTooltip text="Download report">
<MdIconButton icon="download" aria-label="Download report"></MdIconButton>
</MdTooltip>
</>
);
}// Icons need the Material Symbols stylesheet in index.html — see Installation.
// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-tooltip text="Download report">
<md-icon-button icon="download" aria-label="Download report"></md-icon-button>
</md-tooltip><script setup>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import '@awc-ui/core/define';
</script>
<template>
<md-tooltip text="Download report">
<md-icon-button icon="download" aria-label="Download report"></md-icon-button>
</md-tooltip>
</template><script>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import '@awc-ui/core/define';
</script>
<md-tooltip text="Download report">
<md-icon-button icon="download" aria-label="Download report"></md-icon-button>
</md-tooltip>Show code for each technology
<!-- index.html <head> — the icon font the components draw from -->
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap">
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-icon-button icon="mail" variant="standard" aria-label="Inbox, 5 unread">
<md-badge value="5"></md-badge>
</md-icon-button>
<md-icon-button icon="notifications" variant="standard" aria-label="Notifications, new activity">
<md-badge variant="small"></md-badge>
</md-icon-button>// Icons need the Material Symbols stylesheet in index.html — see Installation.
import { MdBadge, MdIconButton } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdIconButton icon="mail" variant="standard" aria-label="Inbox, 5 unread">
<MdBadge value="5"></MdBadge>
</MdIconButton>
<MdIconButton icon="notifications" variant="standard" aria-label="Notifications, new activity">
<MdBadge variant="small"></MdBadge>
</MdIconButton>
</>
);
}// Icons need the Material Symbols stylesheet in index.html — see Installation.
// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-icon-button icon="mail" variant="standard" aria-label="Inbox, 5 unread">
<md-badge value="5"></md-badge>
</md-icon-button>
<md-icon-button icon="notifications" variant="standard" aria-label="Notifications, new activity">
<md-badge variant="small"></md-badge>
</md-icon-button><script setup>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import '@awc-ui/core/define';
</script>
<template>
<md-icon-button icon="mail" variant="standard" aria-label="Inbox, 5 unread">
<md-badge value="5"></md-badge>
</md-icon-button>
<md-icon-button icon="notifications" variant="standard" aria-label="Notifications, new activity">
<md-badge variant="small"></md-badge>
</md-icon-button>
</template><script>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import '@awc-ui/core/define';
</script>
<md-icon-button icon="mail" variant="standard" aria-label="Inbox, 5 unread">
<md-badge value="5"></md-badge>
</md-icon-button>
<md-icon-button icon="notifications" variant="standard" aria-label="Notifications, new activity">
<md-badge variant="small"></md-badge>
</md-icon-button>Rating
Section titled “Rating”Show code for each technology
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-rating value="3.5" precision="0.5" show-value-label rating-label="Product rating"></md-rating>import { MdRating } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdRating value="3.5" precision="0.5" showValueLabel ratingLabel="Product rating"></MdRating>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-rating value="3.5" precision="0.5" show-value-label rating-label="Product rating"></md-rating><script setup>
import '@awc-ui/core/define';
</script>
<template>
<md-rating value="3.5" precision="0.5" show-value-label rating-label="Product rating"></md-rating>
</template><script>
import '@awc-ui/core/define';
</script>
<md-rating value="3.5" precision="0.5" show-value-label rating-label="Product rating"></md-rating>OTP field
Section titled “OTP field”Show code for each technology
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-otp-field length="6" group-size="3" label="Verification code" style="max-width: 320px;"></md-otp-field>import { MdOtpField } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdOtpField length="6" groupSize="3" label="Verification code" style={{ maxWidth: '320px' }}></MdOtpField>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-otp-field length="6" group-size="3" label="Verification code" style="max-width: 320px;"></md-otp-field><script setup>
import '@awc-ui/core/define';
</script>
<template>
<md-otp-field length="6" group-size="3" label="Verification code" style="max-width: 320px;"></md-otp-field>
</template><script>
import '@awc-ui/core/define';
</script>
<md-otp-field length="6" group-size="3" label="Verification code" style="max-width: 320px;"></md-otp-field>To see these composed into full screens — checkout flows, dashboards, data consoles — browse the recipes.
FAQ: the md-* tag collision
Section titled “FAQ: the md-* tag collision”Can I use AWC UI and @material/web on the same page?
No. Both libraries define the same md-* tag names (md-button,
md-checkbox, md-dialog, …), and the custom elements registry allows each
tag to be defined exactly once per page — the second library to load throws a
DOMException on customElements.define. There is no scoping trick that
makes them coexist in one document.
So how do I migrate?
Treat it as a per-page (or per-app) decision, not a per-component one:
- Whole page at a time. Swap the loader import, keep the markup, and reconcile attribute differences. Because the tag names match, most templates port without renaming elements — the diff is in attributes and events, not structure.
- Separate documents can differ. A multi-page site or micro-frontend setup with genuinely separate documents (including iframes) can run one library per document during a transition.
- What doesn’t work: trialing AWC UI inside an existing
@material/webpage. That is a real constraint of the shared tag prefix, and it cuts both ways.
Is the shared prefix intentional?
The md-* prefix is the natural one for a Material Design library, and
keeping it means a migration reads as an attribute-level diff rather than a
rewrite. The cost is the mutual exclusion above — we would rather state that
plainly than have you discover it in a console error.
Related
Section titled “Related”- Comparisons index — the other options measured the same way
- Recipes — complete screens built from the AWC UI catalog
- Getting started — install and render your first component
Material Design is a trademark of Google LLC. AWC UI is not affiliated with or endorsed by Google.