Skip to content

Date Picker

A Material Design 3 date picker implementing the MD3 Date Pickers specification:

Pick a single calendar date through one of three presentations:

  • modal-input (default) — outlined text field with a trailing calendar icon that opens a modal calendar dialog. Best for typed-or-pointed entry on any viewport.
  • modal — a bare modal dialog with no inline trigger; surface it from your own button via el.show() or two-way open binding.
  • docked — outlined text field that pops a calendar directly beneath the input. Best for desktop forms where the calendar can stay on screen. Its header uses month and year dropdown menu buttons, each flanked by prev/next chevron icon buttons (‹ [Aug ▾] › ‹ [2025 ▾] ›), and a Cancel / OK action row commits the staged selection.

Inside the modal, a header toggle flips the calendar to a typed date-input view (the MD3 “modal date input” pattern), which parses and validates entered dates and shows inline errors.

Calendar navigation is keyboard-first, fully ARIA-labelled, locale-aware via the Intl.DateTimeFormat / Intl.Locale APIs (including locale-driven first-day-of-week), and renders correctly in RTL.

Live preview Open in Storybook
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 style="min-width: 280px;"></md-date-picker>

Already installed? See the Installation guide for one-time package setup (core + tokens, fonts). Each tab below shows two patterns for using md-date-picker in your project: Option A registers every AWC UI component at once (simplest), Option B imports only this component for tree-shake-friendly bundles.

<!-- ─── Option A: global registration (all components) ─── -->
<script type="module">
  import '@awc-ui/core/define';
</script>


<!-- ─── Option B: single import (tree-shake only md-date-picker) ─── -->
<script type="module">
  import '@awc-ui/core/components/md-date-picker';
</script>


<md-date-picker></md-date-picker>
  • A single calendar date the user picks or types: a due date, a birthday, a booking.
  • Dates far from today, or where the day of the week matters — that is what a calendar grid buys you over a plain field.
SituationUse instead
A time of daymd-time-picker
A date very close to todayA few md-chip shortcuts (“Today”, “Tomorrow”)
A free-form stringmd-text-field
Choosing from a fixed listmd-select
NeedSetting
Field plus modal calendarvariant="modal-input" (default)
A bare dialog you open yourselfvariant="modal" + show()
Calendar under the field, desktop formsvariant="docked"
Restrict the rangemin / max, or isDateDisabled for gaps
A clear affordanceclearable
Filled field instead of outlinedfield-variant="filled"
Force a localelocale, else the document’s
Compact paneldensity="-1…-4"
One-click picking, no confirm stepcommit-on-select
Keep a docked panel open on click-awayoutside-click-dismissible="false"
Keep a modal open when the scrim is clickedscrim-dismissible="false"
VariantTriggerSurfaceCommit behaviorUse case
modal-inputOutlined text fieldCentered modal dialogOK buttonGeneral-purpose date entry
modalNone (show())Centered modal dialogOK buttonSurfaced from a custom trigger
dockedOutlined text fieldPopup beneath the fieldOK buttonDesktop forms
modal-input, docked, and a filled field — click one to open it Open in Storybook
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 label="Due date"></md-date-picker>
<md-date-picker variant="docked" label="Due date"></md-date-picker>
<md-date-picker field-variant="filled" label="Due date"></md-date-picker>

The docked variant follows the MD3 docked date-picker anatomy:

  1. Outlined text field — the trigger renders the shared md-text-field (variant="outlined", label="Date", supporting text MM/DD/YYYY) with a trailing md-icon-button calendar toggle that opens the calendar.
  2. Month menu button (month-menu-button) — opens a baseline md-menu with md-menu-item radio checks on the left; the day grid is hidden until a month is chosen.
  3. Year menu button (year-menu-button) — same pattern for years in the min/max range; scrollable list, day grid hidden while open.
  4. Icon buttonsmd-icon-button prev/next chevrons flank each menu button (prev-month-button / next-month-button, prev-year-button / next-year-button) and step the view by one month / year. Chevrons mirror in RTL. Each chevron is wrapped in md-tooltip showing the action and its keyboard shortcut (e.g. “Previous year (Shift+Page Up)”). The tooltip sets aria-description on the trigger so screen readers announce the shortcut on focus. The menu buttons expose Shift+M / Shift+Y shortcuts (via md-tooltip) to jump from the day grid into the month / year listboxes. Each unit is both cyclable (chevrons) and selectable (dropdown).
  5. Weekday labels — the S M T W T F S row (24px-tall column headers).
  6. Unselected date (day-unselected) — plain in-month day text.
  7. Today’s date (day-today) — outlined circle ring.
  8. Outside-month date (day-outside) — muted adjacent-month day.
  9. Text buttons — Cancel / OK action row.
  10. Selected date (day-selected) — filled circle.
  11. Container (panel) — corner-large elevated surface.
Open it to see the anatomy above — menu buttons, flanking chevrons, weekday row, action row Open in Storybook
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="Date"
  supporting-text="MM/DD/YYYY"
  ></md-date-picker>

Month/year selection uses the shared md-menu / md-menu-item baseline menu (type="radio", check-position="start"). Arrow keys, Home/End, Enter, and Space follow md-menu roving focus; Escape closes the menu and restores the day grid.

  • Enabled / populated — empty or holding a selected value.
  • Focused — 2px primary outline on the field; primary label color.
  • Errorerror + error-text paint the field and helper text with the error color; the input is marked aria-invalid.
  • Disableddisabled removes the field from the tab order and blocks interaction.
  • Day-cell states — unselected (plain text), today (outline ring), selected (filled circle), disabled / out-of-range, and out-of-month (muted).
Empty, populated, supporting text, error, disabled and clearable Open in Storybook
Show code for each technology
<!-- index.html — register the AWC UI elements once -->
<script type="module">
  import '@awc-ui/core/define';
</script>

<div style="display:flex;gap:20px;flex-wrap:wrap;align-items:flex-start;">
  <md-date-picker label="Empty" style="min-width: 220px;"></md-date-picker>
  <md-date-picker label="Populated" value="2026-08-17" style="min-width: 220px;"></md-date-picker>
  <md-date-picker label="Supporting" supporting-text="DD/MM/YYYY" style="min-width: 220px;"></md-date-picker>
  <md-date-picker label="Error" error error-text="Date is in the past" style="min-width: 220px;"></md-date-picker>
  <md-date-picker label="Disabled" disabled style="min-width: 220px;"></md-date-picker>
  <md-date-picker label="Clearable" value="2026-08-17" clearable style="min-width: 220px;"></md-date-picker>
</div>
  • Typed entry (no input masks)modal-input and docked text fields do not insert separators or reformat while typing. The raw characters you type are preserved until you commit with Enter or by leaving the field (blur). On commit the value is parsed flexibly (slashes, dashes, dots, or spaces when date-separator is unset; only the configured separator when set; optional leading zeros; compact digit strings such as 06122025 or 6122025) and reformatted for the configured locale and date-separator. Ambiguous month/day order (e.g. 06/12/2025 or 12062025) is resolved with day-first heuristics when a segment exceeds 12, otherwise the locale hint applies. The value prop and mdChange / mdSelected events always use ISO YYYY-MM-DD. This avoids mid-keystroke mutations that confuse screen readers and voice dictation.

  • Trigger input exposes aria-haspopup="dialog" and aria-expanded.

  • Dialog uses role="dialog" + aria-modal="true", traps Tab focus, closes on Escape, and restores focus to the trigger on close.

  • Calendar uses role="grid" with a presentational weekday row (aria-hidden) and role="gridcell" day buttons using roving tabindex; each day exposes a full localized aria-label and aria-selected. Today is aria-current="date". Weekday abbreviations rely on locale convention for assistive technology; sighted users see the full weekday name in an md-tooltip on hover (weekdays are not focusable).

  • Day-grid keyboard shortcuts (modal, modal-input, and docked variants):

    KeyAction
    Arrow keysMove one day (left/right) or one week (up/down)
    HomeFirst day of the focused month
    EndLast day of the focused month
    Page Up / Page DownSame calendar day in the previous / next month (clamped, e.g. Jan 31 → Feb 28)
    Shift+Page Up / Shift+Page DownSame calendar day in the previous / next year
    Shift+M (docked only)Open the month list and move focus into it
    Shift+Y (docked only)Open the year list and move focus into it
    EnterSelect the focused day, commit, and close (single variants)
    SpaceSelect the focused day (staged until OK in single variants)
    EscapeDismiss without committing

    Shortcuts are handled at the panel level while the picker is open, so they work when focus is anywhere inside the calendar panel (day grid, nav chevrons, docked month/year buttons, or action row) — not only on a focused day cell.

    On keyboards without dedicated Page Up / Page Down keys (many Mac laptops), use Fn+↑ / Fn+↓ for Page Up / Page Down (browsers report these as PageUp / PageDown). Some Mac layouts also emit Option+↑ / Option+↓ instead — the picker maps those to the same month navigation. Tooltips and aria-description text use the Page Up / Page Down labels per the MD3 spec. Shift+M / Shift+Y use Shift + the letter key (m / y, case-insensitive); they work from the day grid, nav controls, and the docked trigger field while the panel is open.

  • Month/year nav chevrons (docked and modal) expose md-tooltip hints on hover and focus with the matching shortcut: Page Up / Page Down for month, Shift+Page Up / Shift+Page Down for year. Screen readers receive the shortcut via aria-description on the trigger (set by md-tooltip).

  • Docked month/year menu buttons expose Shift+M / Shift+Y via md-tooltip aria-description. The truncated month label (e.g. Aug) shows the full month name in the tooltip on hover and keyboard focus.

  • Docked month/year dropdown menus use the shared md-menu baseline list; Arrow Up/Down, Home/End, Enter/Space to select, and Escape to close (returning focus to the menu button).

  • Tested with axe-core — zero WCAG 2.1 AA violations.

The locale prop drives Intl formatting only — month and weekday names, date display order, first day of week, and typed-input format hints. It does not auto-translate button labels, headlines, tooltips, or other static UI copy; pass explicit *-label attributes for non-English locales.

UI stringPropHTML attribute
Field / dialog labellabellabel
Header supporting textheadlineheadline
Large headline (no date staged)selectDateLabelselect-date-label
Large headline (typed-entry mode)enterDatesLabelenter-dates-label
Cancel buttoncancelLabelcancel-label
OK buttonokLabelok-label
Invalid typed date errorinvalidDateLabelinvalid-date-label

Tooltip visible text reuses the same *-label props as aria-label on each control. Keyboard shortcut suffixes (Page Up, Shift+M, etc.) are appended in English and are not localized.

Controlaria-label propTooltip pattern
Previous / next month chevronpreviousMonthLabel / nextMonthLabel{label} (Page Up) or (Page Down)
Previous / next year chevron (docked)previousYearLabel / nextYearLabel{label} (Shift+Page Up) or (Shift+Page Down)
Docked month menu buttonchooseMonthLabel{fullMonth} · {label} (Shift+M)
Docked year menu buttonchooseYearLabel{label} (Shift+Y)
Modal month/year togglechooseMonthYearLabel{monthYear} · {chooseMonthAndYearLabel}
Calendar / text mode toggletoggleCalendarLabel / toggleTextLabelSame as aria-label
Calendar trigger iconopenCalendarLabel / closeCalendarLabelaria-label only (no tooltip)

Weekday column headers show locale-formatted full weekday names via md-tooltip (derived from locale, not overridable).

Open the calendar — headline, month names, weekday tooltips and the キャンセル / OK row are all localized Open in Storybook
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
  label="日付"
  locale="ja-JP"
  headline="日付を選択"
  select-date-label="日付を選択"
  enter-dates-label="日付を入力"
  cancel-label="キャンセル"
  ok-label="OK"
  previous-month-label="前の月"
  next-month-label="次の月"
  previous-year-label="前の年"
  next-year-label="次の年"
  choose-month-label="月を選択"
  choose-year-label="年を選択"
  choose-month-year-label="月と年を選択"
  choose-month-and-year-label="月と年を選択"
  toggle-calendar-label="カレンダー入力に切り替え"
  toggle-text-label="テキスト入力に切り替え"
  open-calendar-label="カレンダーを開く"
  close-calendar-label="カレンダーを閉じる"
  year-grid-label="年"
  value="2025-06-15"
  ></md-date-picker>

RTL — every box metric is a logical property. The previous/next month and year chevrons mirror automatically, and the docked month/year menus anchor to the inline-start edge of their buttons. See RTL.

Same markup, dir=ltr vs dir=rtl — open both to compare the calendars Open in Storybook
Show code for each technology
<!-- index.html — register the AWC UI elements once -->
<script type="module">
  import '@awc-ui/core/define';
</script>

<div style="display:flex;gap:20px;flex-wrap:wrap;align-items:flex-start;">
  <div dir="ltr"><md-date-picker label="Date" style="min-width: 240px;"></md-date-picker></div>
  <div dir="rtl"><md-date-picker locale="ar" label="التاريخ" style="min-width: 240px;"></md-date-picker></div>
</div>

Densitydensity="-1…-4" compacts the field and the panel, and locally overrides an inherited data-density rung. See Density.

Density 0 through -4 — open one to see the panel follow Open in Storybook
Show code for each technology
<!-- index.html — register the AWC UI elements once -->
<script type="module">
  import '@awc-ui/core/define';
</script>

<div style="display:flex;gap:16px;flex-wrap:wrap;align-items:flex-start;">
  <md-date-picker density="0" label="0" value="2026-08-17" style="min-width: 170px;"></md-date-picker>
  <md-date-picker density="-1" label="-1" value="2026-08-17" style="min-width: 170px;"></md-date-picker>
  <md-date-picker density="-2" label="-2" value="2026-08-17" style="min-width: 170px;"></md-date-picker>
  <md-date-picker density="-3" label="-3" value="2026-08-17" style="min-width: 170px;"></md-date-picker>
  <md-date-picker density="-4" label="-4" value="2026-08-17" style="min-width: 170px;"></md-date-picker>
</div>

i18n — see Localization above: the locale drives month and weekday names, the first day of the week, and the parse/format order, while every user-facing string (headline, cancel-label, ok-label, invalid-date-label, the nav labels…) is a prop you must translate.

Override these on the host element for per-instance theming:

PropertyDescriptionDefault
--md-date-picker-container-colorDialog / popup background--md-sys-color-surface-container-high
--md-date-picker-container-shapeDialog / popup corner radius--md-sys-shape-corner-extra-large
--md-date-picker-field-colorText-field outline / active indicator color--md-sys-color-outline
--md-date-picker-field-text-colorText-field input text color--md-sys-color-on-surface
--md-date-picker-field-container-colorFilled-field container background--md-sys-color-surface-container-highest
--md-date-picker-field-container-shapeText-field corner radius--md-sys-shape-corner-extra-small
--md-date-picker-field-filled-pillFilled field: 1 rounds all corners (capsule); omit for MD3 top-only0
--md-date-picker-field-supporting-colorSupporting text below the field--md-sys-color-on-surface-variant
--md-date-picker-label-colorFloating label color--md-sys-color-on-surface-variant
--md-date-picker-headline-colorModal headline (selected date) color--md-sys-color-on-surface
--md-date-picker-supporting-colorHeader supporting-text color--md-sys-color-on-surface-variant
--md-date-picker-weekday-colorWeekday column-header color--md-sys-color-on-surface-variant
--md-date-picker-day-colorDay cell text color--md-sys-color-on-surface
--md-date-picker-day-selected-colorSelected day text color--md-sys-color-on-primary
--md-date-picker-day-selected-bgSelected day container color--md-sys-color-primary
--md-date-picker-today-outline-colorToday’s outline ring color--md-sys-color-primary
--md-date-picker-day-outline-colorDeprecated (unselected days are plain text)
--md-date-picker-action-colorCancel / OK action label color--md-sys-color-primary
--md-date-picker-menu-colorDocked month/year menu surface--md-sys-color-surface-container-high
--md-date-picker-scrim-colorModal scrim color--md-sys-color-scrim
--md-date-picker-icon-colorTrailing / nav icon color--md-sys-color-on-surface-variant
--md-date-picker-panel-widthModal / docked popup panel width360px
--md-date-picker-panel-max-block-sizeModal / docked popup max height524px
--md-date-picker-docked-panel-widthDocked popup width override--md-date-picker-panel-width
Branded, tonal, and squared day cells — open each to see the panel Open in Storybook
Show code for each technology
<!-- index.html — register the AWC UI elements once -->
<script type="module">
  import '@awc-ui/core/define';
</script>

<style>
  .dp-brand {
  --md-date-picker-day-selected-bg: #7c4dff;
  --md-date-picker-headline-color: #7c4dff;
  --md-date-picker-today-outline-color: #7c4dff;
  --md-date-picker-action-color: #7c4dff;
  --md-date-picker-container-shape: 12px;
  }
  .dp-tonal {
  --md-date-picker-container-color: var(--md-sys-color-secondary-container);
  --md-date-picker-day-selected-bg: var(--md-sys-color-tertiary);
  --md-date-picker-day-selected-color: var(--md-sys-color-on-tertiary);
  --md-date-picker-weekday-color: var(--md-sys-color-on-secondary-container);
  }
  .dp-squared {
  --md-date-picker-day-selected-shape: 6px;
  --md-date-picker-day-today-shape: 6px;
  --md-date-picker-panel-width: 320px;
  }
</style>
<div style="display:flex;gap:20px;flex-wrap:wrap;align-items:flex-start;">
  <md-date-picker class="dp-brand" variant="docked" label="Branded" value="2026-08-17" style="min-inline-size: 230px;"></md-date-picker>
  <md-date-picker class="dp-tonal" variant="docked" label="Tonal" value="2026-08-17" style="min-inline-size: 230px;"></md-date-picker>
  <md-date-picker class="dp-squared" variant="docked" label="Squared days" value="2026-08-17" style="min-inline-size: 230px;"></md-date-picker>
</div>

Every default resolves through an md-sys-color role, so a picker that sets no custom properties follows the theme on its own:

Untouched defaults — follows the page theme Open in Storybook
Show code for each technology
<!-- index.html — register the AWC UI elements once -->
<script type="module">
  import '@awc-ui/core/define';
</script>

<div style="display:flex;gap:20px;flex-wrap:wrap;align-items:flex-start;">
  <md-date-picker label="Untouched defaults" value="2026-08-17" clearable style="min-width: 240px;"></md-date-picker>
  <md-date-picker variant="docked" label="Docked" value="2026-08-17" style="min-width: 240px;"></md-date-picker>
</div>

Style internal elements through the shadow boundary:

PartElement
fieldText-field container (trigger)
leading-iconSlotted leading-icon wrapper
labelFloating field label
inputField text input
calendar-buttonTrailing calendar toggle button
supporting-textHelper / error text below the field
modal / scrimModal wrapper / scrim overlay
panelDialog / docked popup surface
headerModal header region
supportingHeader supporting text
headlineLarge selected-date headline
mode-toggleCalendar / text-input switch button
bodyCalendar / year / input body region
navMonth label + prev/next nav row (also docked nav)
month-toggleMonth-year label button (year grid; modal)
prev-button / next-buttonMonth navigation buttons (modal)
month-menu-button / year-menu-buttonDocked month / year dropdown buttons
prev-month-button / next-month-buttonDocked month chevron icon buttons
prev-year-button / next-year-buttonDocked year chevron icon buttons
selection-dividerDivider between docked nav and month/year menu
month-menu / year-menuDocked inline baseline month / year menus
month-option / month-option-selectedMonth menu option / selected
year-option / year-option-selectedYear menu option / selected
calendarCalendar grid wrapper
weekdays / weekdayWeekday header row / single column header
gridDay-cell grid
dayA day cell button
day-unselected / day-selected / day-today / day-disabled / day-outsideDay cell state parts
year-grid / year / year-selectedYear selection grid + options
entry / entry-inputIn-dialog typed date-entry region
actionsCancel / OK action row
cancel-button / ok-buttonAction buttons
day-selected, day-today, weekday, headline and ok-button — open it Open in Storybook
Show code for each technology
<!-- index.html — register the AWC UI elements once -->
<script type="module">
  import '@awc-ui/core/define';
</script>

<style>
  .parts-dp::part(field) { --md-date-picker-field-color: var(--md-sys-color-primary); }
  .parts-dp::part(day-selected) { border-radius: 6px; }
  .parts-dp::part(day-today) { outline-width: 2px; }
  .parts-dp::part(weekday) { text-transform: uppercase; letter-spacing: .1em; font-size: 10px; }
  .parts-dp::part(headline) { font-style: italic; }
  .parts-dp::part(ok-button) { font-weight: 700; }
</style>
<md-date-picker class="parts-dp" variant="docked" label="Styled parts" value="2026-08-17" style="min-width: 260px;"></md-date-picker>
SlotDescription
leading-iconCustom leading icon for the field
headerReplaces the entire modal header
actionsReplaces the Cancel / OK action row
A slotted leading icon Open in Storybook
event
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-date-picker label="Custom icon">
  <span slot="leading-icon" class="material-symbols-outlined">event</span>
</md-date-picker>
EventCancelableDetailFires
mdInputno{ value: string }Every keystroke in the text field
mdSelectednoMdDatePickerSelectedDetailA day cell is picked — staged, not committed
mdChangenoMdDatePickerChangeDetailThe value commits — OK, Enter, or a docked pick
mdOpen / mdClosenovoidThe panel opened / closed
mdCancelnovoidDismissed without committing — Cancel, Escape, the scrim, or a click outside a docked panel
mdViewChangenoMdDatePickerViewChangeDetailThe calendar moved month or year, or switched to the year grid
mdMenuOpen / mdMenuSelectnoMdDatePickerMenu*DetailThe docked month / year dropdowns
mdModeChangenoMdDatePickerModeChangeDetailThe calendar ⇄ text-input toggle

Set commit-on-select and a day click stages and commits: mdSelected then mdChange fire together, the panel dismisses, and the built-in Cancel / OK row is not rendered — there is nothing left for it to confirm.

Open each and pick a day — only the first waits for OK Open in Storybook
Show code for each technology
<!-- index.html — register the AWC UI elements once -->
<script type="module">
  import '@awc-ui/core/define';
</script>

<!-- default: a day click only stages; OK commits -->
<md-date-picker label="Due date" value="2026-08-10"></md-date-picker>

<!-- one click, no confirm step, no action row -->
<md-date-picker label="Due date" value="2026-08-10" commit-on-select></md-date-picker>

A modal puts a scrim between the panel and the page, and clicking it dismisses — scrim-dismissible turns that off. A docked panel has no scrim, so the click that lands outside it is caught on the document instead; outside-click-dismissible is its opt-out.

Either way the dismissal is a cancel: mdCancel fires and anything staged is discarded. Set outside-click-dismissible="false" when a stray click must not throw away a half-made choice, or when the picker sits inside another popup whose own click-away handling would otherwise close both at once.

Open each, then click on the page background — only the first closes Open in Storybook
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="Start"></md-date-picker>

<md-date-picker
  variant="docked"
  label="Start"
  outside-click-dismissible="false"
  ></md-date-picker>
Every event, live — newest first Open in Storybook
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 id="demo"></md-date-picker>

<script type="module">
  import { defineCustomElements } from '@awc-ui/core/loader';
  defineCustomElements(window);

  const el = document.querySelector('#demo');

  el.addEventListener('mdChange', (event) => {
    // e.detail: MdDatePickerChangeDetail
    console.log('mdChange', event.detail);
  });

  el.addEventListener('mdSelected', (event) => {
    // e.detail: MdDatePickerSelectedDetail
    console.log('mdSelected', event.detail);
  });

  el.addEventListener('mdInput', (event) => {
    // e.detail: { value: string }
    console.log('mdInput', event.detail);
  });

  el.addEventListener('mdOpen', (event) => {
    console.log('mdOpen', event.detail);
  });

  el.addEventListener('mdClose', (event) => {
    console.log('mdClose', event.detail);
  });

  el.addEventListener('mdCancel', (event) => {
    console.log('mdCancel', event.detail);
  });

  el.addEventListener('mdViewChange', (event) => {
    // e.detail: MdDatePickerViewChangeDetail
    console.log('mdViewChange', event.detail);
  });

  el.addEventListener('mdMenuOpen', (event) => {
    // e.detail: MdDatePickerMenuOpenDetail
    console.log('mdMenuOpen', event.detail);
  });

  el.addEventListener('mdMenuSelect', (event) => {
    // e.detail: MdDatePickerMenuSelectDetail
    console.log('mdMenuSelect', event.detail);
  });

  el.addEventListener('mdModeChange', (event) => {
    // e.detail: MdDatePickerModeChangeDetail
    console.log('mdModeChange', event.detail);
  });
</script>

Properties

PropertyAttributeTypeDefaultReflects
valueMissingLabelvalue-missing-labelstring'Please choose a date.'
reserveSupportingSpacereserve-supporting-spacebooleanfalse
variantvariant'modal' | 'modal-input' | 'docked''modal-input'Yes
valuevaluestring''Yes
labellabelstring'Date'
placeholderplaceholderstring''
minminstring''
maxmaxstring''
openopenbooleanfalseYes
disableddisabledbooleanfalseYes
requiredrequiredbooleanfalseYes
clearableclearablebooleanfalseYes
clearLabelclear-labelstring'Clear date'
errorerrorbooleanfalseYes
errorTexterror-textstring''
supportingTextsupporting-textstring''
localelocalestring''
dateSeparatordate-separatorstring''
fieldVariantfield-variant'outlined' | 'filled''outlined'
firstDayOfWeekfirst-day-of-weeknumber-1
headlineheadlinestring'Select date'
cancelLabelcancel-labelstring'Cancel'
okLabelok-labelstring'OK'
selectDateLabelselect-date-labelstring'Select date'
enterDatesLabelenter-dates-labelstring'Enter dates'
invalidDateLabelinvalid-date-labelstring'Invalid date'
previousMonthLabelprevious-month-labelstring'Previous month'
nextMonthLabelnext-month-labelstring'Next month'
previousYearLabelprevious-year-labelstring'Previous year'
nextYearLabelnext-year-labelstring'Next year'
chooseMonthLabelchoose-month-labelstring'Choose month'
chooseYearLabelchoose-year-labelstring'Choose year'
chooseMonthYearLabelchoose-month-year-labelstring'Choose a different month and year'
chooseMonthAndYearLabelchoose-month-and-year-labelstring'Choose month and year'
toggleCalendarLabeltoggle-calendar-labelstring'Switch to calendar input'
toggleTextLabeltoggle-text-labelstring'Switch to text input'
openCalendarLabelopen-calendar-labelstring'Open calendar'
closeCalendarLabelclose-calendar-labelstring'Close calendar'
calendarIconcalendar-iconstring'calendar_today'
yearGridLabelyear-grid-labelstring'Year'
scrimDismissiblescrim-dismissiblebooleantrue
outsideClickDismissibleoutside-click-dismissiblebooleantrue
commitOnSelectcommit-on-selectbooleanfalseYes
namenamestring''
isDateDisabledJS only(date: Date) => boolean
densitydensity0 | -1 | -2 | -3 | -40Yes

Methods

MethodParameters
show()none
close()none
clear()none
focusInput()none
getValidity()none
checkValidity()none
reportValidity()none
setCustomValidity()message: string

Slots

SlotDescription
leading-iconCustom leading icon for the field
calendar-iconCustom trailing calendar toggle icon (replaces calendar-icon prop)
headerReplaces the entire modal header
actionsReplaces the Cancel / OK action row

CSS Custom Properties

Override on the host element for per-instance theming:

PropertyDescription
--md-date-picker-container-colorDialog / popup body background
--md-date-picker-header-colorModal header background
--md-date-picker-container-shapeDialog / popup corner radius
--md-date-picker-field-colorText-field resting outline color
--md-date-picker-field-focus-colorText-field focused outline / label color
--md-date-picker-field-text-colorText-field input text color
--md-date-picker-field-filled-pillFilled field: 1 for capsule/pill (all corners)
--md-date-picker-field-supporting-colorSupporting text below the field
--md-date-picker-label-colorFloating label color
--md-date-picker-headline-colorModal headline (selected date) color
--md-date-picker-supporting-colorHeader supporting-text color
--md-date-picker-weekday-colorWeekday column-header color
--md-date-picker-day-colorDay cell text color
--md-date-picker-day-selected-colorSelected day text color
--md-date-picker-day-selected-bgSelected day container color
--md-date-picker-day-selected-shapeSelected day corner radius (overrides morph)
--md-date-picker-day-today-shapeToday ring corner radius
--md-date-picker-today-outline-colorToday's outline ring color
--md-date-picker-day-outline-colorDeprecated — unselected days are plain text
--md-date-picker-action-colorCancel / OK action label color
--md-date-picker-menu-colorDocked month/year menu surface
--md-date-picker-selection-bloom-durationDocked month/year menu bloom-in duration
--md-date-picker-selection-bloom-easingDocked month/year menu bloom-in easing
--md-date-picker-selection-bloom-out-durationDocked menu bloom-out on pick (default short4)
--md-date-picker-calendar-bloom-durationDocked day grid bloom-in after pick (default medium2)
--md-date-picker-grid-slide-durationDay grid month/year slide duration
--md-date-picker-panel-bloom-out-durationPanel bloom-out on dismiss (default short4)
--md-date-picker-scrim-colorModal scrim color
--md-date-picker-icon-colorTrailing / nav icon color
--md-date-picker-panel-widthModal / docked popup panel inline size
--md-date-picker-panel-max-block-sizeModal / docked popup panel max block size
--md-date-picker-docked-panel-widthDocked popup panel inline size (overrides panel-width)
--md-date-picker-year-inline-sizeYear grid cell inline size (modal)
--md-date-picker-year-block-sizeYear grid cell block size (modal)
--md-date-picker-year-grid-gapYear grid row/column gap (modal)
--md-date-picker-year-grid-padding-inlineYear grid inline padding (modal)
--md-date-picker-year-grid-viewport-block-sizeYear grid scroll viewport block size (modal)
--md-date-picker-day-outside-color
--md-date-picker-icon-size

CSS Shadow Parts

Style internal elements through shadow DOM with ::part():

PartDescription
menu-caret
year-grid-wrapAnimated wrapper for modal year grid
month-menuDocked inline baseline md-menu (month selection)
year-menuDocked inline baseline md-menu (year selection)
entry-inputIn-dialog outlined md-text-field (single modal input)
fieldText-field container (trigger)
clear-button
calendar-buttonTrailing calendar toggle button
month-toggleMonth-year menu button (opens year grid; modal; same as menu-button)
navMonth label + prev/next nav row (also docked nav row)
month-menu-buttonDocked month dropdown menu button ("Aug ▾")
year-menu-buttonDocked year dropdown menu button ("2025 ▾")
selection-dividerDivider between docked nav and month/year menu
month-menu-wrap
year-menu-wrap
calendarCalendar grid wrapper
weekdaysWeekday header row
weekdayA single weekday column header
gridDay-cell grid
week
year-grid-scroll year-grid-viewport
year-gridYear-selection grid (modal)
year-row
entryIn-dialog text-entry region
headerModal header region
supportingHeader supporting text
headlineLarge selected-date headline
mode-toggleCalendar / text-input switch button
actionsCancel / OK action row
cancel-buttonCancel action
ok-buttonConfirm action
bodyCalendar / year / input body region
docked-below-nav
panelDialog / docked popup surface
modalModal wrapper (scrim + panel)
scrimModal scrim overlay
month-menu-viewport
year-menu-viewport

md-time-picker · md-text-field · md-select · md-menu · md-dialog · md-icon-button

For AI Agents — md-date-picker

Two artefacts to give your AI agent so it generates correct UI with this component. The per-component spec answers "how do I use this exact tag?". The main-llm spec answers "which tag should I pick in the first place?".

Per-component

md-date-picker spec card

Identity · when to use / when NOT · decision cues · behavioural contract · do/don't · anti-patterns · full API. Paste into your agent when you're implementing with this component.

Main-LLM spec

AWC UI Operator's Manual

System-prompt preamble · decision matrix · token reference · page recipes · anti-patterns. Paste into the system prompt at the start of a piece of work.

Open spec

md-date-picker readme.md

# md-date-picker

<!-- llm:meta
tag: md-date-picker
category: pickers
status: md3-mapped
m3-guidelines: https://m3.material.io/components/date-pickers/guidelines
form-associated: true
depends-on: md-text-field, md-icon-button, md-tooltip, md-button, md-menu, md-menu-item
used-by: none
-->

**Choose a date, by calendar or by typing.** Modal, modal-input, or docked,
with locale-aware formatting, min/max bounds, a custom disabled-date predicate,
and full constraint validation.

> Setup, theming, density and i18n are configured once for the whole library —
> see [`main-llm.md`](../../../../../main-llm.md), the library-wide specification shipped
> alongside these manuals.

---

## When to use

- Any **date** entry: booking, due date, filter bound, date of birth.
- Where the calendar context matters (weekday, nearby dates, month shape).
- Where typing is faster for the user (`modal-input` gives both).

## When NOT to use

| Situation | Use instead |
|---|---|
| A time of day | `md-time-picker` |
| A date range you want side by side | Two pickers with `min`/`max` wired |
| A far-past date like birth year | `md-select` for year, or a typed field |
| A month or year only | `md-select` |
| A relative choice (Today / Last 7 days) | `md-chip` / `md-select` presets |
| A free-text date string | `md-text-field` with validation |

## Decision cues

| Need | Setting |
|---|---|
| Calendar + text entry (recommended default) | `variant="modal-input"` (default) |
| Calendar only, in a modal | `variant="modal"` |
| Inline dropdown calendar anchored to the field | `variant="docked"` |
| Bound the range | `min` / `max` (ISO strings) |
| Disable arbitrary dates (weekends, holidays) | `isDateDisabled` (JS property) |
| Locale formatting | `locale` |
| Week starts on a given day | `first-day-of-week` (0=Sunday; `-1` = locale default) |
| Force a separator regardless of locale | `date-separator` |
| Allow clearing | `clearable` |
| Reserve space for the message line | `reserve-supporting-space` |
| One-click picking, no confirm step | `commit-on-select` |
| Keep a docked panel open on click-away | `outside-click-dismissible="false"` |
| Keep a modal open on scrim click | `scrim-dismissible="false"` |
| Filled trigger field instead of outlined | `field-variant="filled"` |

## API contract

```html
<md-date-picker
  variant="modal|modal-input|docked"   <!-- default: modal-input -->
  field-variant="outlined|filled"      <!-- default: outlined -->
  label="Date"
  value="2026-08-12"
  name="due"
  min="2026-01-01"
  max="2026-12-31"
  placeholder=""                       <!-- modal typed-entry field only -->
  locale="en-GB"
  first-day-of-week="1"                <!-- default: -1 (derive from locale) -->
  date-separator="/"                   <!-- default: "" (locale default) -->
  supporting-text=""
  error
  error-text=""
  reserve-supporting-space
  required
  disabled
  clearable
  open
  scrim-dismissible                    <!-- default: true -->
  outside-click-dismissible            <!-- default: true -->
  commit-on-select                     <!-- default: false -->
  calendar-icon="calendar_today"
  density="-1|-2|-3|-4"                <!-- omit for the default rung -->
></md-date-picker>
```

All user-facing strings are props, each defaulting to English:

```html
<md-date-picker
  headline="Select date"
  cancel-label="Cancel"
  ok-label="OK"
  select-date-label="Select date"
  enter-dates-label="Enter dates"
  invalid-date-label="Invalid date"
  value-missing-label="Please choose a date."
  clear-label="Clear date"
  previous-month-label="Previous month"
  next-month-label="Next month"
  previous-year-label="Previous year"
  next-year-label="Next year"
  choose-month-label="Choose month"
  choose-year-label="Choose year"
  choose-month-year-label="Choose a different month and year"
  choose-month-and-year-label="Choose month and year"
  toggle-calendar-label="Switch to calendar input"
  toggle-text-label="Switch to text input"
  open-calendar-label="Open calendar"
  close-calendar-label="Close calendar"
  year-grid-label="Year"
></md-date-picker>
```

`isDateDisabled` has no attribute — set it as a JS property:

```js
picker.isDateDisabled = (d) => d.getDay() === 0 || d.getDay() === 6;  // no weekends
```

**Events** — `mdChange` (committed), `mdSelected` (a day was picked),
`mdInput` (every keystroke), `mdOpen` / `mdClose`, `mdCancel`, `mdViewChange`,
`mdModeChange`, `mdMenuOpen` / `mdMenuSelect`, and `mdValidityChange`
(`bubbles: true`, **`composed: false`** — listen on the element or a light-DOM
ancestor, never across a shadow boundary).

**Methods** — `show()`, `close()`, `clear()`, `focusInput()`, `getValidity()`,
`checkValidity()`, `reportValidity()`, `setCustomValidity(message)`. All are
async; `await` them.

**Slots** — `leading-icon` (leading icon on the trigger field),
`calendar-icon` (replaces the trailing toggle glyph; makes the `calendar-icon`
prop inert), `header` (replaces the whole modal header), `actions` (replaces
the Cancel / OK row).

**Parts** — this hand-written list is the complete one. The generated
**Shadow Parts** table further down is produced by static analysis and is
missing every part whose name is computed at render time (the `day` and `year`
cell families, the menu options, the docked nav chevrons) — style from this
list, not from that table.

- Trigger field: `field`, `clear-button`, `calendar-button`
- Surface: `modal`, `scrim`, `panel`, `body`
- Header: `header`, `supporting`, `headline`, `mode-toggle`
- Modal nav: `nav`, `month-toggle`, `prev-button`, `next-button`
- Docked nav: `month-menu-button`, `year-menu-button`, `menu-caret`,
  `selection-divider`, `docked-below-nav`, and per chevron
  `prev-month-button` / `next-month-button` / `prev-year-button` /
  `next-year-button`, each inside a `<side>-<scope>-chevron-slot`
  (`prev-month-chevron-slot`, …) alongside a `<side>-<scope>-chevron-spacer`
  that holds the width while the button is hidden
- Docked menus: `month-menu-wrap`, `month-menu`, `year-menu-wrap`, `year-menu`,
  and the options themselves — `month-option` (plus
  `month-option-selected` on the current month) and `year-option` (plus
  `year-option-selected`)
- Calendar: `calendar`, `weekdays`, `weekday`, `grid`, `week`, and `day` — the
  day cell always carries `day` plus one or more of `day-selected` /
  `day-today` / `day-disabled` / `day-outside` / `day-unselected`
- Year grid: `year-grid-wrap`, `year-grid-scroll`, `year-grid-viewport`,
  `year-grid`, `year-row`, and `year` — the year cell always carries `year`
  plus exactly one of `year-selected` / `year-disabled` / `year-unselected`
- Typed entry: `entry`, `entry-input`
- Action row: `actions`, `cancel-button`, `ok-button`

```css
/* Emphasise the selected year in the year grid and the selected docked
   menu rows — hooks that only exist under the names listed above. */
md-date-picker::part(year-selected),
md-date-picker::part(year-option-selected),
md-date-picker::part(month-option-selected) {
  font-weight: 700;
}
md-date-picker::part(day-today) {
  outline: 1px solid var(--md-sys-color-primary);
}
```

### Behavioral contract worth knowing

- **`value` is an ISO date string** (`YYYY-MM-DD`), not a `Date`. `min` and
  `max` are the same. Only display formatting follows `locale` /
  `date-separator`; `value`, `mdChange.detail.value` and
  `mdSelected.detail.value` are always ISO.
- **A day click stages; OK commits.** Every variant — docked included — renders
  the same Cancel / OK row, so a click emits `mdSelected` and only OK emits
  `mdChange`. Set `commit-on-select` to collapse the two: the click stages and
  commits in one go and the built-in action row is not rendered at all. **Typed
  entry keeps its OK button either way**, because typing has no click to
  collapse. A slotted `actions` row is always rendered, `commit-on-select` or not.
- **Cancel, `Escape`, the modal scrim and a click outside a docked panel all
  emit `mdCancel`** and discard staging. `close()` and setting `open = false`
  do **not** emit `mdCancel` — they only animate out and emit `mdClose`.
  `mdClose` fires for every close, cancel or commit alike.
- **Click-away is opt-out per variant**, because the two catch the click
  differently: the modal has a scrim (`scrim-dismissible`), the docked panel has
  none and is dismissed from a document `pointerdown` listener
  (`outside-click-dismissible`). Both default to `true`; turning either off
  leaves `Escape`, the trigger toggle and Cancel / OK working.
- `isDateDisabled` is a **function property** — there is no attribute. It runs
  once per rendered day cell, so keep it cheap and pure.
- `first-day-of-week="-1"` means "use the locale's default"; `0` is Sunday.
- **`placeholder` applies only to the modal typed-entry field**, not to the
  trigger field. When it is empty the entry field falls back to the locale
  format hint (e.g. `MM/DD/YYYY`).
- **`supporting-text` has a fallback**: when it is empty the trigger field shows
  the locale date-format hint instead. Pass a space to blank it.
- `error` and `error-text` are **mutable** — the component sets them itself when
  a typed date cannot be parsed or is out of range (using `invalid-date-label`),
  and clears them on a successful parse or commit.
- The clear button only renders when `clearable` is set, `value` is non-empty
  and the picker is not `disabled`. It calls `clear()`, which resets `value` and
  emits `mdChange` with an empty value.
- **Form-associated via `ElementInternals`.** Give it a `name` and it submits
  the ISO string with the surrounding `<form>`; `required` blocks submit with
  `value-missing-label` as the message; form reset restores the `value` the
  element had at first render. Do not add a hidden input.
- `mdValidityChange` never fires on mount — the first evaluation only primes the
  baseline — and never re-fires for a state that did not change.
- **Nested `mdOpen` / `mdClose` from the internal tooltips and menus are
  swallowed** at the host, so a listener on the picker only ever hears the
  picker's own open/close.
- Day cells are `md-button` elements with `role-override="gridcell"`. Style them
  through the `day*` parts, not as buttons.
- The docked panel flips above the anchor and clamps its height when it would
  leave the viewport, and re-positions on window scroll and resize.

---

## Do / Don't

Sourced from [M3 · Date pickers · Guidelines](https://m3.material.io/components/date-pickers/guidelines).

| ✅ Do | ❌ Don't |
|---|---|
| Offer text entry alongside the calendar for known dates | Don't force calendar navigation for a date the user can type |
| Use the calendar when surrounding dates matter (weekday, availability) | Don't use a calendar to pick a birth year |
| Bound the selectable range with `min`/`max` | Don't let users pick impossible dates and fail on submit |
| Disable unavailable dates with `isDateDisabled` | Don't accept a date and reject it afterwards |
| Show validation inline with `error-text` | Don't rely on the browser's validation balloon |
| Set `locale` so format and week start match the user | Don't assume `en-US` ordering for every audience |
| Localize every label prop | Don't ship a half-translated dialog |
| Keep the field label short | Don't wrap the field label |
| Use `commit-on-select` on a low-stakes, reversible field | Don't drop the confirm step on a form the user submits once |
| Turn click-away off when a stray click would discard real input | Don't turn it off "for safety" — a popup you can't click away from feels stuck |

---

## Patterns

```html
<!-- Bounded, validated, weekdays only -->
<form id="booking">
  <md-date-picker
    id="due" label="Due date" name="due" required
    min="2026-01-01" max="2026-12-31" locale="en-GB" first-day-of-week="1"
    clearable reserve-supporting-space
    supporting-text="Weekdays only"
  ></md-date-picker>
  <md-button type="submit">Book</md-button>
</form>

<script type="module">
  const p = document.getElementById('due');
  p.isDateDisabled = (d) => d.getDay() === 0 || d.getDay() === 6;

  p.addEventListener('mdChange', (e) => console.log('committed', e.detail.value));
  p.addEventListener('mdCancel', () => console.log('dismissed without committing'));

  document.getElementById('booking').addEventListener('submit', (e) => {
    e.preventDefault();
    console.log(new FormData(e.currentTarget).get('due'));  // "2026-08-12"
  });
</script>
```

```html
<!-- Docked inline calendar -->
<md-date-picker variant="docked" label="Start"></md-date-picker>
```

```html
<!-- A range, as two coupled pickers -->
<md-date-picker id="from" label="From"></md-date-picker>
<md-date-picker id="to"   label="To"></md-date-picker>

<script type="module">
  const from = document.getElementById('from');
  const to = document.getElementById('to');
  from.addEventListener('mdChange', () => { to.min = from.value; });
  to.addEventListener('mdChange', () => { from.max = to.value; });
</script>
```

```html
<!-- One-click picking: no Cancel / OK row, the day click commits -->
<md-date-picker label="Filter from" commit-on-select></md-date-picker>

<!-- Docked panel that survives a click on the page behind it -->
<md-date-picker
  variant="docked" label="Start"
  outside-click-dismissible="false"
></md-date-picker>

<!-- Bare modal, opened from your own trigger -->
<md-button id="pick">Pick a date</md-button>
<md-date-picker id="modal" variant="modal"></md-date-picker>

<script type="module">
  const modal = document.getElementById('modal');
  document.getElementById('pick').addEventListener('mdClick', () => modal.show());
</script>
```

```html
<!-- Localized (abbreviated — translate all label props) -->
<md-date-picker
  locale="fr-FR" label="Date" headline="Sélectionner une date"
  cancel-label="Annuler" ok-label="OK"
  select-date-label="Sélectionner une date" enter-dates-label="Saisir les dates"
  previous-month-label="Mois précédent" next-month-label="Mois suivant"
  previous-year-label="Année précédente" next-year-label="Année suivante"
  choose-month-label="Choisir le mois" choose-year-label="Choisir l'année"
  open-calendar-label="Ouvrir le calendrier" close-calendar-label="Fermer le calendrier"
  clear-label="Effacer la date" invalid-date-label="Date invalide"
  year-grid-label="Année"
  value-missing-label="Veuillez choisir une date."
></md-date-picker>
```

## Anti-patterns

| ❌ Wrong | ✅ Right | Why |
|---|---|---|
| `picker.value = new Date()` | Assign an ISO `YYYY-MM-DD` string | `value` is a string, not a `Date`. |
| `is-date-disabled="fn"` as an attribute | `picker.isDateDisabled = fn` in JS | It is a function property with no attribute. |
| An expensive `isDateDisabled` | Keep it cheap and pure | It runs once per rendered day cell. |
| Wiring `mdInput`, `mdSelected` and `mdChange` to the same save | Use `mdChange` | You would save three times per pick. |
| Translating only `headline` and the buttons | Translate every label prop | Everything else stays English. |
| `first-day-of-week="0"` meaning "locale default" | `first-day-of-week="-1"` | `0` is Sunday; `-1` is the locale default. |
| A calendar for date of birth | Typed entry or a year select | M3 usage guidance. |
| Accepting a date then rejecting it server-side | `min` / `max` + `isDateDisabled` | Prevent, don't punish. |
| Listening for `mdValidityChange` on a shadow ancestor | Listen on the element itself | It is `composed: false`. |
| Removing the `outside-click-dismissible` attribute to turn it off | `outside-click-dismissible="false"` | It defaults to `true`, so an absent attribute means ON. |
| `commit-on-select` plus your own OK button in `actions` | Pick one | The click already committed; the button confirms nothing. |
| Treating `mdCancel` as "the user pressed Cancel" | Treat it as "dismissed without committing" | Esc, the scrim and an outside click all emit it. |
| Expecting `mdCancel` after `picker.close()` | Listen for `mdClose` | Programmatic close is not a user cancel. |
| A hidden `<input>` to submit the date | Give the picker a `name` | It is already form-associated. |
| Setting `placeholder` to hint the trigger field | Use `supporting-text` | `placeholder` only reaches the modal typed-entry field. |

## Accessibility, RTL, density, i18n

**Accessibility** — the calendar is a grid: day cells expose `gridcell`
semantics with `aria-selected`, `aria-current="date"` on today and a full
localized date as the accessible name; arrow keys move by day and week, Page
Up / Page Down by month, Shift + Page Up / Page Down by year. Disabled dates are
exposed as `disabled`, not merely greyed. The trigger toggle carries
`aria-haspopup="dialog"` and a live `aria-expanded`. Modal variants trap Tab
inside the panel and restore focus to the previously focused element on close.
`label` names the field and errors surface in the supporting line, so
`reserve-supporting-space` avoids the layout jump when one appears. The
navigation buttons are named entirely by the label props — untranslated, they are
the weakest part of a localized picker.

**RTL** — the calendar grid, the navigation chevrons and the field mirror under
`dir="rtl"`, and the docked panel's start/end alignment flips with it. Set
`locale` to a matching RTL locale so weekday order and formatting agree.

**Density** — `density="-1…-4"` locally overrides the inherited `data-density`
rung; only those four rungs exist, and omitting the attribute is the uncompacted
default. `density="0"` does **not** opt a picker out of an ancestor's rung — for
that, set `style="--md-sys-density-scale: 0"`. Day cells taper from 40px to
28px, so re-check tap targets at the deep rungs.

**i18n** — set `locale` (it drives `Intl` month and weekday names, field
formatting and the default week start) **and** translate every label prop listed
in the API contract. `date-separator` pins the separator when the locale default
is not what you want; it changes display and parsing only, never `value`.

## Related components

`md-time-picker` · `md-text-field` · `md-select` · `md-dialog` · `md-menu` ·
`md-icon-button` · `md-button` · `md-tooltip`

## Theming

| Custom property | Purpose | Default |
|---|---|---|
| `--md-date-picker-container-color` | Dialog / popup body background | `--md-sys-color-surface` |
| `--md-date-picker-container-shape` | Dialog / popup corner radius | `--md-sys-shape-corner-extra-large` (28px) |
| `--md-date-picker-header-color` | Modal header background | `--md-sys-color-surface-container-high` |
| `--md-date-picker-headline-color` | Modal headline (selected date) color | `--md-sys-color-on-surface` |
| `--md-date-picker-supporting-color` | Header supporting-text color | `--md-sys-color-on-surface-variant` |
| `--md-date-picker-field-color` | Trigger field resting outline | `--md-sys-color-outline` |
| `--md-date-picker-field-focus-color` | Trigger field focused outline / label | `--md-sys-color-primary` |
| `--md-date-picker-field-text-color` | Trigger field input text | `--md-sys-color-on-surface` |
| `--md-date-picker-field-container-color` | Filled-field container background | `--md-sys-color-surface-container-highest` |
| `--md-date-picker-field-container-shape` | Trigger field corner radius | `--md-sys-shape-corner-extra-small` (4px) |
| `--md-date-picker-field-filled-pill` | Filled field: `1` for a capsule shape | `0` |
| `--md-date-picker-field-supporting-color` | Supporting text below the field | `--md-sys-color-on-surface-variant` |
| `--md-date-picker-label-color` | Floating label color | `--md-sys-color-on-surface-variant` |
| `--md-date-picker-weekday-color` | Weekday column-header color | `--md-sys-color-on-surface` |
| `--md-date-picker-day-color` | Day cell text color | `--md-sys-color-on-surface` |
| `--md-date-picker-day-outside-color` | Adjacent-month day cell text | `--md-sys-color-on-surface-variant` |
| `--md-date-picker-day-selected-color` | Selected day text | `--md-sys-color-on-primary` |
| `--md-date-picker-day-selected-bg` | Selected day container | `--md-sys-color-primary` |
| `--md-date-picker-day-selected-shape` | Selected day corner radius | Half the day size (circle) |
| `--md-date-picker-day-today-shape` | Today ring corner radius | Half the day size (circle) |
| `--md-date-picker-today-outline-color` | Today's outline ring | `--md-sys-color-primary` |
| `--md-date-picker-action-color` | Cancel / OK label color | `--md-sys-color-primary` |
| `--md-date-picker-menu-color` | Docked month/year menu surface | `--md-sys-color-surface-container-high` |
| `--md-date-picker-scrim-color` | Modal scrim | `--md-sys-color-scrim` |
| `--md-date-picker-icon-color` | Trailing / nav icon color | `--md-sys-color-on-surface-variant` |
| `--md-date-picker-icon-size` | Icon box | 24px, tapering 1px per density rung (18px floor) |
| `--md-date-picker-panel-width` | Modal / docked panel inline size | 380px, tapering 20px per rung (288px floor) |
| `--md-date-picker-panel-max-block-size` | Panel max block size | `524px` |
| `--md-date-picker-docked-panel-width` | Docked panel inline size (wins over `panel-width`) | 360px, tapering 10px per rung |
| `--md-date-picker-year-inline-size` | Year cell inline size (modal) | 72px, tapering 6px per rung (48px floor) |
| `--md-date-picker-year-block-size` | Year cell block size (modal) | 36px, tapering 3px per rung (24px floor) |
| `--md-date-picker-year-grid-gap` | Year grid row/column gap | 12px, tapering 2px per rung (4px floor) |
| `--md-date-picker-year-grid-padding-inline` | Year grid inline padding | `30px` |
| `--md-date-picker-year-grid-viewport-block-size` | Year grid scroll viewport height | 288px, tapering 24px per rung (180px floor) |
| `--md-date-picker-selection-bloom-duration` | Docked month/year menu bloom-in | `--md-sys-motion-duration-long1` (450ms) |
| `--md-date-picker-selection-bloom-easing` | Docked menu bloom-in easing | `--md-sys-motion-easing-emphasized-decelerate` |
| `--md-date-picker-selection-bloom-out-duration` | Docked menu bloom-out on pick | `--md-sys-motion-duration-short4` (200ms) |
| `--md-date-picker-calendar-bloom-duration` | Day grid bloom-in after a pick | `--md-sys-motion-duration-medium2` (300ms) |
| `--md-date-picker-grid-slide-duration` | Day grid month/year slide | `--md-sys-motion-duration-medium2` (300ms) |
| `--md-date-picker-panel-bloom-out-duration` | Panel bloom-out on dismiss | `--md-sys-motion-duration-short4` (200ms) |

**CSS parts** — see the **Parts** list in the API contract; the full generated
list is in **Shadow Parts** below.

```css
md-date-picker.brand {
  --md-date-picker-day-selected-bg: var(--md-sys-color-tertiary);
  --md-date-picker-day-selected-color: var(--md-sys-color-on-tertiary);
  --md-date-picker-container-shape: 16px;
}
```

<!-- Auto Generated Below -->


## Overview

`md-date-picker` — a Material Design 3 date picker.

Variants:
- `modal-input` *(default)* — outlined text field + modal calendar dialog.
- `modal` — bare modal dialog surfaced via `show()` / `open`.
- `docked` — outlined text field + dropdown calendar anchored to the field.

The modal dialog can flip to a typed-entry view (date input) via the
header toggle, satisfying the MD3 "modal date input" pattern.

## Properties

| Property                  | Attribute                     | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                    | Type                                     | Default                               |
| ------------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ------------------------------------- |
| `calendarIcon`            | `calendar-icon`               | Material Symbols name for the trailing calendar toggle on the field. Ignored when a `calendar-icon` slot is provided.                                                                                                                                                                                                                                                                                                                                          | `string`                                 | `'calendar_today'`                    |
| `cancelLabel`             | `cancel-label`                | Label of the cancel button in the modal action row.                                                                                                                                                                                                                                                                                                                                                                                                            | `string`                                 | `'Cancel'`                            |
| `chooseMonthAndYearLabel` | `choose-month-and-year-label` | Tooltip suffix on the modal month/year nav button.                                                                                                                                                                                                                                                                                                                                                                                                             | `string`                                 | `'Choose month and year'`             |
| `chooseMonthLabel`        | `choose-month-label`          | Accessible label for the docked month menu button.                                                                                                                                                                                                                                                                                                                                                                                                             | `string`                                 | `'Choose month'`                      |
| `chooseMonthYearLabel`    | `choose-month-year-label`     | Accessible label for the modal month/year toggle button.                                                                                                                                                                                                                                                                                                                                                                                                       | `string`                                 | `'Choose a different month and year'` |
| `chooseYearLabel`         | `choose-year-label`           | Accessible label for the docked year menu button.                                                                                                                                                                                                                                                                                                                                                                                                              | `string`                                 | `'Choose year'`                       |
| `clearLabel`              | `clear-label`                 | Accessible label for the clear button.                                                                                                                                                                                                                                                                                                                                                                                                                         | `string`                                 | `'Clear date'`                        |
| `clearable`               | `clearable`                   | Show a clear ("×") button on the field trigger while a date is selected, which resets the value (same as calling `clear()`). Applies to the `modal-input` and `docked` variants.                                                                                                                                                                                                                                                                               | `boolean`                                | `false`                               |
| `closeCalendarLabel`      | `close-calendar-label`        | Trigger calendar icon label when the panel is open.                                                                                                                                                                                                                                                                                                                                                                                                            | `string`                                 | `'Close calendar'`                    |
| `commitOnSelect`          | `commit-on-select`            | Commit a day the moment it is clicked, instead of staging it behind the Cancel / OK row. The built-in action row is then not rendered — there is nothing left for it to confirm. Input mode keeps its OK button, since typing has no click to collapse. A slotted `actions` row is always honoured.                                                                                                                                                             | `boolean`                                | `false`                               |
| `dateSeparator`           | `date-separator`              | Character between day, month, and year in the text field (display and parsing). Leave empty to use the locale default (e.g. `/` for `en-US`, `.` for `de-DE`). The `value` prop and `mdChange` / `mdSelected` events always use ISO `YYYY-MM-DD` regardless of this setting.                                                                                                                                                                                   | `string`                                 | `''`                                  |
| `density`                 | `density`                     | Local density rung. Drives the same `--md-sys-density-scale` signal that a global `data-density` ancestor sets, so a local value simply overrides the inherited one. 0 = default, -4 = ultra-compact.                                                                                                                                                                                                                                                          | `-1 \| -2 \| -3 \| -4 \| 0`              | `0`                                   |
| `disabled`                | `disabled`                    | Disabled — blocks all interaction and removes from tab order.                                                                                                                                                                                                                                                                                                                                                                                                  | `boolean`                                | `false`                               |
| `enterDatesLabel`         | `enter-dates-label`           | Large headline shown in typed-entry (input) mode.                                                                                                                                                                                                                                                                                                                                                                                                              | `string`                                 | `'Enter dates'`                       |
| `error`                   | `error`                       | Error state — paints the field with the error color and shows `error-text`.                                                                                                                                                                                                                                                                                                                                                                                    | `boolean`                                | `false`                               |
| `errorText`               | `error-text`                  | Error message shown below the field when `error` is true.                                                                                                                                                                                                                                                                                                                                                                                                      | `string`                                 | `''`                                  |
| `fieldVariant`            | `field-variant`               | Visual variant of the trigger `md-text-field` (`modal-input` and `docked`). Use `filled` with `--md-date-picker-field-container-color` for branded fills.                                                                                                                                                                                                                                                                                                      | `"filled" \| "outlined"`                 | `'outlined'`                          |
| `firstDayOfWeek`          | `first-day-of-week`           | First day of the week (0 = Sunday … 6 = Saturday). `-1` derives it from the locale.                                                                                                                                                                                                                                                                                                                                                                            | `number`                                 | `-1`                                  |
| `headline`                | `headline`                    | Headline / supporting text shown at the top of the modal header.                                                                                                                                                                                                                                                                                                                                                                                               | `string`                                 | `'Select date'`                       |
| `invalidDateLabel`        | `invalid-date-label`          | Default error when a typed date cannot be parsed or is out of range.                                                                                                                                                                                                                                                                                                                                                                                           | `string`                                 | `'Invalid date'`                      |
| `isDateDisabled`          | --                            | Predicate that disables individual dates (return `true` to disable).                                                                                                                                                                                                                                                                                                                                                                                           | `((date: Date) => boolean) \| undefined` | `undefined`                           |
| `label`                   | `label`                       | Floating label / accessible name for the field.                                                                                                                                                                                                                                                                                                                                                                                                                | `string`                                 | `'Date'`                              |
| `locale`                  | `locale`                      | BCP-47 locale for month/weekday names and date formatting.                                                                                                                                                                                                                                                                                                                                                                                                     | `string`                                 | `''`                                  |
| `max`                     | `max`                         | Latest selectable date, inclusive, as ISO `YYYY-MM-DD`.                                                                                                                                                                                                                                                                                                                                                                                                        | `string`                                 | `''`                                  |
| `min`                     | `min`                         | Earliest selectable date, inclusive, as ISO `YYYY-MM-DD`.                                                                                                                                                                                                                                                                                                                                                                                                      | `string`                                 | `''`                                  |
| `name`                    | `name`                        | Form-association name attribute.                                                                                                                                                                                                                                                                                                                                                                                                                               | `string`                                 | `''`                                  |
| `nextMonthLabel`          | `next-month-label`            | Accessible label for the "next month" nav chevron.                                                                                                                                                                                                                                                                                                                                                                                                             | `string`                                 | `'Next month'`                        |
| `nextYearLabel`           | `next-year-label`             | Accessible label for the "next year" nav chevron.                                                                                                                                                                                                                                                                                                                                                                                                              | `string`                                 | `'Next year'`                         |
| `okLabel`                 | `ok-label`                    | Label of the confirm button in the modal action row.                                                                                                                                                                                                                                                                                                                                                                                                           | `string`                                 | `'OK'`                                |
| `open`                    | `open`                        | Whether the modal / docked panel is open. Two-way bindable.                                                                                                                                                                                                                                                                                                                                                                                                    | `boolean`                                | `false`                               |
| `openCalendarLabel`       | `open-calendar-label`         | Trigger calendar icon label when the panel is closed.                                                                                                                                                                                                                                                                                                                                                                                                          | `string`                                 | `'Open calendar'`                     |
| `outsideClickDismissible` | `outside-click-dismissible`   | Clicking outside a **docked** panel dismisses it (the modal equivalent is `scrim-dismissible`). Set `false` to keep the panel open until Cancel / OK / Esc. Dismissing this way is a cancel: it emits `mdCancel` and discards staging.                                                                                                                                                                                                                          | `boolean`                                | `true`                                |
| `placeholder`             | `placeholder`                 | Placeholder shown inside the text-field input.                                                                                                                                                                                                                                                                                                                                                                                                                 | `string`                                 | `''`                                  |
| `previousMonthLabel`      | `previous-month-label`        | Accessible label for the "previous month" nav chevron.                                                                                                                                                                                                                                                                                                                                                                                                         | `string`                                 | `'Previous month'`                    |
| `previousYearLabel`       | `previous-year-label`         | Accessible label for the "previous year" nav chevron.                                                                                                                                                                                                                                                                                                                                                                                                          | `string`                                 | `'Previous year'`                     |
| `required`                | `required`                    | Marks the field as required (for form-validation contexts).                                                                                                                                                                                                                                                                                                                                                                                                    | `boolean`                                | `false`                               |
| `reserveSupportingSpace`  | `reserve-supporting-space`    | Always occupy the supporting-text line, even when there is no message, so a validation error does not push the content below it down. Forwarded to the embedded md-text-field. See that component for why it is opt-in.                                                                                                                                                                                                                                        | `boolean`                                | `false`                               |
| `scrimDismissible`        | `scrim-dismissible`           | Clicking the modal scrim closes the picker when true.                                                                                                                                                                                                                                                                                                                                                                                                          | `boolean`                                | `true`                                |
| `selectDateLabel`         | `select-date-label`           | Large headline when no date is staged in calendar mode.                                                                                                                                                                                                                                                                                                                                                                                                        | `string`                                 | `'Select date'`                       |
| `supportingText`          | `supporting-text`             | Helper text shown below the field. Hidden while an error is displayed.                                                                                                                                                                                                                                                                                                                                                                                         | `string`                                 | `''`                                  |
| `toggleCalendarLabel`     | `toggle-calendar-label`       | Mode-toggle label when in typed-entry mode (switch to calendar).                                                                                                                                                                                                                                                                                                                                                                                               | `string`                                 | `'Switch to calendar input'`          |
| `toggleTextLabel`         | `toggle-text-label`           | Mode-toggle label when in calendar mode (switch to text).                                                                                                                                                                                                                                                                                                                                                                                                      | `string`                                 | `'Switch to text input'`              |
| `value`                   | `value`                       | Selected date as ISO `YYYY-MM-DD`. Two-way bindable.                                                                                                                                                                                                                                                                                                                                                                                                           | `string`                                 | `''`                                  |
| `valueMissingLabel`       | `value-missing-label`         | Localized constraint-validation message shown when `required` is unmet.  A prop rather than a hardcoded string, matching md-time-picker's `value-missing-label`: components stay i18n-engine-agnostic and the consumer localizes through its own dictionary. Native inputs get their message from the browser's locale for free; a form-associated custom element supplies its own, so leaving this hardcoded would ship an English-only form to every locale. | `string`                                 | `'Please choose a date.'`             |
| `variant`                 | `variant`                     | Presentation variant.                                                                                                                                                                                                                                                                                                                                                                                                                                          | `"docked" \| "modal" \| "modal-input"`   | `'modal-input'`                       |
| `yearGridLabel`           | `year-grid-label`             | Accessible label for the modal year-selection grid.                                                                                                                                                                                                                                                                                                                                                                                                            | `string`                                 | `'Year'`                              |


## Events

| Event              | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | Type                                                                                          |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
| `mdCancel`         | Emitted when the user dismisses without committing (scrim, Cancel, Esc).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | `CustomEvent<void>`                                                                           |
| `mdChange`         | Emitted when the user commits a selection.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | `CustomEvent<MdDatePickerChangeDetail>`                                                       |
| `mdClose`          | Emitted when the picker closes for any reason.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | `CustomEvent<void>`                                                                           |
| `mdInput`          | Emitted on every keystroke in a text-entry field.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | `CustomEvent<{ value: string; }>`                                                             |
| `mdMenuOpen`       | Emitted when the user opens the month or year selection menu / grid.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | `CustomEvent<MdDatePickerMenuOpenDetail>`                                                     |
| `mdMenuSelect`     | Emitted when the user picks a month or year from a selection menu / grid.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | `CustomEvent<MdDatePickerMenuSelectDetail>`                                                   |
| `mdModeChange`     | Emitted when the user toggles between calendar and typed-entry views.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | `CustomEvent<MdDatePickerModeChangeDetail>`                                                   |
| `mdOpen`           | Emitted when the picker opens.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | `CustomEvent<void>`                                                                           |
| `mdSelected`       | Emitted when the user selects a day in the calendar (click or keyboard).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | `CustomEvent<MdDatePickerSelectedDetail>`                                                     |
| `mdValidityChange` | Fires when this control's validity CHANGES — never on every keystroke, and never for a re-publish that lands on the same state.  `composed: false` is deliberate. Composites like md-select embed an md-text-field, and a composed event escapes that inner shadow root, so a listener on md-select would receive the inner field's event as well as the host's — two events, different payloads, for one logical control. Keeping it uncomposed means each component reports only for itself, while `bubbles: true` still lets a <form> or app root hear every control. | `CustomEvent<{ valid: boolean; validationMessage: string; flags: Record<string, boolean>; }>` |
| `mdViewChange`     | Emitted when the displayed calendar month/year changes from user navigation.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `CustomEvent<MdDatePickerViewChangeDetail>`                                                   |


## Methods

### `checkValidity() => Promise<boolean>`

Constraint-validation API, matching md-text-field and the native contract.

#### Returns

Type: `Promise<boolean>`



### `clear() => Promise<void>`

Clear the current selection.

#### Returns

Type: `Promise<void>`



### `close() => Promise<void>`

Close the picker without committing.

#### Returns

Type: `Promise<void>`



### `focusInput() => Promise<void>`

Move keyboard focus to the field's text input (if present).

#### Returns

Type: `Promise<void>`



### `getValidity() => Promise<{ valid: boolean; validationMessage: string; flags: Record<string, boolean>; }>`

Current validity: boolean, message and flags. Mirrors md-text-field.

#### Returns

Type: `Promise<{ valid: boolean; validationMessage: string; flags: Record<string, boolean>; }>`



### `reportValidity() => Promise<boolean>`



#### Returns

Type: `Promise<boolean>`



### `setCustomValidity(message: string) => Promise<void>`



#### Parameters

| Name      | Type     | Description |
| --------- | -------- | ----------- |
| `message` | `string` |             |

#### Returns

Type: `Promise<void>`



### `show() => Promise<void>`

Open the picker.

#### Returns

Type: `Promise<void>`




## Shadow Parts

| Part                   | Description |
| ---------------------- | ----------- |
| `"actions"`            |             |
| `"body"`               |             |
| `"calendar"`           |             |
| `"calendar-button"`    |             |
| `"cancel-button"`      |             |
| `"clear-button"`       |             |
| `"docked-below-nav"`   |             |
| `"entry"`              |             |
| `"entry-input"`        |             |
| `"field"`              |             |
| `"grid"`               |             |
| `"header"`             |             |
| `"headline"`           |             |
| `"menu-caret"`         |             |
| `"modal"`              |             |
| `"mode-toggle"`        |             |
| `"month-menu"`         |             |
| `"month-menu-button"`  |             |
| `"month-menu-wrap"`    |             |
| `"month-toggle"`       |             |
| `"nav"`                |             |
| `"ok-button"`          |             |
| `"panel"`              |             |
| `"scrim"`              |             |
| `"selection-divider"`  |             |
| `"supporting"`         |             |
| `"week"`               |             |
| `"weekday"`            |             |
| `"weekdays"`           |             |
| `"year-grid"`          |             |
| `"year-grid-scroll"`   |             |
| `"year-grid-viewport"` |             |
| `"year-grid-wrap"`     |             |
| `"year-menu"`          |             |
| `"year-menu-button"`   |             |
| `"year-menu-wrap"`     |             |
| `"year-row"`           |             |


## Dependencies

### Depends on

- [md-text-field](../md-text-field)
- [md-icon-button](../md-icon-button)
- [md-tooltip](../md-tooltip)
- [md-button](../md-button)
- [md-menu](../md-menu)
- [md-menu-item](../md-menu-item)

### Graph
```mermaid
graph TD;
  md-date-picker --> md-text-field
  md-date-picker --> md-icon-button
  md-date-picker --> md-tooltip
  md-date-picker --> md-button
  md-date-picker --> md-menu
  md-date-picker --> md-menu-item
  md-icon-button --> md-ripple
  md-button --> md-ripple
  md-button --> md-loading-indicator
  md-menu-item --> md-ripple
  style md-date-picker fill:#f9f,stroke:#333,stroke-width:4px
```

----------------------------------------------

*Built with [StencilJS](https://stenciljs.com/)*

AWC UI Operator’s Manual

# main-llm.md — AWC UI build director

<!-- llm:meta
role: director
audience: llm
library: "@awc-ui/core"
component-count: 57
sub-component-count: 24
manual-count: 81
per-component-docs: ./packages/core/src/components/<tag>/readme.md
-->

**You are building a web app with AWC UI, a Material Design 3 web-component
library.** This file is the entry point and the only document you need before
writing UI code. Everything here is self-contained: tokens, recipes,
composition rules and the ship checklist. Per-component detail lives in
`./packages/core/src/components/<tag>/readme.md` — e.g.
[`md-button`](./packages/core/src/components/md-button/readme.md).

Your job, in order:

1. **Interview** the user (§1). Do not skip it and do not guess.
2. **Lock the configuration** their answers imply (§2) and bootstrap it (§3–§4).
3. **Route every UI need through the decision matrix** (§5) — never pick a
   component by name-similarity.
4. **Load the component's readme.md before writing a single line of its
   markup** (§6). The file is
   `./packages/core/src/components/<tag>/readme.md` — read it in full, do not
   skim it, and do not write the markup from memory of a similar library. Each
   one has a `When NOT to use`, a `Do / Don't` table sourced from
   [m3.material.io](https://m3.material.io), and an `Anti-patterns` table of
   mistakes models actually make. If you are about to use three components,
   load all three readmes first.
5. **Check what nests inside what** (§7) and start from a recipe (§8) rather
   than from a blank page.
6. **Apply the universal rules** (§9) — the API, content and accessibility
   rules every component is bound by, and the ones §10 checks you against.
7. **Run the ship checklist** (§10) before declaring done.

**Fail closed.** If you cannot satisfy a step — no component fits, a token
doesn't exist, an accessible name has nowhere to come from — say so and ask.
Do not invent an `md-*` tag, a prop, or a token. If it is not in this file or
in the component's manual, it does not exist.

---

## §1 — Interview the user

Ask these **one at a time**, in this order. Each answer closes off decisions
downstream, so don't batch them into a wall of questions. Skip a question only
if the user has already answered it unprompted.

If the user says "just pick sensible defaults", use the **bold** option and tell
them what you chose.

### 1.1 Scope and shape

1. **What is the app?** One or two sentences — domain, primary job, who uses it.
2. **What kind of surface is it?**
   - Internal tool / admin console / dashboard
   - Data-heavy CRUD application
   - Consumer-facing product
   - Marketing or content site
   - Mobile-first / PWA
3. **Which framework?** React · Angular · Vue · Svelte · **plain HTML** ·
   Next · Nuxt · SvelteKit · Astro
4. **Does it server-render?** (SSR/SSG, or **client-only SPA**)
5. **Roughly how many distinct screens**, and what are the top 3?

### 1.2 Look and feel

6. **Density** — how much information per screen?
   - `0` — **default**, comfortable, touch-friendly
   - `-1` / `-2` — compact; typical for admin consoles
   - `-3` / `-4` — ultra-compact; dense data tables, trading/ops screens
7. **Theme** — light only, dark only, or **both with a user toggle**?
   Does it follow the OS preference?
8. **Brand color** — a seed/primary color, or **stock MD3 palette**?
9. **Expressive motion** — keep **ripple and shape-morph on** (default), or turn
   them off for a flatter, more utilitarian feel?
10. **Shape language** — **rounded** (MD3 default) or squared?

### 1.3 Internationalization

11. **How many locales**, and which?
12. **Any RTL locales** (Arabic, Hebrew, Farsi, Urdu)? — this changes layout
    verification and directional-icon handling.
13. **Which i18n engine?** (i18next, vue-i18n, ngx-translate, Paraglide, custom)
    Components are engine-agnostic — you localize in the consumer layer.
14. **Locale-formatted values** — dates, numbers, currency? Which locale drives
    `Intl`?

### 1.4 Data and forms

15. **Is there significant tabular data?** How many rows, and is it
    server-paged? (Drives how you page `md-table` — it holds the state, you
    supply each page of rows.)
16. **Are there charts?** Which questions should they answer?
17. **How heavy are the forms?** Validation rules, async validation, multi-step?
18. **Rich text editing anywhere?** — ⚠️ **AWC UI has no RTE component.** If yes,
    you must integrate a third-party editor (TipTap, Lexical, Quill) and style
    it to the MD3 tokens yourself. Confirm this with the user explicitly.

### 1.5 Constraints

19. **Accessibility target** — **WCAG 2.1 AA** (what the library is tested to),
    or stricter?
20. **Browser/device support floor?**
21. **Anything already decided** you must not change — existing design system,
    router, state library, CSS approach?

---

## §2 — Map answers to configuration

| Answer | What you set |
|---|---|
| Admin console / data-heavy | `data-density="-1"` or `-2` on `<html>`; prefer `size="xs"`/`"sm"` on actions |
| Consumer / marketing | leave density alone (`0` is the default); larger button sizes (`md`/`lg`) for CTAs |
| Mobile-first | `md-navigation-bar` + `md-fab`; avoid `md-navigation-rail`, `md-transfer-list`, wide tables |
| Desktop-first | `md-navigation-rail` or `md-app-bar`; rail over bottom bar |
| Dark mode | `data-theme="dark"` on `<html>`; wire a toggle, and mirror OS via `prefers-color-scheme` |
| Both themes with toggle | persist the choice; set the attribute before first paint to avoid a flash |
| Brand color | override the `--md-sys-color-*` roles in your own stylesheet, loaded after the tokens (§4.2) |
| Flat / utilitarian | `data-ripple="off"` and `data-shape-morph="off"` on `<html>` |
| Any RTL locale | `dir="rtl"` on `<html>`; add `mirror-icon` to `md-button`s with directional glyphs; swap the glyph name yourself everywhere else (§4.5) |
| Multiple locales | build a dictionary in the consumer layer and feed component text props from it; never hardcode strings in markup |
| `Intl`-formatted values | pass a `locale` prop where a component exposes one; format everything else before it reaches the component |
| SSR | import from `@awc-ui/core/hydrate` on the server; use the client/server wrappers in `@awc-ui/react` |
| Heavy forms | components are form-associated via `ElementInternals` — use a real `<form>`, `md-button type="submit"`, and native `required` (§4.7) |
| Rich text | integrate a third-party editor; there is no `md-rich-text` |

### 2.1 Global switches — the complete set

All are attributes on `<html>` (or any ancestor; the nearest one wins).

```html
<html
  lang="en"
  dir="ltr"                  <!-- or rtl -->
  data-theme="dark"          <!-- omit for light -->
  data-density="-1"          <!-- -1 … -4; see below -->
  data-ripple="off"          <!-- default on -->
  data-shape-morph="off"     <!-- default on -->
>
```

**`data-density="0"` is inert.** No `[density="0"]` rule exists — density `0` is
simply the base values on `:root`, and a `0` rule would pin them onto every
element (reflected props write `density="0"` almost everywhere) and break global
inheritance. So the *overriding* range is `-1 … -4`. To escape an inherited rung
for one subtree, reset the scale directly:

```css
.opt-out-of-density { --md-sys-density-scale: 0; }
```

Per-component overrides beat the global one: a `density` prop, or
`ripple="off"` / `shape-morph="off"` on the element. **53 of the 57 top-level
components expose a `density` prop.** The four that don't: `md-divider`,
`md-ripple`, `md-sparkline`, `md-tabs`. (Nine sub-components also lack one —
they inherit density from the parent that owns their layout.)

---

## §3 — Install and bootstrap

```bash
npm install @awc-ui/core
```

**Register everything (recommended).** Two imports, once per app entry — one
defines every component, one loads the tokens. Both come from `@awc-ui/core`
itself, so this path needs no second package:

```ts
import { defineCustomElements } from '@awc-ui/core/loader';
import '@awc-ui/core/css/tokens.css';

defineCustomElements(window);
```

`@awc-ui/core/css/tokens.css` is the complete, self-contained token sheet —
light and dark colour roles, shape, elevation, motion, typescale, spacing and
z-index. It is the same set documented in §4.1.

**One-line alternative.** `@awc-ui/core/define` does both steps in a single
import. It loads the package's own token sheet, so it needs nothing else
installed:

```ts
import '@awc-ui/core/define'; // defines every component + loads the token sheet
```

`define` is client-only (it eval-guards `window`) and, because it imports CSS,
it needs a bundler. Never import it into a server graph.

**Per-component registration**, for size-sensitive bundles — load the token
sheet once yourself:

```ts
import '@awc-ui/core/css/tokens.css';
import '@awc-ui/core/components/md-button';
import '@awc-ui/core/components/md-text-field';
```

**Framework wrappers** — use these instead of raw elements; they handle
registration, typed props, and event binding. Below the version floor, drop to
the raw custom elements and the `loader` import above.

| Framework | Package | Requires |
|---|---|---|
| React / Next | `@awc-ui/react` | React 18+ |
| Angular | `@awc-ui/angular` (`AwcUiModule`) | Angular 17+ |
| Vue / Nuxt | `@awc-ui/vue` | Vue 3 |
| Svelte / SvelteKit | `@awc-ui/svelte` | Svelte 4+ |
| Plain HTML / Astro | `@awc-ui/core/loader` | — |

**SSR** — `@awc-ui/core/hydrate` renders Declarative Shadow DOM on the server.
`@awc-ui/react` ships matching client/server wrappers. Keep `define` and
`loader` out of the server graph; both are browser entries.

**Fonts** — components expect Roboto and Material Symbols Outlined to be
available. The library does **not** inject them. Every `icon="…"` prop renders a
Material Symbols glyph inside shadow DOM, so the font must be registered at the
**document** level (font registration crosses shadow boundaries; class rules do
not — the components declare the class rule inside their own roots):

```html
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap">
```

To swap the whole library to a different Material Symbols cut, set
`--md-sys-icon-font-family` on `:root` and load the matching `@font-face`.

---

## §4 — Global configuration reference

### 4.1 The token system

All AWC UI styling resolves to design tokens. **Never hardcode hex colours,
pixel radii, shadows, or font stacks** — they break theming, dark mode, density
and RTL in one stroke. Every value below is a real custom property defined by
`@awc-ui/tokens`; you may read them, and you may override them.

#### Color roles

Light is the default; `[data-theme="dark"]` swaps the palette. Every role has a
matching `on-` role for content drawn on top of it.

| Token | Light | Dark | Typical usage |
|---|---|---|---|
| `--md-sys-color-primary` | `#6750A4` | `#D0BCFF` | Filled buttons, active states |
| `--md-sys-color-on-primary` | `#FFFFFF` | `#381E72` | Text/icons on primary |
| `--md-sys-color-primary-container` | `#EADDFF` | `#4F378B` | FAB, selected segments |
| `--md-sys-color-on-primary-container` | `#21005D` | `#EADDFF` | Text on primary-container |
| `--md-sys-color-secondary` | `#625B71` | `#CCC2DC` | Secondary accents |
| `--md-sys-color-secondary-container` | `#E8DEF8` | `#4A4458` | Tonal buttons, chips |
| `--md-sys-color-tertiary` | `#7D5260` | `#EFB8C8` | Tertiary accents |
| `--md-sys-color-tertiary-container` | `#FFD8E4` | `#633B48` | Tertiary surfaces |
| `--md-sys-color-error` | `#B3261E` | `#F2B8B5` | Error / destructive |
| `--md-sys-color-on-error` | `#FFFFFF` | `#601410` | Content on error |
| `--md-sys-color-error-container` | `#F9DEDC` | `#8C1D18` | Error surfaces |
| `--md-sys-color-surface` | `#FFFBFE` | `#1C1B1F` | Page / card background |
| `--md-sys-color-on-surface` | `#1C1B1F` | `#E6E1E5` | Body text |
| `--md-sys-color-surface-variant` | `#E7E0EC` | `#49454F` | Muted fills |
| `--md-sys-color-on-surface-variant` | `#49454F` | `#CAC4D0` | Secondary text, icons |
| `--md-sys-color-surface-container-lowest` | `#FFFFFF` | `#0F0D13` | Lowest surface tier |
| `--md-sys-color-surface-container-low` | `#F7F2FA` | `#1D1B20` | Card / elevated button bg |
| `--md-sys-color-surface-container` | `#F3EDF7` | `#211F26` | Default container tier |
| `--md-sys-color-surface-container-high` | `#ECE6F0` | `#2B2930` | Dialog / menu surfaces |
| `--md-sys-color-surface-container-highest` | `#E6E0E9` | `#36343B` | Highest surface tier |
| `--md-sys-color-outline` | `#79747E` | `#938F99` | Borders (3:1 contrast) |
| `--md-sys-color-outline-variant` | `#CAC4D0` | `#49454F` | Dividers (low contrast) |
| `--md-sys-color-inverse-surface` | `#313033` | `#E6E1E5` | Snackbar, inverse chips |
| `--md-sys-color-inverse-on-surface` | `#F4EFF4` | `#313033` | Content on inverse-surface |
| `--md-sys-color-scrim` | `#000000` | `#000000` | Modal scrims |

**Semantic status roles** — beyond the baseline M3 palette, the library adds
`success`, `warning` and `info`, each with `on-`, `-container` and
`on-…-container` companions, in both themes, AA-verified. Used by `md-chip`,
`md-badge`, `md-meter`, `md-status-dot`.

| Token | Light | Dark |
|---|---|---|
| `--md-sys-color-success` | `#2E6B4F` | `#9DD5B0` |
| `--md-sys-color-success-container` | `#B8F0CE` | `#14512F` |
| `--md-sys-color-warning` | `#7A5900` | `#EFC148` |
| `--md-sys-color-warning-container` | `#FFDF9B` | `#5C4200` |
| `--md-sys-color-info` | `#38608F` | `#A2C9FE` |
| `--md-sys-color-info-container` | `#D3E4FF` | `#1E4975` |

#### Shape tokens

| Token | Value | Typical usage |
|---|---|---|
| `--md-sys-shape-corner-none` | `0px` | Square edges |
| `--md-sys-shape-corner-extra-small` | `4px` | Snackbar, small chips |
| `--md-sys-shape-corner-small` | `8px` | Cards (outlined), small surfaces |
| `--md-sys-shape-corner-medium` | `12px` | Cards (default), inputs |
| `--md-sys-shape-corner-large` | `16px` | Dialogs, large cards |
| `--md-sys-shape-corner-extra-large` | `28px` | Bottom sheets |
| `--md-sys-shape-corner-full` | `9999px` | Buttons, chips, FAB |

Partial-corner variants also exist for surfaces that meet an edge:
`--md-sys-shape-corner-extra-small-top`, `-large-top`, `-large-end`,
`-extra-large-top`. Their values are four-value `border-radius` shorthands
(e.g. `16px 16px 0px 0px`), so assign them to `border-radius`, not to a single
corner.

#### Elevation tokens

`-0` is the keyword `none`; `-1` through `-5` are complete `box-shadow` values,
and those five are **re-declared with deeper shadows under `[data-theme="dark"]`**
— so always use the token rather than copying its value.

| Token | When |
|---|---|
| `--md-sys-elevation-0` | `none` — resting cards, flat surfaces |
| `--md-sys-elevation-1` | Elevated buttons and cards at rest |
| `--md-sys-elevation-2` | Hover bump |
| `--md-sys-elevation-3` | FAB, menus, dialogs at rest |
| `--md-sys-elevation-4` – `-5` | Reserved for the most prominent surfaces |

#### Motion tokens

Durations come in four families of four: `short1…4` (50/100/150/200ms),
`medium1…4` (250/300/350/400ms), `long1…4` (450/500/550/600ms), and
`extra-long1…4` (700/800/900/1000ms).

| Token | Value | When |
|---|---|---|
| `--md-sys-motion-duration-short2` | `100ms` | Buttons, ripples, state layers |
| `--md-sys-motion-duration-medium2` | `300ms` | Dialogs, sheets, menus |
| `--md-sys-motion-duration-long2` | `500ms` | Large surface transitions |
| `--md-sys-motion-duration-extra-long1` | `700ms` | Ambient / chart entrances |
| `--md-sys-motion-easing-standard` | `cubic-bezier(0.2, 0, 0, 1)` | Small utility transitions |
| `--md-sys-motion-easing-standard-accelerate` | `cubic-bezier(0.3, 0, 1, 1)` | Exits |
| `--md-sys-motion-easing-standard-decelerate` | `cubic-bezier(0, 0, 0, 1)` | Entrances |
| `--md-sys-motion-easing-emphasized` | `cubic-bezier(0.2, 0, 0, 1)` | Expressive transitions |
| `--md-sys-motion-easing-emphasized-accelerate` | `cubic-bezier(0.3, 0, 0.8, 0.15)` | Expressive exits |
| `--md-sys-motion-easing-emphasized-decelerate` | `cubic-bezier(0.05, 0.7, 0.1, 1)` | Expressive entrances |
| `--md-sys-motion-easing-linear` | `linear` | Progress, indeterminate loops |

> `--md-sys-motion-easing-emphasized` deliberately equals the standard curve:
> the full M3 emphasized curve is a two-phase path interpolator that CSS cannot
> express. The accelerate and decelerate halves *are* expressible, and are the
> ones to reach for.

MD3 Expressive also ships spring pairs, each an easing plus its natural
duration: `--md-sys-motion-spring-{spatial,effects}-{fast,default,slow}-easing`
and `…-duration`. Spatial = position/size/shape; effects = colour/opacity.

#### State-layer opacities

| Token | Value |
|---|---|
| `--md-sys-state-hover-state-layer-opacity` | `0.08` |
| `--md-sys-state-focus-state-layer-opacity` | `0.12` |
| `--md-sys-state-pressed-state-layer-opacity` | `0.12` |
| `--md-sys-state-dragged-state-layer-opacity` | `0.16` |
| `--md-sys-state-disabled-container-opacity` | `0.12` |
| `--md-sys-state-disabled-content-opacity` | `0.38` |

#### Typography, spacing and layering

- **Typescale** — `--md-sys-typescale-<role>-<size>-*` for
  `display`/`headline`/`title`/`label`/`body` × `large`/`medium`/`small`. Each
  role exposes `-font-family`, `-font-size`, `-line-height`, `-font-weight`,
  `-letter-spacing`, plus a `-font` shorthand
  (e.g. `--md-sys-typescale-headline-medium-font: 400 28px/36px Roboto, sans-serif`).
  Use the shorthand on the `font` property for headings you write yourself.
- **Spacing** — a 4px scale that tightens with density:
  `--md-sys-spacing-inset-{xs,sm,md,lg,xl}` (internal padding, 4/8/12/16/24px)
  and `--md-sys-spacing-gap-{xs,sm,md,lg}` (between siblings, 4/8/12/16px), plus
  `--md-sys-spacing-row-height` (`56px` at density 0).
- **Layering** — `--md-sys-z-index-app-bar` (100), `-navigation` (200),
  `-bottom-sheet` (300), `-popup` (1000), `-dialog-scrim` (1001), `-tooltip`
  (1500), `-snackbar` (2000). Use these instead of inventing z-indexes, or your
  overlay will land under a menu.

### 4.2 Theming and rebranding

To rebrand, redefine the role variables **after** the tokens stylesheet — both
themes, or dark mode inherits your light brand colour:

```css
:root {
  --md-sys-color-primary: #00629E;
  --md-sys-color-on-primary: #FFFFFF;
  --md-sys-color-primary-container: #CFE5FF;
  --md-sys-color-on-primary-container: #001D33;
}
[data-theme="dark"] {
  --md-sys-color-primary: #9BCBFF;
  --md-sys-color-on-primary: #003354;
  --md-sys-color-primary-container: #004A78;
  --md-sys-color-on-primary-container: #CFE5FF;
}
```

Per-component knobs are `--md-<component>-*` custom properties, listed in each
manual's Theming section (`--md-button-container-color`,
`--md-card-container-shape`, …). Prefer those over `::part()`, and prefer
`::part()` over reaching into shadow internals, which are unstable and will
break on any release.

**Overriding a colour role forfeits the library's contrast testing.** Re-verify
AA (4.5:1 text, 3:1 borders and dividers) after any palette change. The Theme
Generator at <https://awc-ui.dev/theme-generator> takes a seed colour, emits both
palettes as `--md-sys-color-*` overrides, and runs the WCAG checks live — use it
rather than hand-picking container and `on-` pairs.

### 4.3 Density

`data-density` steps `-1 → -4`; each rung trims ~4px of padding and touch
target, driving `--md-sys-density-scale` and the spacing tokens. `-4` is the
floor. Set it globally, override locally with the `density` prop. Do not go
below `-2` on touch-primary surfaces — you will break the 48px target.

Two details that bite:

- **`density="0"` does nothing** — see §2 for why, and for the
  `--md-sys-density-scale: 0` escape hatch.
- **`md-table` accepts two vocabularies**: the semantic `compact` / `standard` /
  `comfortable` (row heights 36 / 52 / 60px) *and* the numeric rungs. They
  compose — `density="compact"` inside a `data-density="-2"` region condenses
  further.

### 4.4 Dark mode

Set `data-theme="dark"` on `<html>` (or any ancestor — the nearest wins). The
tokens swap automatically and inherit across every shadow boundary; no
per-component code. To mirror the OS, read `prefers-color-scheme` and write the
attribute **before first paint**, or the page flashes light.

### 4.5 RTL

Layout is written with CSS logical properties, so it flips with `dir="rtl"`
without extra work. Two things still need you:

- **Directional glyphs do not flip.** Material Symbols are not auto-mirrored.
  `md-button` has a `mirror-icon` prop that mirrors its own leading/trailing
  glyph — set it on buttons using arrows, chevrons, `send`, `reply`, and leave
  it off for `add`, `search`, `favorite`. **`mirror-icon` exists only on
  `md-button`.** Everywhere else — `md-icon-button icon="…"`, `md-list-item`
  leading/trailing icons, `md-app-bar leading-icon` — swap the glyph name
  yourself (`arrow_back` ↔ `arrow_forward`).
- **Icon props you supply yourself** — e.g. `md-transfer-list`'s
  `move-right-icon` / `move-left-icon` / `move-all-*-icon` — must be swapped by
  you when the direction flips.

Never write physical CSS (`margin-left`, `padding-right`, `left`) around these
components. Use `margin-inline-start`, `padding-inline-end`, `inset-inline-end`.

### 4.6 Internationalization

Components are **i18n-engine-agnostic by design**. Every user-visible string is
either slotted content or a prop. Localize in the consumer layer: resolve your
dictionary to plain strings, then pass them in. Do not add a translation engine
inside a component. `locale` props exist only where a component computes an
`Intl`-formatted value itself: `md-date-picker`, `md-number-field`,
`md-meter`, `md-dialog`, and the charts (`md-bar-chart`, `md-line-chart`,
`md-area-chart`, `md-pie-chart`). `md-time-picker` has no
`locale` prop — it takes `format="12h" | "24h"` instead.

Templated strings keep their placeholder tokens when translated — translate
around the braces, don't remove them:

```html
<md-transfer-list count-template="{checked} / {total} ausgewählt"></md-transfer-list>
```

Other templated props: `md-autocomplete status-template`, `md-otp-field
cell-label-template`. Default prop values are English (`Search`, `Dismiss`,
`No results`, `Move selected to target`, …) — every one of them needs
translating in a localized app.

### 4.7 Forms and validation

Fourteen components are form-associated via `ElementInternals`, so they
participate in `FormData` and constraint validation like native controls:

`md-text-field` · `md-number-field` · `md-otp-field` · `md-select` ·
`md-multi-select` · `md-autocomplete` · `md-checkbox` · `md-radio` ·
`md-switch` · `md-slider` · `md-rating` · `md-date-picker` · `md-time-picker` ·
`md-button`

Rules:

- Use a real `<form>`. `md-button type="submit"` calls `form.requestSubmit()`
  (not `submit()`), so the `submit` event fires **and** built-in constraint
  validation runs. `required` genuinely blocks submit.
  `md-button type="reset"` calls `form.reset()`.
- Give every control a `name`, or it will not appear in `FormData`.
- Do **not** add hidden `<input>`s to mirror values — that's the old pattern and
  it double-submits.
- Validity changes are announced on a `mdValidityChange` event on the control.
  Error presentation is `error` + `error-text` on the field.
- Boolean state props differ by control — `md-checkbox` uses `checked`,
  `md-switch` uses **`selected`**, `md-select-option` uses `selected`. Check the
  manual; guessing `checked` on a switch silently does nothing.

---

## §5 — Component decision matrix

Route by **need**, not by name. If the need isn't listed, find the closest row
and read that component's `When NOT to use`.

### 5.1 Actions

| Need | Use | Don't use |
|---|---|---|
| Discrete labelled action | `md-button` | `md-chip`, raw `<button>` |
| Icon-only action | `md-icon-button` | `md-button` with no label |
| A destructive action | `md-button` + `md-dialog` to confirm | an unconfirmed `filled` button |
| The single most prominent screen action (mobile) | `md-fab` | a second `filled` `md-button` |
| One prominent action that expands to several | `md-fab-menu` + `md-fab-menu-item` | a stack of FABs |
| Primary action + variants of it | `md-split-button` | button + separate menu |
| 2–5 related actions as one unit | `md-button-group` | loose adjacent buttons |
| Mutually exclusive view/mode switch | `md-segmented-button-set` + `md-segmented-button` | radio buttons, tabs |
| Overflow / contextual actions | `md-menu` + `md-menu-item` | a row of text buttons |

### 5.2 Text input

| Need | Use | Don't use |
|---|---|---|
| Any single-line or multi-line text entry | `md-text-field` | raw `<input>` |
| A number with steppers and locale formatting | `md-number-field` | `md-text-field type="number"` |
| A one-time code / PIN | `md-otp-field` | a row of text fields |
| Text entry with suggestions | `md-autocomplete` | `md-select` |
| Site/app-wide search with a results surface | `md-search` | `md-text-field` with an icon |

### 5.3 Selection

| Need | Use | Don't use |
|---|---|---|
| One of many, from a list | `md-select` + `md-select-option` | radio group over ~7 options |
| One of few (2–5), all visible | `md-radio` | `md-select` |
| Several of many | `md-multi-select` | many `md-checkbox`es |
| Several of few, all visible | `md-checkbox` | `md-multi-select` |
| Instant on/off setting | `md-switch` | `md-checkbox` |
| A value in a numeric range | `md-slider` | `md-text-field type=number` |
| Subjective score | `md-rating` | slider |
| A color | `md-color-picker` | `<input type=color>` |
| Assign a subset from a bounded pool, side by side | `md-transfer-list` | two lists + buttons |
| Filter / attribute / removable entry | `md-chip` | small buttons |
| A date | `md-date-picker` | three selects |
| A time | `md-time-picker` | text field |

### 5.4 Navigation

| Need | Use | Don't use |
|---|---|---|
| Top-level destinations, mobile | `md-navigation-bar` + `md-navigation-tab` | tabs |
| Top-level destinations, desktop | `md-navigation-rail` + `md-navigation-rail-tab` | bottom bar |
| App header: title, actions, search | `md-app-bar` | a custom `<header>` |
| A dense action strip | `md-toolbar` | app bar |
| Sibling views **within** one screen | `md-tabs` + `md-tab` + `md-tab-panels` + `md-tab-panel` | navigation bar |
| Hierarchy / where-am-I | `md-breadcrumbs` + `md-breadcrumb-item` | text links |
| A linear multi-step flow | `md-stepper` + `md-step` | tabs |
| Contextual popup actions | `md-menu` + `md-menu-item`, `md-menu-item-group`, `md-sub-menu-item` | dialog |
| Hierarchical command menu (File → Export → PDF) | `md-menu` + `md-sub-menu-item` | nested dialogs |

### 5.5 Containment and feedback

| Need | Use | Don't use |
|---|---|---|
| Group related content | `md-card` | a bare `<div>` with a border |
| Blocking decision or focused task | `md-dialog` | a new page |
| Critical error the user must acknowledge | `md-dialog` | `md-snackbar` |
| Supplementary content from the bottom (mobile) | `md-bottom-sheet` | dialog |
| Supplementary content from the side (desktop) | `md-side-sheet` | dialog |
| Brief confirmation of an action, optionally undoable | `md-snackbar` | dialog, alert |
| Explain a control on hover/focus | `md-tooltip` | a dialog or inline hint |
| Progressive disclosure of sections | `md-accordion` + `md-accordion-item` | tabs |
| Visual separation | `md-divider` | a styled `<hr>` |
| A vertical set of records | `md-list` + `md-list-item` | a table |
| Determinate/indeterminate progress | `md-progress-indicator` | spinner GIF |
| Brand-consistent page/content loading | `md-loading-indicator` | custom spinner |
| Content-shaped loading placeholder | `md-skeleton` | a spinner over the whole page |
| Count or status on an element | `md-badge` | superscript text |
| Compact status dot | `md-status-dot` | a colored emoji |
| Read-only value within a known range (quota, battery) | `md-meter` | `md-progress-indicator` |
| A person or entity image/initials | `md-avatar` | a raw `<img>` |
| Touch feedback inside a custom control | `md-ripple` | custom CSS animation |

### 5.6 Data

| Need | Use | Don't use |
|---|---|---|
| Any table | `md-table-container` wrapping `md-table`, with `-head`/`-body`/`-row`/`-cell`/`-foot` inside the table and `-toolbar`/`-pagination` beside it in the container (§7.1) | a native `<table>` |
| Sorting, selection, paging on that table | the same parts — `md-table` carries the STATE (`sort-by`, `sort-order`, `selection`, `row-offset`, `row-count`, `loading`) and emits events; you own the data and do the actual sorting/paging | expecting it to sort an array for you |
| Hierarchy / reporting lines | `md-organization-chart` | nested lists |
| Compare categories | `md-bar-chart` | pie chart |
| Trend over time | `md-line-chart` | bar chart |
| Trend with cumulative volume | `md-area-chart` | line chart |
| Parts of a whole (≤ ~6 slices) | `md-pie-chart` | bar chart |
| Inline micro-trend in a cell or card | `md-sparkline` | a full chart |

> **There is no data-driven table component.** `md-table` is composable: you
> render the rows. It tracks and announces sort, selection and pagination state
> and emits events when the user changes them, but the sorting, filtering and
> slicing of your data is yours to perform. Render rows from your own array in
> response to those events.

### 5.7 Choosing the variant

The matrix picks the component; this picks its shape. Every value below is a
real enum member — anything not listed here is not a valid value.

| Component | Prop | Values, and when |
|---|---|---|
| `md-button` | `variant` | `filled` the one primary action · `tonal` a strong secondary · `outlined` secondary, and the safe choice for a destructive action behind a confirm · `text` low emphasis, dialog Cancel · `elevated` when it sits on a busy or coloured background (default `filled`) |
| `md-button` / `md-icon-button` / `md-button-group` | `size` | `xs` `sm` `md` `lg` `xl` — default `sm`; go `md`/`lg` for consumer CTAs, `xs`/`sm` for dense admin UI |
| `md-icon-button` | `variant` | `standard` (default) · `filled` · `tonal` · `outlined` |
| `md-button-group` | `variant` | `standard` spaced · `connected` fused into one bar |
| `md-fab` | `size` | `standard` (default) · `medium` · `large` |
| `md-fab` | `variant` | `primary-container` (default) · `secondary-container` · `tertiary-container` · `surface` · `primary` · `secondary` · `tertiary` |
| `md-card` | `variant` | `filled` for a group of comparable items · `elevated` (default) for a hero or featured item · `outlined` for a settings or form section |
| `md-text-field` / `md-select` | `variant` | `outlined` for forms on a surface · `filled` for dense or tinted layouts (`md-text-field` defaults to `filled`, `md-select` to `outlined`) — pick one and use it for every field on the screen |
| `md-text-field` | `type` | any native input type: `password`, `email`, `tel`, `url`, `search`, … (default `text`) |
| `md-text-field` | `multiline` | `"auto-grow"` grows with content · `"fixed"` with `rows` for a fixed comment box · `false` (default) single line |
| `md-search` | `variant` | `contained` (default) · `divided` |
| `md-app-bar` | `variant` | `small` (default) · `medium` · `large` for a prominent headline · `search` for a bar that hosts a field |
| `md-chip` | `variant` | `assist` (default) · `filter` for toggleable facets · `input` for user-entered removable values · `suggestion` |
| `md-chip` | `appearance` | `outlined` (default) · `filled` · `elevated` |
| `md-badge` | `variant` | `small` a bare dot · `large` (default) a count |
| `md-tooltip` | `variant` | `plain` (default) a short label on an icon control · `rich` an explanatory popover that may hold a link or action |
| `md-divider` | — | **no `variant`** — use the booleans `inset`, `inset-start`, `inset-end` |
| `md-side-sheet` | `variant` | `standard` coexists with page content · `modal` overlays with a scrim |
| `md-bottom-sheet` | `variant` | `standard` (default) · `detached` floating above the edge |
| `md-dialog` | `fullscreen` | boolean — a full-screen dialog for a long mobile task |
| `md-date-picker` | `variant` | `modal-input` (default) calendar plus a typed field · `modal` calendar only · `docked` inline, anchored to the field |
| `md-time-picker` | `variant` | `dial` · `input` (default) |
| `md-progress-indicator` | `variant` | `linear` (default) · `circular`; add `indeterminate` when the total is unknown |
| `md-list-item` | `lines` | `1` · `2` · `3` — must match how much supporting text you pass |
| `md-navigation-bar` | — | 3–5 destinations. Fewer than three: use `md-tabs`. More than five: a rail or a menu |
| `md-navigation-rail` | — | 3–7 destinations; cap the overflow with `max-visible` |

### 5.8 Not in the library

Rich text editor · file upload/dropzone · calendar/scheduler view · map ·
toast stack manager (use `md-snackbar` and manage the queue yourself) ·
data grid with virtualized columns. If the user needs one, say so plainly and
integrate a third-party component styled with the MD3 tokens.

**There is no `md-grid`, no `md-data-table`, no `md-layout`, no `md-icon`.**
If a tag is not listed in §6, it does not exist — do not emit it.

---

## §6 — Component inventory

**Every component has exactly one manual, and it is the readme.md in its own
source folder:**

```
./packages/core/src/components/<tag>/readme.md
```

e.g. [`md-select`](./packages/core/src/components/md-select/readme.md),
[`md-button`](./packages/core/src/components/md-button/readme.md).

**Load that file before you use the component.** There is no second, shorter
summary to rely on — this readme is the single source, so anything you do not
read there, you do not know. Each one carries When-to-use, a Do/Don't table
from M3, copy-paste patterns, an anti-patterns table, and the theming surface.

Work through them one at a time: pick the component from the decision matrix
(§5), load its readme, write that component's markup, then move to the next.

There are 81 manuals for 57 components: 24 of them document sub-components that
are only valid inside a parent (a table cell, a tab panel, a select option).
Every sub-component manual names its parent in the first line, and §7 below
summarises the nesting.

`status` in each manual's `llm:meta` block is one of:

- **`md3-mapped`** — has a Material Design 3 guidelines page; the Do/Don't is
  sourced from it.
- **`custom`** — an addition to MD3; guidance is derived house rules.
- **`sub-component`** — only valid inside a specific parent.

| Category | Components |
|---|---|
| Actions | `md-button` `md-icon-button` `md-fab` `md-fab-menu` `md-fab-menu-item` `md-split-button` `md-button-group` `md-segmented-button` `md-segmented-button-set` |
| Text input | `md-text-field` `md-number-field` `md-otp-field` `md-autocomplete` `md-search` |
| Selection | `md-select` `md-select-option` `md-multi-select` `md-checkbox` `md-radio` `md-switch` `md-slider` `md-rating` `md-color-picker` `md-transfer-list` `md-chip` |
| Pickers | `md-date-picker` `md-time-picker` |
| Navigation | `md-app-bar` `md-toolbar` `md-navigation-bar` `md-navigation-tab` `md-navigation-rail` `md-navigation-rail-tab` `md-tabs` `md-tab` `md-tab-panels` `md-tab-panel` `md-breadcrumbs` `md-breadcrumb-item` `md-menu` `md-menu-item` `md-menu-item-group` `md-sub-menu-item` `md-stepper` `md-step` |
| Containment | `md-card` `md-dialog` `md-bottom-sheet` `md-side-sheet` `md-snackbar` `md-tooltip` `md-accordion` `md-accordion-item` `md-divider` `md-list` `md-list-item` |
| Data | `md-table` `md-table-container` `md-table-head` `md-table-body` `md-table-row` `md-table-cell` `md-table-foot` `md-table-toolbar` `md-table-pagination` `md-table-sort-label` `md-table-expand-toggle` `md-organization-chart` |
| Charts | `md-bar-chart` `md-line-chart` `md-area-chart` `md-pie-chart` `md-sparkline` |
| Status & feedback | `md-progress-indicator` `md-loading-indicator` `md-skeleton` `md-badge` `md-status-dot` `md-meter` `md-avatar` `md-ripple` |

---

## §7 — Composition rules

### 7.1 What nests inside what

A sub-component is only valid inside its parent. Putting one anywhere else
produces an unstyled, unregistered-looking element with no keyboard behaviour,
because the parent is what wires roving tabindex, ARIA ids and selection.

| Parent | Children it manages |
|---|---|
| `md-button-group` | `md-button`, `md-icon-button` |
| `md-segmented-button-set` | `md-segmented-button` |
| `md-fab-menu` | `md-fab-menu-item` (anchored to an `md-fab` via `anchor="<id>"`) |
| `md-menu` | `md-menu-item`, `md-sub-menu-item`, `md-menu-item-group` — **not** `md-divider`; separate rows with `md-menu-item`'s own `divider` (or `gap`) prop |
| `md-menu-item-group` | `md-menu-item` |
| `md-sub-menu-item` | a nested `md-menu` in `slot="submenu"` — the items go in *that* menu. There is no default slot, so anything else you nest renders nothing |
| `md-select`, `md-multi-select`, `md-autocomplete` | `md-select-option` |
| `md-list` | `md-list-item`, `md-divider` |
| `md-tabs` | `md-tab` |
| `md-tab-panels` | `md-tab-panel` (one per tab, in tab order) |
| `md-navigation-bar` | `md-navigation-tab` |
| `md-navigation-rail` | `md-navigation-rail-tab`, plus an `md-fab` in `slot="fab"` |
| `md-accordion` | `md-accordion-item` |
| `md-stepper` | `md-step` (horizontal steppers also take `slot="content"`) |
| `md-breadcrumbs` | `md-breadcrumb-item` |
| `md-table-container` | `md-table` — **it wraps the table, not the other way round** — plus `md-table-toolbar` in `slot="top"` and `md-table-pagination` in `slot="bottom"`, which sit outside the scroll region |
| `md-table` | `md-table-head`, `md-table-body`, `md-table-foot` (and bare `md-table-row`). It does **not** accept the container, toolbar or pagination |
| `md-table-head` / `-body` / `-foot` | `md-table-row` |
| `md-table-row` | `md-table-cell`, plus `md-table-expand-toggle` for an expandable row |
| `md-table-cell` | `md-table-sort-label` in a header cell |
| `md-tooltip` | **its trigger** — the tooltip wraps the element it describes in its default slot |
| `md-dialog` | body content in the default slot; `md-button` in `slot="actions"` |
| `md-bottom-sheet` / `md-side-sheet` | content in the default slot; `md-button` in `slot="actions"`; headline in `slot="headline"` |
| `md-app-bar` | `md-icon-button` in `slot="leading"` and `slot="trailing"`; `md-menu` for overflow; a field in `slot="search"` on `variant="search"` |
| `md-toolbar` | `md-icon-button`, `md-button`, `md-button-group`; an `md-fab` in `slot="fab"`; `slot="leading"` / `slot="trailing"` for the end clusters |

### 7.2 Pairs that belong together

These don't nest — they sit next to each other in a working flow. Reaching for
one usually means you want the other.

| This | Goes with | Why |
|---|---|---|
| `md-icon-button` | `md-tooltip` | The tooltip supplies the visible meaning the icon lacks |
| `md-button` (`soft-disabled`) | `md-tooltip` | Explains *why* the action is unavailable |
| `md-fab` | `md-fab-menu` | The FAB is the menu's anchor |
| `md-split-button` | `md-menu` | The trailing half opens it |
| `md-card` | `md-button`, `md-icon-button`, `md-divider` | Footer actions, corner action, internal sections |
| `md-list-item` | `md-checkbox`, `md-switch`, `md-icon-button` | Trailing controls in a selectable or settings row |
| `md-bottom-sheet` / `md-side-sheet` | `md-list` | Action menus and filter panels inside the sheet |
| `md-search` | `md-list`, `md-avatar`, `md-icon-button` | Results in the panel; account and voice/filter affordances in the trailing slot |
| `md-date-picker` | `md-time-picker` | Date + time row for booking and scheduling forms |
| `md-time-picker` | `md-button` | The trigger, when `hide-trigger` is set. The picker **is** its own dialog — don't nest it in another one |
| `md-text-field` | `md-button` | Submit / cancel in the form footer |
| `md-multi-select` / `md-autocomplete` | `md-text-field`, `md-menu`, `md-chip` | The field is the trigger (and inherits its variant, density and error state), the menu is the option surface, chips are the selected values |
| `md-autocomplete` | `md-progress-indicator` | The loading row while suggestions are fetched |
| `md-transfer-list` | `md-checkbox`, `md-text-field`, `md-icon-button` | Per-row select, per-side search, mover controls |
| `md-number-field` | `md-text-field` hooks, `md-icon-button` | It *is* an `md-text-field` internally — every `--md-text-field-*` custom property passes through — and its steppers are `md-icon-button`s |
| `md-otp-field` | `md-button` | Verify action (`auto-submit` covers the no-button flow) |
| Any chart | `md-card` | Charts belong on a dashboard tile |
| `md-line-chart` | `md-segmented-button-set` | Period picker (1W / 1M / 1Y) driving the range |
| `md-sparkline` | `md-list-item`, `md-table-cell` | Trend column beside a value |
| `md-skeleton` | `md-card`, `md-list` | Render N placeholders in the real layout while fetching |
| `md-meter` | `md-chip`, `md-card` | Same semantic status colour on both; quota and usage summaries live on a card |
| `md-color-picker` | `md-text-field`, `md-button` | Label and helper text beside it in a form; save / cancel in the surrounding popover or dialog |
| `md-rating` | `md-text-field`, `md-card` | A rating row in a review form; aggregate scores on a review card |
| `md-accordion` | `md-divider` | Optional inner dividers inside long item content |
| `md-stepper` | `md-button` | Next / back / submit adjacent to the stepper |

### 7.3 Nesting that is always wrong

- A dialog opened from inside a dialog. Use `md-stepper` inside **one**
  `md-dialog`.
- A component inside a native interactive element (`<button>`, `<a>`) — it
  nests interactive controls and destroys the accessibility tree. Use the
  component's own `href` / `type` props.
- `md-tabs` used for top-level app navigation. Tabs switch sibling views of the
  same data; destinations are `md-navigation-bar` / `md-navigation-rail`.

---

## §8 — Page recipes

Complete, runnable screens. Each renders as-is once the components are
registered and the token sheet and font links from §3 are loaded — no
placeholder identifiers, no helper functions to write.

### 8.1 Login screen

`<form>` is load-bearing: it is what makes `required` block submit and what
`type="submit"` calls `requestSubmit()` on.

```html
<main style="display: grid; place-items: center; min-block-size: 100dvh; padding: 24px;">
  <form id="login-form" style="inline-size: min(420px, 100%);">
    <md-card variant="elevated" style="padding: 32px; display: flex; flex-direction: column; gap: 20px;">
      <h1 style="margin: 0; font: var(--md-sys-typescale-headline-medium-font);">Sign in</h1>

      <md-text-field
        variant="outlined"
        label="Email"
        type="email"
        name="email"
        autocomplete="username"
        required
      ></md-text-field>

      <md-text-field
        variant="outlined"
        label="Password"
        type="password"
        name="password"
        autocomplete="current-password"
        password-toggle="internal"
        required
      ></md-text-field>

      <md-button variant="filled" type="submit" full-width>Sign in</md-button>
      <md-button variant="text" href="/forgot-password">Forgot password?</md-button>
    </md-card>
  </form>
</main>

<script type="module">
  document.getElementById('login-form').addEventListener('submit', (e) => {
    e.preventDefault();
    const data = new FormData(e.currentTarget);
    console.log(data.get('email'), data.get('password'));
  });
</script>
```

### 8.2 Settings page (mobile)

Top app bar + grouped rows of instant-apply switches. `md-switch` uses
**`selected`**, not `checked`. The rows are `type="text"` (non-interactive), so
the switch is the only control — one tab stop per setting.

```html
<md-app-bar variant="small" headline="Settings">
  <md-icon-button slot="leading" icon="arrow_back" aria-label="Back"></md-icon-button>
</md-app-bar>

<main style="padding: 16px; display: flex; flex-direction: column; gap: 16px;">
  <md-card variant="outlined">
    <md-list>
      <md-list-item headline="Notifications" supporting-text="Push, email, in-app" lines="2">
        <md-switch slot="trailing" selected aria-label="Enable notifications" data-setting="notifications"></md-switch>
      </md-list-item>
      <md-divider></md-divider>
      <md-list-item headline="Dark mode" supporting-text="Match system" lines="2">
        <md-switch slot="trailing" aria-label="Enable dark mode" data-setting="dark"></md-switch>
      </md-list-item>
      <md-divider></md-divider>
      <md-list-item headline="Sync over cellular">
        <md-switch slot="trailing" selected aria-label="Sync over cellular" data-setting="cellular"></md-switch>
      </md-list-item>
    </md-list>
  </md-card>
</main>

<md-snackbar id="settings-toast" message="Setting saved"></md-snackbar>

<script type="module">
  const toast = document.getElementById('settings-toast');
  document.querySelectorAll('md-switch[data-setting]').forEach((sw) => {
    sw.addEventListener('mdChange', (e) => {
      if (sw.dataset.setting === 'dark') {
        document.documentElement.setAttribute('data-theme', e.detail.selected ? 'dark' : 'light');
      }
      toast.show();
    });
  });
</script>
```

### 8.3 Settings form (deferred save, with validation)

When settings are saved on submit rather than applied instantly, use a real
form. `required` blocks the submit; every control needs a `name` to reach
`FormData`.

```html
<form id="profile-form" style="max-inline-size: 560px; margin: 24px auto; padding: 0 16px;">
  <md-card variant="outlined" style="padding: 24px; display: flex; flex-direction: column; gap: 20px;">
    <h2 style="margin: 0; font: var(--md-sys-typescale-title-large-font);">Profile</h2>

    <md-text-field
      variant="outlined"
      label="Display name"
      name="displayName"
      required
      supporting-text="Shown on your public profile"
    ></md-text-field>

    <md-select variant="outlined" label="Language" name="language" value="en" required>
      <md-select-option value="en" label="English"></md-select-option>
      <md-select-option value="de" label="Deutsch"></md-select-option>
      <md-select-option value="ar" label="العربية"></md-select-option>
    </md-select>

    <!-- md-checkbox has NO label slot. It is a labelable, form-associated
         element: wrap it in a native <label> (which names it AND forwards
         clicks), or give the host an aria-label. Text between the tags is
         NOT rendered. -->
    <label style="display: inline-flex; align-items: center; gap: 12px; cursor: pointer;">
      <md-checkbox name="newsletter" value="yes" supporting-text="Monthly, no more"></md-checkbox>
      <span>Send me product news</span>
    </label>

    <div style="display: flex; gap: 8px; justify-content: flex-end;">
      <md-button variant="text" type="reset">Reset</md-button>
      <md-button variant="filled" type="submit">Save changes</md-button>
    </div>
  </md-card>
</form>

<md-snackbar id="saved-toast" message="Changes saved" action="Undo"></md-snackbar>

<script type="module">
  const form = document.getElementById('profile-form');
  const toast = document.getElementById('saved-toast');

  form.addEventListener('submit', (e) => {
    e.preventDefault();
    const data = Object.fromEntries(new FormData(form));
    console.log(data); // { displayName, language, newsletter? }
    toast.show();
  });

  toast.addEventListener('mdAction', () => form.reset());
</script>
```

### 8.4 Dashboard with a FAB

```html
<md-app-bar variant="medium" headline="Inbox">
  <md-icon-button slot="trailing" icon="search" aria-label="Search"></md-icon-button>
  <md-icon-button slot="trailing" icon="filter_list" aria-label="Filter"></md-icon-button>
</md-app-bar>

<main style="padding: 16px; display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));">
  <md-card variant="filled" interactive style="padding: 16px;">
    <strong>Acme contract</strong>
    <p style="margin: 4px 0 0; color: var(--md-sys-color-on-surface-variant);">Renewal due in 5 days</p>
  </md-card>
  <md-card variant="filled" interactive style="padding: 16px;">
    <strong>Q4 report draft</strong>
    <p style="margin: 4px 0 0; color: var(--md-sys-color-on-surface-variant);">Shared by Alex</p>
  </md-card>
</main>

<md-fab
  icon="add"
  aria-label="New item"
  style="position: fixed; inset-block-end: 16px; inset-inline-end: 16px; z-index: var(--md-sys-z-index-navigation);"
></md-fab>
```

`inset-inline-end` (not `right`) is what keeps the FAB in the correct corner
under `dir="rtl"`.

### 8.5 Destructive confirmation dialog

`md-dialog` traps focus and returns it to the trigger on close — do not add your
own focus management. The error colouring goes through the button's own custom
properties, never a hex.

```html
<md-button id="open-delete" variant="outlined">Delete account</md-button>

<md-dialog id="confirm-delete" headline="Delete account?" icon="warning">
  <p style="margin: 0;">
    This will permanently delete your account and all associated data.
    This action cannot be undone.
  </p>
  <md-button id="cancel-delete" slot="actions" variant="text">Cancel</md-button>
  <md-button
    id="do-delete"
    slot="actions"
    variant="filled"
    style="--md-button-container-color: var(--md-sys-color-error);
           --md-button-label-color: var(--md-sys-color-on-error);"
  >Delete</md-button>
</md-dialog>

<script type="module">
  const dialog = document.getElementById('confirm-delete');
  document.getElementById('open-delete').addEventListener('mdClick', () => dialog.show());
  document.getElementById('cancel-delete').addEventListener('mdClick', () => dialog.close());
  document.getElementById('do-delete').addEventListener('mdClick', () => {
    dialog.close();
    // perform the deletion
  });
</script>
```

If you omit `slot="actions"` entirely, `md-dialog` renders its own Cancel / OK
pair (labels via `cancel-label` / `ok-label`) and closes itself.

### 8.6 Tabbed content page

`md-tab-panels` finds the nearest preceding `md-tabs` (or the one named by
`for`), follows its `mdTabChange`, and wires `aria-controls` /
`aria-labelledby` both ways. **No JavaScript is required**, and `md-tab` has no
`value` or `selected` prop — selection lives on `md-tabs` as `active-tab-index`.

```html
<md-tabs id="profile-tabs" aria-label="Profile sections" active-tab-index="0">
  <md-tab label="Posts"></md-tab>
  <md-tab label="Replies"></md-tab>
  <md-tab label="Likes" badge="3"></md-tab>
</md-tabs>

<md-tab-panels for="profile-tabs">
  <md-tab-panel style="padding: 16px;">
    <p style="margin: 0;">Your posts appear here.</p>
  </md-tab-panel>
  <md-tab-panel style="padding: 16px;">
    <p style="margin: 0;">Your replies appear here.</p>
  </md-tab-panel>
  <md-tab-panel style="padding: 16px;">
    <p style="margin: 0;">Posts you liked appear here.</p>
  </md-tab-panel>
</md-tab-panels>
```

To react to the switch (lazy-loading a panel, for example), listen on the tabs:

```js
document.getElementById('profile-tabs')
  .addEventListener('mdTabChange', (e) => console.log(e.detail.index, e.detail.previousIndex));
```

### 8.7 The full recipe library

Twenty-two more complete screens live in the documentation, each with a live
demo, the full markup, framework tabs, and production notes. When the user's
request matches one of these, read that page (append the slug to
`https://awc-ui.dev/recipes/`) instead of composing from scratch:

| Recipe slug | Screen |
|---|---|
| `two-factor-verification` | OTP entry, resend countdown, backup-codes dialog |
| `kpi-overview` | Stat tiles with sparklines + charts under a range switcher |
| `data-grid-console` | Bulk-action data table: filters, selection, undo |
| `server-fleet-status` | Table of hosts with meters, sparklines, status dots |
| `release-health-drilldown` | Table row → side-sheet master-detail with charts |
| `csv-import-wizard` | Stepper: upload, map columns, validate, import |
| `role-permission-assignment` | Transfer-list permissions with confirm dialog |
| `org-chart-explorer` | Searchable org chart with side-sheet profiles |
| `moderation-queue` | Verdict controls, confidence meter, card queue |
| `checkout-wizard` | Three-step commerce checkout with validation |
| `appointment-booking` | Date + time pickers inside a stepper flow |
| `survey-nps` | Paged survey using every input control |
| `notification-preferences` | Accordion × switch matrix, quiet hours |
| `app-shell` | Responsive rail/bar chrome, SSR-ready, zero CLS |
| `inbox-shell` | Three-zone list + reading-pane layout |
| `mobile-filter-sheet` | Bottom-sheet filters with applied-chip row |
| `product-reviews` | Rating input + per-star meter breakdown |
| `order-tracking` | Shipment timeline stepper with progress meter |
| `portfolio-markets` | Finance charts + holdings table + trade ticket |
| `pricing-page` | Plan cards, billing toggle, comparison table |
| `async-feedback-patterns` | Skeleton/progress/snackbar choreography rules |
| `charts-gallery` | Every chart component, live-retheming palette |

---

## §9 — Universal do's and don'ts

Apply to every component. Component-specific rules live in each manual.

### 9.1 API and styling

| ✅ Do | ❌ Don't |
|---|---|
| Use the custom element directly — it *is* the control, with its own `href` / `type` props | Wrap it in a native `<button>` / `<a>` (§7.3) |
| Set arrays and objects as **JS properties** (`el.items = [...]`) | Pass them as HTML attributes — they won't parse |
| Theme via `--md-<component>-*` properties, then `::part()` | Reach into shadow internals or override `.md-*` classes |
| Change appearance through design tokens (§4.1) | Hardcode hex colors, px spacing, shadows, or font stacks |
| Use `--md-sys-z-index-*` for your own overlays | Invent z-indexes that land under a menu |
| Use logical CSS (`margin-inline-start`, `inset-inline-end`) | Use `margin-left`, `right`, `padding-right` |
| Set `data-density` / `data-theme` / `dir` once, globally | Set them per component unless you mean a local exception |
| Listen to the component's `md*` events | Rely on native `click` — it fires even when the component's `disabled` / `loading` guard suppressed the action |
| Check the manual for the state prop name | Assume `checked`; `md-switch` uses `selected` |
| Render the element, then call its `@Method` | Call `.show()` on an element not yet in the DOM |

Toggling components flip their own state on activation and *then* emit. On
`md-button`, `mdClick` is cancelable — `preventDefault()` on it vetoes the
toggle and any navigation. `mdChange` fires after the flip and is not
cancelable. If a controlled parent rejects a change it must revert the child
explicitly.

`md-button` has a **toggle mode**: set `toggle` and the button flips its own
`selected` on each activation and exposes `aria-pressed`. `selected` is the
state, not the switch — set it for the initial pressed state and read it back
from `mdClick`'s `detail.selected`. Setting `toggle` and then styling
"pressed" yourself, or setting `selected` without `toggle` (which emits no
`aria-pressed` at all), both produce a control that lies to assistive tech.

### 9.2 Content and hierarchy

| ✅ Do | ❌ Don't |
|---|---|
| Use sentence case for all labels | Uppercase or title-case UI text |
| Keep one high-emphasis action per screen region | Compete `filled` buttons against each other |
| Use `soft-disabled` + `md-tooltip` for contextually-unavailable actions | Use `disabled` and remove it from tab order with no explanation |
| Localize every text prop and `aria-label` | Leave default English prop values in a translated app |

**`disabled` vs `soft-disabled`.** Both render the disabled appearance, but
`disabled` also removes the control from the tab order, so a keyboard or screen
reader user can never find out *why* it is off. `soft-disabled` keeps the
control focusable and announced while still blocking activation — which is what
lets an `md-tooltip` on it explain the gate. Use it whenever the reason is
informative rather than obvious.

Nesting rules — dialog inside a dialog, a component inside a native
`<button>` / `<a>`, tabs used as app navigation — live in §7.3.

### 9.3 Accessibility contract

The library ships WCAG 2.1 AA keyboard and ARIA wiring by default. These are the
rules you must not break:

1. **Every icon-only control gets an accessible name.** `md-icon-button`,
   `md-fab`, and icon-only `md-tab` are nameless without `aria-label` (or
   `aria-labelledby`). `md-fab` will also accept `label`, and warns in the
   console when it has neither.
2. **Every form field gets a `label` prop.** `placeholder` is not a label — it
   disappears on type.
3. **Don't break the tab order.** Every interactive control must be reachable by
   Tab and operable by Enter/Space. If you add `tabindex="-1"` to anything,
   have a reason.
4. **Don't write your own focus trap.** `md-dialog`, `md-bottom-sheet` and
   `md-side-sheet` trap focus while open and restore it to the trigger on
   close — including across shadow boundaries. Adding your own fights theirs.
5. **Don't wrap components in your own live regions.** `md-snackbar` announces
   itself (`politeness="polite"` by default, `"assertive"` when the message must
   interrupt). For an error that blocks the task, use `md-dialog`.
6. **Don't disable motion yourself.** Components honour
   `prefers-reduced-motion: reduce` internally.
7. **Directional icons do not auto-flip in RTL** — see §4.5.
8. **Re-verify contrast after overriding any colour role** — the library's AA
   testing covers the shipped palette only. The Theme Generator at
   <https://awc-ui.dev/theme-generator> runs the WCAG checks live (§4.2).

---

## §10 — Before you ship

1. **Every icon-only control has an accessible name.**
2. **Keyboard-only pass**: reach and operate every control; focus is always
   visible; no traps in dialogs/menus/sheets; focus returns to the trigger.
3. **Theme pass**: light *and* dark, if both are supported — and the attribute
   is set before first paint.
4. **RTL pass**, if an RTL locale ships — check directional icons specifically,
   and grep your own CSS for physical properties.
5. **Density pass** at the configured rung — no clipped labels, touch targets
   still adequate. Remember `density="0"` is inert; use
   `--md-sys-density-scale: 0` to opt a subtree out.
6. **Forms**: every control has a `name`; `required` blocks submit; `FormData`
   contains every field; reset works.
7. **No hardcoded strings** left in markup if the app is localized — including
   default prop values like `Search`, `Dismiss`, `No results`.
8. **No hardcoded design values** — grep the diff for hex colours, `px` radii,
   `box-shadow`, and `z-index` literals.
9. **Contrast** re-verified against AA if colour roles were overridden.
10. **SSR**: if server-rendered, confirm no hydration mismatch and that content
    is present with JS disabled.
11. **No shadow-internal CSS** — only tokens, custom properties, and `::part()`.
12. **Every `md-*` tag you emitted appears in §6.** If it doesn't, it doesn't
    exist.