md-search 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.
A full search surface, not a text field — a bar that opens into a results
panel. Two orthogonal axes (contained / divided × full-screen / docked)
cover all four spec-canonical configurations, with debounced querying, a
built-in voice affordance and an external trigger.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-search placeholder="Search mail" style="max-inline-size: 480px;">
<md-avatar slot="trailing" initials="MV" size="small"></md-avatar>
</md-search>import { MdAvatar, MdSearch } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdSearch placeholder="Search mail" style={{ maxInlineSize: '480px' }}>
<MdAvatar slot="trailing" initials="MV" size="small"></MdAvatar>
</MdSearch>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-search placeholder="Search mail" style="max-inline-size: 480px;">
<md-avatar slot="trailing" initials="MV" size="small"></md-avatar>
</md-search><script setup>
import '@awc-ui/core/define';
</script>
<template>
<md-search placeholder="Search mail" style="max-inline-size: 480px;">
<md-avatar slot="trailing" initials="MV" size="small"></md-avatar>
</md-search>
</template><script>
import '@awc-ui/core/define';
</script>
<md-search placeholder="Search mail" style="max-inline-size: 480px;">
<md-avatar slot="trailing" initials="MV" size="small"></md-avatar>
</md-search>
Already installed? See the
Installation guide for one-time package setup
(core + tokens, fonts). Each tab below shows two patterns for using
md-search 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-search) ─── -->
<script type="module">
import '@awc-ui/core/components/md-search';
</script>
<md-search></md-search>// ─── Option A: typed React wrapper (registers all components) ───
// Importing from '@awc-ui/react' calls defineCustomElements() as a
// side effect, so every md-* element becomes available in the browser.
import { MdSearch } from '@awc-ui/react';
export function Example() {
return <MdSearch></MdSearch>;
}
// ─── Option B: single import (tree-shake to only md-search) ───
// Skip the wrapper and use the raw custom element. Smallest bundle,
// but you lose typed props/events on JSX.
import '@awc-ui/core/components/md-search';
export function ExampleTreeShaken() {
return <md-search></md-search>;
}// ─── Option A: schema module (any md-* element accepted) ───
// Pair with `defineCustomElements(window)` in main.ts.
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@Component({
standalone: true,
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `<md-search></md-search>`,
})
export class ExampleComponent {}
// ─── Option B: typed directive (tree-shake friendly) ───
// Pair with `import '@awc-ui/core/components/md-search'` in main.ts.
import { Component } from '@angular/core';
import { MdSearch } from '@awc-ui/angular';
@Component({
standalone: true,
imports: [MdSearch],
template: `<md-search></md-search>`,
})
export class ExampleTreeShakenComponent {}<!-- ─── Option A: typed Vue wrapper (registers all components) ─── -->
<script setup lang="ts">
import { MdSearch } from '@awc-ui/vue';
</script>
<template>
<MdSearch></MdSearch>
</template>
<!-- ─── Option B: single import (tree-shake to only md-search) ─── -->
<script setup lang="ts">
import '@awc-ui/core/components/md-search';
</script>
<template>
<md-search></md-search>
</template><!-- ─── Option A: global registration (done once in main entry) ─── -->
<!-- main.ts: -->
<!-- import { defineCustomElements } from '@awc-ui/svelte'; -->
<!-- defineCustomElements(window); -->
<md-search></md-search>
<!-- ─── Option B: single import (tree-shake to only md-search) ─── -->
<script lang="ts">
import '@awc-ui/core/components/md-search';
</script>
<md-search></md-search>| Situation | Use instead |
|---|---|
| Filtering a list already on screen | md-text-field with a debounce |
| Picking one known value | md-select / md-autocomplete |
| Search as a secondary action in the header | md-app-bar’s search variant |
| A handful of preset filters | md-chip |
| Variant | Look | Use for |
|---|---|---|
contained | Expressive filled pill | Default. Search as a first-class surface |
divided | Baseline bar with a hairline divider | Search inside a denser app frame |
Both variants are available in both layouts, so contained × full-screen,
contained × docked, divided × full-screen and divided × docked are all
spec-canonical and all four are fully supported.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-search variant="contained" placeholder="Search mail"></md-search>
<md-search variant="divided" placeholder="Search mail"></md-search>import { MdSearch } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdSearch variant="contained" placeholder="Search mail"></MdSearch>
<MdSearch variant="divided" placeholder="Search mail"></MdSearch>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-search variant="contained" placeholder="Search mail"></md-search>
<md-search variant="divided" placeholder="Search mail"></md-search><script setup>
import '@awc-ui/core/define';
</script>
<template>
<md-search variant="contained" placeholder="Search mail"></md-search>
<md-search variant="divided" placeholder="Search mail"></md-search>
</template><script>
import '@awc-ui/core/define';
</script>
<md-search variant="contained" placeholder="Search mail"></md-search>
<md-search variant="divided" placeholder="Search mail"></md-search>| Layout | Opens as | Use for |
|---|---|---|
full-screen | Opaque full-viewport surface, scroll-locked and focus-trapped | Default. Mobile, and search-first surfaces |
docked | Anchored drawer under the bar, no scrim, no scroll lock | Desktop, and search beside other content |
While closed, layout="full-screen" (both contained and divided) shows a
compact search icon button — not the resting search bar. Clicking it (or
calling show()) opens the full-screen view. Override with trigger="bar" if
you need the legacy resting bar entry point, or slot a custom control in
slot="trigger".
When open is true, the results panel becomes an opaque surface that fills
the entire viewport edge-to-edge (inset: 0,
--md-sys-color-surface-container-low) and animates in. There is no scrim —
the open/close transition matches md-dialog’s full-screen pattern: the surface
and the bar rise together from translateY(30px) to 0 while fading in (no
dimmer, no scale, no container-grow, no gray bands). The bar floats on top of
the surface. Behaviour differs by variant:
| Variant | Bar chrome |
|---|---|
contained | Floating expressive pill bar, inset from the viewport edges by --md-search-fullscreen-bar-offset-inline (8px) / --md-search-fullscreen-bar-offset-block-start (16px). Its gutters sit over the opaque results surface, so they read as the surface — never a page band |
divided | Edge-bleed flat app-bar header (no rounding) flush to the viewport top (safe-area only) |
Because the panel surface fills inset: 0 (behind the bar too) there are no
gaps for the page to show through. Override the surface colour via
--md-search-fullscreen-results-color.
The component:
escape-closes)chevron_left
for the Expressive contained variant, arrow_back for the baseline
divided variant. Override per-instance via open-leading-icon.Tune contained full-screen spacing with --md-search-fullscreen-bar-offset-block-start,
--md-search-fullscreen-bar-offset-inline, and --md-search-fullscreen-bar-panel-gap.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-search variant="contained" layout="full-screen" placeholder="Search mail">
<md-list slot="results">
<md-list-item headline="Quarterly report.pdf" supporting-text="Shared folder"></md-list-item>
<md-list-item headline="Roadmap.key" supporting-text="Recent"></md-list-item>
</md-list>
</md-search>import { MdList, MdListItem, MdSearch } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdSearch variant="contained" layout="full-screen" placeholder="Search mail">
<MdList slot="results">
<MdListItem headline="Quarterly report.pdf" supportingText="Shared folder"></MdListItem>
<MdListItem headline="Roadmap.key" supportingText="Recent"></MdListItem>
</MdList>
</MdSearch>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-search variant="contained" layout="full-screen" placeholder="Search mail">
<md-list slot="results">
<md-list-item headline="Quarterly report.pdf" supporting-text="Shared folder"></md-list-item>
<md-list-item headline="Roadmap.key" supporting-text="Recent"></md-list-item>
</md-list>
</md-search><script setup>
import '@awc-ui/core/define';
</script>
<template>
<md-search variant="contained" layout="full-screen" placeholder="Search mail">
<md-list slot="results">
<md-list-item headline="Quarterly report.pdf" supporting-text="Shared folder"></md-list-item>
<md-list-item headline="Roadmap.key" supporting-text="Recent"></md-list-item>
</md-list>
</md-search>
</template><script>
import '@awc-ui/core/define';
</script>
<md-search variant="contained" layout="full-screen" placeholder="Search mail">
<md-list slot="results">
<md-list-item headline="Quarterly report.pdf" supporting-text="Shared folder"></md-list-item>
<md-list-item headline="Roadmap.key" supporting-text="Recent"></md-list-item>
</md-list>
</md-search>When layout="docked" and open is true, the results drawer anchors beneath
the bar with just enough offset to clear the bar’s focus ring (8px by default —
focus-ring offset + thickness + 3px), a content-driven height (no fixed
empty bay — the drawer shrinks to fit few rows), and a max-height of
min(400px, 60vh) (--md-search-panel-max-block-size).
The drawer’s inline width matches the open bar exactly — both are
inset by the focused expand margin (--md-search-expand-focused-inset, 12px),
so the bar and the drawer share identical left and right edges across the whole
responsive range (the overall component stays within the 360–720px clamp). The
component:
dismiss-on-outside-click)escape-closes)<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-search layout="docked" placeholder="Search mail">
<md-list slot="results">
<md-list-item headline="Quarterly report.pdf" supporting-text="Shared folder"></md-list-item>
<md-list-item headline="Roadmap.key" supporting-text="Recent"></md-list-item>
</md-list>
</md-search>import { MdList, MdListItem, MdSearch } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdSearch layout="docked" placeholder="Search mail">
<MdList slot="results">
<MdListItem headline="Quarterly report.pdf" supportingText="Shared folder"></MdListItem>
<MdListItem headline="Roadmap.key" supportingText="Recent"></MdListItem>
</MdList>
</MdSearch>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-search layout="docked" placeholder="Search mail">
<md-list slot="results">
<md-list-item headline="Quarterly report.pdf" supporting-text="Shared folder"></md-list-item>
<md-list-item headline="Roadmap.key" supporting-text="Recent"></md-list-item>
</md-list>
</md-search><script setup>
import '@awc-ui/core/define';
</script>
<template>
<md-search layout="docked" placeholder="Search mail">
<md-list slot="results">
<md-list-item headline="Quarterly report.pdf" supporting-text="Shared folder"></md-list-item>
<md-list-item headline="Roadmap.key" supporting-text="Recent"></md-list-item>
</md-list>
</md-search>
</template><script>
import '@awc-ui/core/define';
</script>
<md-search layout="docked" placeholder="Search mail">
<md-list slot="results">
<md-list-item headline="Quarterly report.pdf" supporting-text="Shared folder"></md-list-item>
<md-list-item headline="Roadmap.key" supporting-text="Recent"></md-list-item>
</md-list>
</md-search>The MD3 spec lists the following anatomy items. They map to the API surface as follows:
| Spec item | API |
|---|---|
| Search bar container | [part="bar"] |
| Leading icon | leading-icon prop, or slot="leading" for full replacement |
| Supporting text (placeholder hint) | placeholder prop |
| Trailing icon and avatar (optional) | slot="trailing" (any number / combination of icon-buttons + avatar) |
| Input text | value prop / mdInput event / [part="input"] |
| Container for search suggestions or results | slot="results", rendered inside [part="panel"] → [part="panel-body"] → [part="results-host"] → [part="results-viewport"] |
Divider (divided variant) | [part="divider"] |
| Search icon trigger (full-screen closed) | [part="trigger"] / [part="trigger-button"] or slot="trigger" |
The spec calls out four canonical trailing clusters. All are produced just by filling the trailing slot:
<!-- 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-search>
<md-icon-button slot="trailing" icon="mic" aria-label="Voice search"></md-icon-button>
<md-avatar slot="trailing" name="Jane Doe"></md-avatar>
</md-search>// Icons need the Material Symbols stylesheet in index.html — see Installation.
import { MdAvatar, MdIconButton, MdSearch } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdSearch>
<MdIconButton slot="trailing" icon="mic" aria-label="Voice search"></MdIconButton>
<MdAvatar slot="trailing" name="Jane Doe"></MdAvatar>
</MdSearch>
</>
);
}// Icons need the Material Symbols stylesheet in index.html — see Installation.
// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-search>
<md-icon-button slot="trailing" icon="mic" aria-label="Voice search"></md-icon-button>
<md-avatar slot="trailing" name="Jane Doe"></md-avatar>
</md-search><script setup>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import '@awc-ui/core/define';
</script>
<template>
<md-search>
<md-icon-button slot="trailing" icon="mic" aria-label="Voice search"></md-icon-button>
<md-avatar slot="trailing" name="Jane Doe"></md-avatar>
</md-search>
</template><script>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import '@awc-ui/core/define';
</script>
<md-search>
<md-icon-button slot="trailing" icon="mic" aria-label="Voice search"></md-icon-button>
<md-avatar slot="trailing" name="Jane Doe"></md-avatar>
</md-search>The component renders a built-in clear (×) button automatically as soon as the
input has a value. Set show-clear-button="false" to opt out.
Set voice-search to render a built-in microphone button in the trailing
cluster, wired to the browser’s Web Speech API
(SpeechRecognition / webkitSpeechRecognition).
Clicking the mic starts recognition and streams the interim + final transcript
straight into the input — firing the normal mdInput / mdSearch flow exactly
as if the text had been typed — and the mic shows a pulsing “listening” state
while active. Recognition stops automatically on the final result, on
Escape, or when the panel closes. Listen to the mdVoice event for the raw
transcript stream, style the button via ::part(voice-button), or drive it
programmatically with the startVoice() / stopVoice() methods. For a fully
custom mic, slot your own <md-icon-button slot="trailing"> instead.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-search layout="docked" voice-search placeholder="Search by voice"></md-search>
<script type="module">
var el = document.querySelector('[data-voice]');
var log = document.querySelector('[data-voice-log]');
el.addEventListener('mdVoice', function (e) {
log.textContent = (e.detail.final ? 'final: ' : 'interim: ') + JSON.stringify(e.detail.value);
});
</script>import { useEffect } from 'react';
import { MdSearch } from '@awc-ui/react';
export function Demo() {
useEffect(() => {
var el = document.querySelector('[data-voice]');
var log = document.querySelector('[data-voice-log]');
el.addEventListener('mdVoice', function (e) {
log.textContent = (e.detail.final ? 'final: ' : 'interim: ') + JSON.stringify(e.detail.value);
});
}, []);
return (
<>
<MdSearch layout="docked" voiceSearch placeholder="Search by voice"></MdSearch>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
// app.component.ts
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';
@Component({
selector: 'app-demo',
templateUrl: './app.component.html',
})
export class DemoComponent implements AfterViewInit {
ngAfterViewInit() {
var el = document.querySelector('[data-voice]');
var log = document.querySelector('[data-voice-log]');
el.addEventListener('mdVoice', function (e) {
log.textContent = (e.detail.final ? 'final: ' : 'interim: ') + JSON.stringify(e.detail.value);
});
}
}
<!-- app.component.html -->
<md-search layout="docked" voice-search placeholder="Search by voice"></md-search><script setup>
import { onMounted } from 'vue';
import '@awc-ui/core/define';
onMounted(() => {
var el = document.querySelector('[data-voice]');
var log = document.querySelector('[data-voice-log]');
el.addEventListener('mdVoice', function (e) {
log.textContent = (e.detail.final ? 'final: ' : 'interim: ') + JSON.stringify(e.detail.value);
});
});
</script>
<template>
<md-search layout="docked" voice-search placeholder="Search by voice"></md-search>
</template><script>
import { onMount } from 'svelte';
import '@awc-ui/core/define';
onMount(() => {
var el = document.querySelector('[data-voice]');
var log = document.querySelector('[data-voice-log]');
el.addEventListener('mdVoice', function (e) {
log.textContent = (e.detail.final ? 'final: ' : 'interim: ') + JSON.stringify(e.detail.value);
});
});
</script>
<md-search layout="docked" voice-search placeholder="Search by voice"></md-search>The built-in icon trigger and slot="trigger" both have to live inside
md-search. When the opener belongs somewhere else on the page — an app-bar
icon, an overflow menu item, a keyboard-shortcut hint — point trigger-for at
it instead. The component wires that element rather than rendering it:
aria-haspopup="dialog" is set on it, and aria-expanded tracks open.Use a real button. Activation rides on click, which native buttons,
md-button and md-icon-button already emit for Enter and Space.
<md-icon-button id="app-bar-search" icon="search" aria-label="Search files"></md-icon-button><md-search trigger-for="#app-bar-search"></md-search><!-- index.html <head> — the icon font the components draw from -->
<link rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=swap">
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-icon-button id="app-bar-search" icon="search" aria-label="Search files"></md-icon-button>
<md-search trigger-for="#app-bar-search" layout="docked" placeholder="Search files">
<md-list slot="results">
<md-list-item headline="Quarterly report.pdf"></md-list-item>
<md-list-item headline="Roadmap.key"></md-list-item>
</md-list>
</md-search>import { useState } from 'react';
import { MdSearch, MdIconButton, MdList, MdListItem } from '@awc-ui/react';
export function FileSearch() {
// A ref is the natural handle in React, so pass the element itself.
const [trigger, setTrigger] = useState<HTMLElement | null>(null);
return (
<>
<MdIconButton ref={setTrigger} icon="search" aria-label="Search files" />
<MdSearch triggerElement={trigger} layout="docked" placeholder="Search files">
<MdList slot="results">
<MdListItem headline="Quarterly report.pdf" />
<MdListItem headline="Roadmap.key" />
</MdList>
</MdSearch>
</>
);
}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@Component({
selector: 'app-file-search',
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<md-icon-button #trigger icon="search" aria-label="Search files"></md-icon-button>
<md-search [triggerElement]="trigger" layout="docked" placeholder="Search files">
<md-list slot="results">
<md-list-item headline="Quarterly report.pdf"></md-list-item>
<md-list-item headline="Roadmap.key"></md-list-item>
</md-list>
</md-search>
`,
})
export class FileSearchComponent {}<script setup lang="ts">
import { ref } from 'vue';
const trigger = ref<HTMLElement | null>(null);
</script>
<template>
<md-icon-button ref="trigger" icon="search" aria-label="Search files" />
<!-- camelCase: triggerElement is a property with no attribute form, so a
kebab binding would be written out as a useless string attribute. -->
<md-search :triggerElement="trigger" layout="docked" placeholder="Search files">
<md-list slot="results">
<md-list-item headline="Quarterly report.pdf" />
<md-list-item headline="Roadmap.key" />
</md-list>
</md-search>
</template><script lang="ts">
let trigger: HTMLElement;
</script>
<md-icon-button bind:this={trigger} icon="search" aria-label="Search files"></md-icon-button>
<md-search triggerElement={trigger} layout="docked" placeholder="Search files">
<md-list slot="results">
<md-list-item headline="Quarterly report.pdf"></md-list-item>
<md-list-item headline="Roadmap.key"></md-list-item>
</md-list>
</md-search>| State | Token usage |
|---|---|
| Enabled | Container: --md-sys-color-surface-container-high |
| Hover | State layer at --md-sys-state-hover-state-layer-opacity |
| Focus | No state-layer wash — the layer is zeroed and a --md-search-focus-indicator-color ring is drawn instead (3px at 2px offset, keyboard-only). The bar’s side inset springs from 24px → 12px (Expressive motion) |
| Pressed | md-ripple plus state layer at --md-search-pressed-state-layer-opacity (literal 0.1) |
| Disabled | Bar at 38% opacity, ripple disabled, input disabled |
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<div style="display:grid; gap:20px; max-inline-size:420px;">
<md-search layout="docked" placeholder="Enabled"></md-search>
<md-search layout="docked" placeholder="With a value" value="material design"></md-search>
<md-search layout="docked" placeholder="Loading" value="design" loading></md-search>
<md-search layout="docked" placeholder="Disabled" disabled></md-search>
</div>import { MdSearch } from '@awc-ui/react';
export function Demo() {
return (
<>
<div style={{ display: 'grid', gap: '20px', maxInlineSize: '420px' }}>
<MdSearch layout="docked" placeholder="Enabled"></MdSearch>
<MdSearch layout="docked" placeholder="With a value" value="material design"></MdSearch>
<MdSearch layout="docked" placeholder="Loading" value="design" loading></MdSearch>
<MdSearch layout="docked" placeholder="Disabled" disabled></MdSearch>
</div>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<div style="display:grid; gap:20px; max-inline-size:420px;">
<md-search layout="docked" placeholder="Enabled"></md-search>
<md-search layout="docked" placeholder="With a value" value="material design"></md-search>
<md-search layout="docked" placeholder="Loading" value="design" loading></md-search>
<md-search layout="docked" placeholder="Disabled" disabled></md-search>
</div><script setup>
import '@awc-ui/core/define';
</script>
<template>
<div style="display:grid; gap:20px; max-inline-size:420px;">
<md-search layout="docked" placeholder="Enabled"></md-search>
<md-search layout="docked" placeholder="With a value" value="material design"></md-search>
<md-search layout="docked" placeholder="Loading" value="design" loading></md-search>
<md-search layout="docked" placeholder="Disabled" disabled></md-search>
</div>
</template><script>
import '@awc-ui/core/define';
</script>
<div style="display:grid; gap:20px; max-inline-size:420px;">
<md-search layout="docked" placeholder="Enabled"></md-search>
<md-search layout="docked" placeholder="With a value" value="material design"></md-search>
<md-search layout="docked" placeholder="Loading" value="design" loading></md-search>
<md-search layout="docked" placeholder="Disabled" disabled></md-search>
</div>| Event | Detail | Fires when… |
|---|---|---|
mdInput | { value: string } | The user types into the input or clears it via the × button (immediate, every keystroke) |
mdSearch | { value: string } | A query is ready to fetch — debounced/throttled + distinct-until-changed (trimmed). The event to wire your async fetch to. |
mdSubmit | { value: string } | The user presses Enter inside the input |
mdChange | { value: string } | Focus leaves the input and the value differs from focus-time |
mdOpen | void | The panel transitions from closed → open |
mdClose | void | The panel transitions from open → closed |
mdClear | void | The user activates the built-in clear (×) button |
mdVoice | { value: string; final: boolean } | Voice search is active and a transcript chunk streamed in (final true on the recognised result) |
mdLeadingIconClick | MdSearchLeadingIconClickDetail | The interactive leading affordance is clicked — the morphing back/dismiss button while open, or a custom slotted leading icon. Additive (the default back button still dismisses). The closed resting search glyph is not a click target, so it never emits. |
mdTrailingIconClick | MdSearchTrailingIconClickDetail | A slotted trailing affordance is clicked. The built-in clear (×) and voice (mic) buttons emit mdClear / mdVoice instead and never trigger this. Only fires when slotted trailing content is present and was the click target. |
<md-search id="docs-search" layout="docked" debounce="300" placeholder="Search the docs…"></md-search>
<script type="module">
const search = document.getElementById('docs-search');
search.addEventListener('mdInput', (e) => {
const { value } = e.detail; // every keystroke
console.log('typing', value);
});
search.addEventListener('mdSearch', (e) => {
const { value } = e.detail; // debounced + de-duplicated
console.log('fetch', value);
});
search.addEventListener('mdSubmit', (e) => {
console.log('submit', e.detail.value); // Enter inside the input
});
search.addEventListener('mdClear', () => {
console.log('cleared');
});
</script>import { MdSearch } from '@awc-ui/react';
export function DocsSearch() {
return (
<MdSearch
layout="docked"
debounce={300}
placeholder="Search the docs…"
onMdInput={(e) => console.log('typing', e.detail.value)}
onMdSearch={(e) => console.log('fetch', e.detail.value)}
onMdSubmit={(e) => console.log('submit', e.detail.value)}
onMdClear={() => console.log('cleared')}
/>
);
}import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@Component({
selector: 'app-docs-search',
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<md-search
layout="docked"
debounce="300"
placeholder="Search the docs…"
(mdInput)="onInput($event)"
(mdSearch)="onSearch($event)"
(mdSubmit)="onSubmit($event)"
(mdClear)="onClear()"
></md-search>
`,
})
export class DocsSearchComponent {
onInput(e: CustomEvent<{ value: string }>) { console.log('typing', e.detail.value); }
onSearch(e: CustomEvent<{ value: string }>) { console.log('fetch', e.detail.value); }
onSubmit(e: CustomEvent<{ value: string }>) { console.log('submit', e.detail.value); }
onClear() { console.log('cleared'); }
}<script setup lang="ts">
function onInput(e: CustomEvent) { console.log('typing', e.detail.value); }
function onSearch(e: CustomEvent) { console.log('fetch', e.detail.value); }
function onSubmit(e: CustomEvent) { console.log('submit', e.detail.value); }
function onClear() { console.log('cleared'); }
</script>
<template>
<md-search
layout="docked"
debounce="300"
placeholder="Search the docs…"
@mdInput="onInput"
@mdSearch="onSearch"
@mdSubmit="onSubmit"
@mdClear="onClear"
/>
</template><script lang="ts">
function onInput(e: CustomEvent) { console.log('typing', e.detail.value); }
function onSearch(e: CustomEvent) { console.log('fetch', e.detail.value); }
function onSubmit(e: CustomEvent) { console.log('submit', e.detail.value); }
function onClear() { console.log('cleared'); }
</script>
<md-search
layout="docked"
debounce="300"
placeholder="Search the docs…"
on:mdInput={onInput}
on:mdSearch={onSearch}
on:mdSubmit={onSubmit}
on:mdClear={onClear}
/>mdSearch and not mdInputmdInput is a keystroke: it fires on every character, including the ones the
user is about to delete. mdSearch is a query: it is debounced, throttled and
de-duplicated on the trimmed value, so wiring a fetch to it costs one request
per intention rather than one per key.
Wire an asynchronous fetch to the mdSearch event rather than mdInput.
mdSearch is rate-limited so you don’t hammer the server on every keystroke:
debounce (ms) — waits for a typing pause before emitting. 0 (default)
emits on every change.throttle (ms) — a maxWait safety net: during sustained typing where the
debounce never settles, force an emit at least this often so the user still
sees interim results. 0 (default) disables it; only meaningful alongside a
non-zero debounce.Enter or clearing the field flushes immediately.Set loading while the request is in flight to show the M3 loading
indicator (the looping shape-morph) in the bar’s trailing cluster; it replaces
the clear button until the fetch resolves and is announced to assistive tech
via the live region (loading-label, default “Searching”). The swap is an
expressive cross-fade — the loading indicator and clear-× morph (opacity +
scale + rotate) into one another in both directions over the same fixed-size
box, so the trailing cluster never jumps.
<md-search id="product-search" layout="docked" debounce="300" throttle="1000" placeholder="Search products">
<md-list slot="results" id="product-results"></md-list>
</md-search>
<script type="module">
const search = document.getElementById('product-search');
let token = 0;
search.addEventListener('mdSearch', async (e) => {
const { value } = e.detail;
const ticket = ++token; // ignore out-of-order responses
if (!value) {
search.loading = false;
renderResults([]);
return;
}
search.loading = true;
try {
const results = await fetchFromServer(value);
if (ticket !== token) return; // a newer query superseded this one
renderResults(results);
} finally {
if (ticket === token) search.loading = false;
}
});
</script>import { useRef, useState } from 'react';
import { MdSearch, MdList, MdListItem } from '@awc-ui/react';
export function ProductSearch() {
const [rows, setRows] = useState<string[]>([]);
const [loading, setLoading] = useState(false);
const token = useRef(0);
return (
<MdSearch
layout="docked"
debounce={300}
throttle={1000}
loading={loading}
placeholder="Search products"
onMdSearch={async (e) => {
const ticket = ++token.current;
const { value } = e.detail;
if (!value) { setRows([]); return; }
setLoading(true);
const hits = await fetchFromServer(value);
if (ticket !== token.current) return; // a newer query superseded this one
setRows(hits);
setLoading(false);
}}
>
<MdList slot="results">
{rows.map((row) => <MdListItem key={row} headline={row} />)}
</MdList>
</MdSearch>
);
}import { CommonModule } from '@angular/common';
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@Component({
selector: 'app-product-search',
standalone: true,
imports: [CommonModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<md-search
layout="docked"
debounce="300"
throttle="1000"
[loading]="loading"
placeholder="Search products"
(mdSearch)="onSearch($event)"
>
<md-list slot="results">
<md-list-item *ngFor="let row of rows" [headline]="row"></md-list-item>
</md-list>
</md-search>
`,
})
export class ProductSearchComponent {
rows: string[] = [];
loading = false;
private token = 0;
async onSearch(e: CustomEvent<{ value: string }>) {
const ticket = ++this.token;
const { value } = e.detail;
if (!value) { this.rows = []; return; }
this.loading = true;
const hits = await fetchFromServer(value);
if (ticket !== this.token) return; // a newer query superseded this one
this.rows = hits;
this.loading = false;
}
}<script setup lang="ts">
import { ref } from 'vue';
const rows = ref<string[]>([]);
const loading = ref(false);
let token = 0;
async function onSearch(e: CustomEvent) {
const ticket = ++token;
const { value } = e.detail;
if (!value) { rows.value = []; return; }
loading.value = true;
const hits = await fetchFromServer(value);
if (ticket !== token) return; // a newer query superseded this one
rows.value = hits;
loading.value = false;
}
</script>
<template>
<md-search
layout="docked"
debounce="300"
throttle="1000"
:loading="loading"
placeholder="Search products"
@mdSearch="onSearch"
>
<md-list slot="results">
<md-list-item v-for="row in rows" :key="row" :headline="row" />
</md-list>
</md-search>
</template><script lang="ts">
let rows: string[] = [];
let loading = false;
let token = 0;
async function onSearch(e: CustomEvent) {
const ticket = ++token;
const { value } = e.detail;
if (!value) { rows = []; return; }
loading = true;
const hits = await fetchFromServer(value);
if (ticket !== token) return; // a newer query superseded this one
rows = hits;
loading = false;
}
</script>
<md-search
layout="docked"
debounce="300"
throttle="1000"
{loading}
placeholder="Search products"
on:mdSearch={onSearch}
>
<md-list slot="results">
{#each rows as row (row)}
<md-list-item headline={row}></md-list-item>
{/each}
</md-list>
</md-search>| Property | Attribute | Type | Default | Reflects |
|---|---|---|---|---|
variant | variant | 'contained' | 'divided' | 'contained' | Yes |
layout | layout | 'full-screen' | 'docked' | 'full-screen' | Yes |
trigger | trigger | 'bar' | 'icon' | — | Yes |
triggerIcon | trigger-icon | string | 'search' | — |
triggerFor | trigger-for | string | '' | — |
triggerElement | JS only | HTMLElement | — | — |
fullWidth | full-width | boolean | false | Yes |
value | value | string | '' | Yes |
placeholder | placeholder | string | 'Search' | — |
open | open | boolean | false | Yes |
disabled | disabled | boolean | false | Yes |
elevation | elevation | 0 | 1 | 2 | 3 | 4 | 5 | 0 | Yes |
leadingIcon | leading-icon | string | 'search' | — |
openLeadingIcon | open-leading-icon | string | — | — |
showClearButton | show-clear-button | boolean | true | — |
voiceSearch | voice-search | boolean | false | — |
escapeCloses | escape-closes | boolean | true | — |
dismissOnOutsideClick | dismiss-on-outside-click | boolean | true | — |
scrollShadow | scroll-shadow | boolean | true | Yes |
maxBlockSize | max-block-size | string | '' | — |
inputAriaLabel | input-aria-label | string | '' | — |
initialFocus | initial-focus | 'auto' | 'input' | 'leading' | 'auto' | — |
announceResults | announce-results | boolean | true | — |
resultsLabel | results-label | string | '{count} results available' | — |
noResultsLabel | no-results-label | string | 'No results available' | — |
loading | loading | boolean | false | Yes |
debounce | debounce | number | 0 | — |
throttle | throttle | number | 0 | — |
loadingLabel | loading-label | string | 'Searching' | — |
density | density | 0 | -1 | -2 | -3 | -4 | 0 | Yes |
| Method | Parameters |
|---|---|
show() | none |
close() | none |
toggle() | none |
focusInput() | none |
startVoice() | none |
stopVoice() | none |
| Slot | Description |
|---|---|
trigger | Replace the built-in search icon trigger (full-screen) |
leading | Replace the leading icon |
loader | Replace the in-bar loading indicator shown while `loading` |
trailing | Trailing icon buttons + avatar (any combination) |
results | Suggestions / results list |
Override on the host element for per-instance theming:
| Property | Description |
|---|---|
--md-search-container-color | Bar background (#ECE6F0 / surface-container-high) |
--md-search-container-elevation | Custom bar shadow — overrides the |
--md-search-leading-icon-color | Leading / open-back icon (#1D1B20 / on-surface) |
--md-search-trailing-icon-color | Trailing + clear icons (#49454F / on-surface-variant) |
--md-search-icon-color | Alias for trailing icon colour (legacy) |
--md-search-loading-color | In-bar loading indicator shape colour |
--md-search-container-shape | Bar border-radius |
--md-search-container-height | Bar block-size — docked / contained (56px) |
--md-search-icon-button-size | Leading / trailing icon tap target (48px) |
--md-search-leading-icon-label-gap | Tap target → label gap (4px) |
--md-search-trailing-icon-label-gap | Label → tap target gap (4px) |
--md-search-container-padding-inline | Extra inline padding on the bar (0px) |
--md-search-container-padding-inline-focused | Inline padding on the bar when focused / open |
--md-search-leading-padding-inline | Space from the bar's leading edge |
--md-search-trailing-padding-inline | Space from the bar's trailing edge |
--md-search-input-color | Input text colour |
--md-search-input-font-family | Input typescale font (body-large) |
--md-search-input-font-size | Input typescale size (16px) |
--md-search-input-line-height | Input typescale line height (24px) |
--md-search-input-font-weight | Input typescale weight (400) |
--md-search-input-letter-spacing | Input typescale tracking (0.5px) |
--md-search-placeholder-color | Placeholder / supporting text colour |
--md-search-state-layer-color | Hover/press wash (#1D1B20 / on-surface) |
--md-search-focus-indicator-color | Focus ring colour (#625B71 / secondary) |
--md-search-focus-indicator-offset | Focus ring offset (2px) |
--md-search-avatar-size | Avatar diameter when slotted (40px) |
--md-search-panel-min-block-size | Docked panel floor — 0 = shrink to |
--md-search-panel-max-block-size | Docked panel maximum height — |
--md-search-max-block-size | Open surface max height — full-screen |
--md-search-docked-panel-shape | Contained docked results card radius |
--md-search-panel-offset | Docked panel gap fallback (divided / generic 4px) |
--md-search-bar-fullscreen-color | Bar background in full-screen open |
--md-search-fullscreen-results-color | Opaque results surface in full-screen |
--md-search-divider-color | Hairline colour (divided variant) |
--md-search-divider-thickness | Hairline thickness (1px) |
--md-search-fullscreen-bar-panel-gap | Contained full-screen: gap between |
--md-search-empty-color | No-results message colour |
--md-search-empty-padding-block | No-results vertical padding |
--md-search-empty-padding-inline | No-results horizontal padding |
--md-search-expand-inset | Docked/inline bar RESTING side |
--md-search-expand-focused-inset | Docked/inline bar FOCUSED side |
--md-search-expand-duration | Springy expand duration (medium4 / 400ms) |
--md-search-expand-easing | Springy expand easing — back-out |
--md-search-panel-motion-duration | Docked panel + bar transition |
--md-search-fullscreen-expand-duration | Full-screen modal enter duration |
--md-search-fullscreen-expand-easing | Full-screen enter easing |
--md-search-fullscreen-collapse-easing | Full-screen exit easing |
--md-search-divided-fullscreen-bar-height | — |
--md-search-container-min-inline-size | — |
--md-search-container-max-inline-size | — |
--md-search-icon-size | — |
--md-search-focus-indicator-thickness | — |
--md-search-panel-padding-block-start | — |
Style internal elements through shadow DOM with ::part():
| Part | Description |
|---|---|
trailing | Trailing slot container (icon buttons / avatar) |
trigger | Icon-button entry point (full-screen closed state) |
trigger-button | Built-in search icon button (when trigger slot empty) |
leading | Leading icon container |
leading-state-layer | — |
clear-button | Built-in clear (×) button |
voice-button | Built-in voice-search mic button (when `voice-search` |
loading | In-bar loading indicator (shown while `loading`) |
status | Visually-hidden polite live region (result announcements) |
bar | Search bar container |
state-layer | Hover / focus / press overlay on the bar |
input | Native <input> |
panel | Results panel — full-viewport surface (full-screen) / |
divider | Hairline between bar and panel (visible in `divided`) |
panel-body | Flex region that sizes the scroll viewport |
empty | Visible no-results message (open + query + zero rows) |
results-host | — |
results-viewport | Inner scroll viewport (md-scroll-shadow) |
loading-shape | Morphing shape inside the loading indicator |
<input> has role="combobox" with
aria-autocomplete="list" (APG popup search / autosuggest pattern). Per the
M3 labeling guidance the hinted search text describes the bar, so the
accessible name defaults to placeholder; set input-aria-label only when
the visible hint and the accessible name should differ. aria-expanded
mirrors open and aria-controls points to the results panel.md-list + md-list-item so screen readers announce
them as a list. A visually-hidden polite live region (role="status",
part="status") announces the count whenever the slotted list changes while
the panel is open. Customize with results-label ({count} token) and
no-results-label, or disable it with announce-results="false".part="empty") shows the same no-results-label copy (default
"No results available"); the hidden live region defers to it so the string
is never announced twice.initial-focus="auto" / "input"); use initial-focus="leading" to
focus the leading button instead. Focus is restored on close.role="dialog". aria-modal is "true" only
when layout is full-screen and open is true; docked panels are
popup-style and stay non-modal so the rest of the page stays traversable.aria-label="Close search" and the clear (×) button "Clear search". Label
any slotted leading / trailing icon buttons yourself.| Keys | Action |
|---|---|
Tab / Shift+Tab | Navigate between interactive elements. In the full-screen layout focus is trapped to the bar + panel + slotted buttons and cycles without leaving the dialog. |
Space / Enter | Activate the text field for input. While closed they open the search view (focus alone never opens it); while open, Enter submits the query via mdSubmit. |
Arrow Down / Arrow Up | Navigate between slotted result items. Arrow Down from the field steps into the first item; Arrow Up from the first item returns to the field. Result rows are given a roving tabindex so focus can land on them. |
Escape | Close the panel (configurable via escape-closes). |
Tab into the bar, type a letter, then use Arrow Down to walk into the results
and Escape to close. A screen reader announces the row count from the hidden
live region as the list changes.
<!-- 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-search layout="docked" placeholder="Search contacts" input-aria-label="Search contacts" results-label="{count} contacts available" style="max-inline-size:460px;">
<md-icon-button slot="trailing" icon="tune" aria-label="Filters"></md-icon-button>
<md-list slot="results">
<md-list-item headline="Amelia Novak" supporting-text="amelia@example.com"></md-list-item>
<md-list-item headline="Bruno Sadiq" supporting-text="bruno@example.com"></md-list-item>
<md-list-item headline="Carmen Diaz" supporting-text="carmen@example.com"></md-list-item>
</md-list>
</md-search>// Icons need the Material Symbols stylesheet in index.html — see Installation.
import { MdIconButton, MdList, MdListItem, MdSearch } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdSearch layout="docked" placeholder="Search contacts" inputAriaLabel="Search contacts" resultsLabel="{count} contacts available" style={{ maxInlineSize: '460px' }}>
<MdIconButton slot="trailing" icon="tune" aria-label="Filters"></MdIconButton>
<MdList slot="results">
<MdListItem headline="Amelia Novak" supportingText="amelia@example.com"></MdListItem>
<MdListItem headline="Bruno Sadiq" supportingText="bruno@example.com"></MdListItem>
<MdListItem headline="Carmen Diaz" supportingText="carmen@example.com"></MdListItem>
</MdList>
</MdSearch>
</>
);
}// Icons need the Material Symbols stylesheet in index.html — see Installation.
// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-search layout="docked" placeholder="Search contacts" input-aria-label="Search contacts" results-label="{count} contacts available" style="max-inline-size:460px;">
<md-icon-button slot="trailing" icon="tune" aria-label="Filters"></md-icon-button>
<md-list slot="results">
<md-list-item headline="Amelia Novak" supporting-text="amelia@example.com"></md-list-item>
<md-list-item headline="Bruno Sadiq" supporting-text="bruno@example.com"></md-list-item>
<md-list-item headline="Carmen Diaz" supporting-text="carmen@example.com"></md-list-item>
</md-list>
</md-search><script setup>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import '@awc-ui/core/define';
</script>
<template>
<md-search layout="docked" placeholder="Search contacts" input-aria-label="Search contacts" results-label="{count} contacts available" style="max-inline-size:460px;">
<md-icon-button slot="trailing" icon="tune" aria-label="Filters"></md-icon-button>
<md-list slot="results">
<md-list-item headline="Amelia Novak" supporting-text="amelia@example.com"></md-list-item>
<md-list-item headline="Bruno Sadiq" supporting-text="bruno@example.com"></md-list-item>
<md-list-item headline="Carmen Diaz" supporting-text="carmen@example.com"></md-list-item>
</md-list>
</md-search>
</template><script>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import '@awc-ui/core/define';
</script>
<md-search layout="docked" placeholder="Search contacts" input-aria-label="Search contacts" results-label="{count} contacts available" style="max-inline-size:460px;">
<md-icon-button slot="trailing" icon="tune" aria-label="Filters"></md-icon-button>
<md-list slot="results">
<md-list-item headline="Amelia Novak" supporting-text="amelia@example.com"></md-list-item>
<md-list-item headline="Bruno Sadiq" supporting-text="bruno@example.com"></md-list-item>
<md-list-item headline="Carmen Diaz" supporting-text="carmen@example.com"></md-list-item>
</md-list>
</md-search>The trailing tune button is yours to label — the built-in clear (×) and mic
already carry their own names. Verified against axe-core with zero WCAG 2.1 AA
violations.
RTL — every box metric is a logical property (inset-inline,
padding-inline, margin-inline-start…), so the bar mirrors automatically
inside any dir="rtl" ancestor. The leading and trailing slots swap visually
with no consumer-side adjustment. See RTL.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<div style="display:grid;grid-template-columns:auto 1fr;gap:14px 16px;align-items:center;">
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">ltr</span>
<div dir="ltr"><md-search layout="docked" placeholder="Search mail" style="max-inline-size: 440px;"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search></div>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">rtl</span>
<div dir="rtl"><md-search layout="docked" placeholder="بحث في البريد" style="max-inline-size: 440px;"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search></div>
</div>import { MdAvatar, MdSearch } from '@awc-ui/react';
export function Demo() {
return (
<>
<div style={{ display: 'grid', gridTemplateColumns: 'auto 1fr', gap: '14px 16px', alignItems: 'center' }}>
<span style={{ inlineSize: '3.5rem', opacity: '.65', fontSize: '.75rem', fontFamily: 'ui-monospace,monospace' }}>ltr</span>
<div dir="ltr"><MdSearch layout="docked" placeholder="Search mail" style={{ maxInlineSize: '440px' }}><MdAvatar slot="trailing" initials="AL" size="small"></MdAvatar></MdSearch></div>
<span style={{ inlineSize: '3.5rem', opacity: '.65', fontSize: '.75rem', fontFamily: 'ui-monospace,monospace' }}>rtl</span>
<div dir="rtl"><MdSearch layout="docked" placeholder="بحث في البريد" style={{ maxInlineSize: '440px' }}><MdAvatar slot="trailing" initials="AL" size="small"></MdAvatar></MdSearch></div>
</div>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<div style="display:grid;grid-template-columns:auto 1fr;gap:14px 16px;align-items:center;">
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">ltr</span>
<div dir="ltr"><md-search layout="docked" placeholder="Search mail" style="max-inline-size: 440px;"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search></div>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">rtl</span>
<div dir="rtl"><md-search layout="docked" placeholder="بحث في البريد" style="max-inline-size: 440px;"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search></div>
</div><script setup>
import '@awc-ui/core/define';
</script>
<template>
<div style="display:grid;grid-template-columns:auto 1fr;gap:14px 16px;align-items:center;">
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">ltr</span>
<div dir="ltr"><md-search layout="docked" placeholder="Search mail" style="max-inline-size: 440px;"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search></div>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">rtl</span>
<div dir="rtl"><md-search layout="docked" placeholder="بحث في البريد" style="max-inline-size: 440px;"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search></div>
</div>
</template><script>
import '@awc-ui/core/define';
</script>
<div style="display:grid;grid-template-columns:auto 1fr;gap:14px 16px;align-items:center;">
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">ltr</span>
<div dir="ltr"><md-search layout="docked" placeholder="Search mail" style="max-inline-size: 440px;"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search></div>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">rtl</span>
<div dir="rtl"><md-search layout="docked" placeholder="بحث في البريد" style="max-inline-size: 440px;"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search></div>
</div>md-search takes a local density rung (-1 … -4) and also honours a global
data-density ancestor; a local -1 … -4 rung wins over the inherited one.
0 is the undensified baseline you get when nothing sets a rung. Each rung
takes 4px off the bar (56px at 0, floor 40px), 4px off the leading /
trailing tap targets (48px at 0, floor 32px), 1px off the icon glyphs
(24px at 0, floor 18px) and 0.5px off the input type (16px at 0, floor
13px). Override any of it directly with --md-search-container-height or
--md-search-icon-button-size.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<div style="display:grid;grid-template-columns:auto 1fr;gap:14px 16px;align-items:center;">
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">0</span>
<md-search layout="docked" density="0" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">-1</span>
<md-search layout="docked" density="-1" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">-2</span>
<md-search layout="docked" density="-2" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">-3</span>
<md-search layout="docked" density="-3" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">-4</span>
<md-search layout="docked" density="-4" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
</div>import { MdSearch } from '@awc-ui/react';
export function Demo() {
return (
<>
<div style={{ display: 'grid', gridTemplateColumns: 'auto 1fr', gap: '14px 16px', alignItems: 'center' }}>
<span style={{ inlineSize: '3.5rem', opacity: '.65', fontSize: '.75rem', fontFamily: 'ui-monospace,monospace' }}>0</span>
<MdSearch layout="docked" density="0" placeholder="Search mail" style={{ maxInlineSize: '420px' }}></MdSearch>
<span style={{ inlineSize: '3.5rem', opacity: '.65', fontSize: '.75rem', fontFamily: 'ui-monospace,monospace' }}>-1</span>
<MdSearch layout="docked" density="-1" placeholder="Search mail" style={{ maxInlineSize: '420px' }}></MdSearch>
<span style={{ inlineSize: '3.5rem', opacity: '.65', fontSize: '.75rem', fontFamily: 'ui-monospace,monospace' }}>-2</span>
<MdSearch layout="docked" density="-2" placeholder="Search mail" style={{ maxInlineSize: '420px' }}></MdSearch>
<span style={{ inlineSize: '3.5rem', opacity: '.65', fontSize: '.75rem', fontFamily: 'ui-monospace,monospace' }}>-3</span>
<MdSearch layout="docked" density="-3" placeholder="Search mail" style={{ maxInlineSize: '420px' }}></MdSearch>
<span style={{ inlineSize: '3.5rem', opacity: '.65', fontSize: '.75rem', fontFamily: 'ui-monospace,monospace' }}>-4</span>
<MdSearch layout="docked" density="-4" placeholder="Search mail" style={{ maxInlineSize: '420px' }}></MdSearch>
</div>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<div style="display:grid;grid-template-columns:auto 1fr;gap:14px 16px;align-items:center;">
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">0</span>
<md-search layout="docked" density="0" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">-1</span>
<md-search layout="docked" density="-1" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">-2</span>
<md-search layout="docked" density="-2" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">-3</span>
<md-search layout="docked" density="-3" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">-4</span>
<md-search layout="docked" density="-4" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
</div><script setup>
import '@awc-ui/core/define';
</script>
<template>
<div style="display:grid;grid-template-columns:auto 1fr;gap:14px 16px;align-items:center;">
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">0</span>
<md-search layout="docked" density="0" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">-1</span>
<md-search layout="docked" density="-1" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">-2</span>
<md-search layout="docked" density="-2" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">-3</span>
<md-search layout="docked" density="-3" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">-4</span>
<md-search layout="docked" density="-4" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
</div>
</template><script>
import '@awc-ui/core/define';
</script>
<div style="display:grid;grid-template-columns:auto 1fr;gap:14px 16px;align-items:center;">
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">0</span>
<md-search layout="docked" density="0" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">-1</span>
<md-search layout="docked" density="-1" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">-2</span>
<md-search layout="docked" density="-2" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">-3</span>
<md-search layout="docked" density="-3" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">-4</span>
<md-search layout="docked" density="-4" placeholder="Search mail" style="max-inline-size:420px;"></md-search>
</div><!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<div dir="rtl" data-density="-2" style="display:flex;flex-direction:column;gap:16px;inline-size:100%;max-inline-size:460px;">
<md-search layout="docked" placeholder="بحث في البريد"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search>
<md-search layout="docked" density="-4" placeholder="بحث في الملفات"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search>
<md-search layout="docked" style="--md-sys-density-scale: 0;" placeholder="بحث في الصور"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search>
</div>import { MdAvatar, MdSearch } from '@awc-ui/react';
export function Demo() {
return (
<>
<div dir="rtl" data-density="-2" style={{ display: 'flex', flexDirection: 'column', gap: '16px', inlineSize: '100%', maxInlineSize: '460px' }}>
<MdSearch layout="docked" placeholder="بحث في البريد"><MdAvatar slot="trailing" initials="AL" size="small"></MdAvatar></MdSearch>
<MdSearch layout="docked" density="-4" placeholder="بحث في الملفات"><MdAvatar slot="trailing" initials="AL" size="small"></MdAvatar></MdSearch>
<MdSearch layout="docked" style={{ '--md-sys-density-scale': '0' }} placeholder="بحث في الصور"><MdAvatar slot="trailing" initials="AL" size="small"></MdAvatar></MdSearch>
</div>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<div dir="rtl" data-density="-2" style="display:flex;flex-direction:column;gap:16px;inline-size:100%;max-inline-size:460px;">
<md-search layout="docked" placeholder="بحث في البريد"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search>
<md-search layout="docked" density="-4" placeholder="بحث في الملفات"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search>
<md-search layout="docked" style="--md-sys-density-scale: 0;" placeholder="بحث في الصور"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search>
</div><script setup>
import '@awc-ui/core/define';
</script>
<template>
<div dir="rtl" data-density="-2" style="display:flex;flex-direction:column;gap:16px;inline-size:100%;max-inline-size:460px;">
<md-search layout="docked" placeholder="بحث في البريد"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search>
<md-search layout="docked" density="-4" placeholder="بحث في الملفات"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search>
<md-search layout="docked" style="--md-sys-density-scale: 0;" placeholder="بحث في الصور"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search>
</div>
</template><script>
import '@awc-ui/core/define';
</script>
<div dir="rtl" data-density="-2" style="display:flex;flex-direction:column;gap:16px;inline-size:100%;max-inline-size:460px;">
<md-search layout="docked" placeholder="بحث في البريد"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search>
<md-search layout="docked" density="-4" placeholder="بحث في الملفات"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search>
<md-search layout="docked" style="--md-sys-density-scale: 0;" placeholder="بحث في الصور"><md-avatar slot="trailing" initials="AL" size="small"></md-avatar></md-search>
</div><!-- 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-search
placeholder="Rechercher dans les messages"
input-aria-label="Rechercher"
results-label="{count} résultats disponibles"
no-results-label="Aucun résultat"
loading-label="Recherche en cours"
>
<md-icon-button slot="trailing" icon="mic" aria-label="Recherche vocale"></md-icon-button>
</md-search>// Icons need the Material Symbols stylesheet in index.html — see Installation.
import { MdIconButton, MdSearch } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdSearch
placeholder="Rechercher dans les messages"
inputAriaLabel="Rechercher"
resultsLabel="{count} résultats disponibles"
noResultsLabel="Aucun résultat"
loadingLabel="Recherche en cours"
>
<MdIconButton slot="trailing" icon="mic" aria-label="Recherche vocale"></MdIconButton>
</MdSearch>
</>
);
}// Icons need the Material Symbols stylesheet in index.html — see Installation.
// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-search
placeholder="Rechercher dans les messages"
input-aria-label="Rechercher"
results-label="{count} résultats disponibles"
no-results-label="Aucun résultat"
loading-label="Recherche en cours"
>
<md-icon-button slot="trailing" icon="mic" aria-label="Recherche vocale"></md-icon-button>
</md-search><script setup>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import '@awc-ui/core/define';
</script>
<template>
<md-search
placeholder="Rechercher dans les messages"
input-aria-label="Rechercher"
results-label="{count} résultats disponibles"
no-results-label="Aucun résultat"
loading-label="Recherche en cours"
>
<md-icon-button slot="trailing" icon="mic" aria-label="Recherche vocale"></md-icon-button>
</md-search>
</template><script>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import '@awc-ui/core/define';
</script>
<md-search
placeholder="Rechercher dans les messages"
input-aria-label="Rechercher"
results-label="{count} résultats disponibles"
no-results-label="Aucun résultat"
loading-label="Recherche en cours"
>
<md-icon-button slot="trailing" icon="mic" aria-label="Recherche vocale"></md-icon-button>
</md-search>Density — the rungs are declared on :host, so a local density of -1 …
-4 out-ranks the inherited data-density ancestor value. There is no
[density="0"] rule, so 0 inherits instead of resetting; use
--md-sys-density-scale: 0 for that. See Density.
i18n — translate placeholder, input-aria-label, results-label,
no-results-label and loading-label, plus every slotted control’s
aria-label. The results you slot are yours to localize.
| Custom property | Purpose | Default |
|---|---|---|
--md-search-container-color | Bar background | --md-sys-color-surface-container-high |
--md-search-container-elevation | Custom bar shadow — overrides the elevation prop level | 0px 0px 0px 0px transparent (a transparent zero-shadow, not none — see the caution below) |
--md-search-container-shape | Bar border-radius | --md-sys-shape-corner-full |
--md-search-container-height | Bar block-size (contained + divided docked) | 56px at density 0 |
--md-search-divided-fullscreen-bar-height | Divided × full-screen header height | 72px at density 0 |
--md-search-container-min-inline-size | Bar minimum width | 360px |
--md-search-container-max-inline-size | Bar maximum width | 720px |
--md-search-container-padding-inline | Extra inline padding on the bar (rare; defaults to 0 so the clusters control their own gutters) | 0px |
--md-search-container-padding-inline-focused | Inline padding when focused / open | inherits resting |
--md-search-leading-padding-inline | Bar leading edge → leading tap target | 4px |
--md-search-trailing-padding-inline | Bar trailing edge → trailing tap target | 8px |
--md-search-icon-button-size | Leading / trailing tap-target diameter (M3 48px) | 48px at density 0 |
--md-search-leading-icon-label-gap | Leading tap target → input text | 4px |
--md-search-trailing-icon-label-gap | Input text → trailing tap target | 4px |
--md-search-input-color | Input text colour | --md-sys-color-on-surface |
--md-search-input-font-family | Input / placeholder font (M3 body-large) | --md-sys-typescale-body-large-font-family |
--md-search-input-font-size | Input / placeholder size | 16px at density 0 |
--md-search-input-line-height | Input / placeholder line height | 24px |
--md-search-input-font-weight | Input / placeholder weight | 400 |
--md-search-input-letter-spacing | Input / placeholder tracking | 0.5px |
--md-search-placeholder-color | Placeholder (supporting text) colour | --md-sys-color-on-surface-variant |
--md-search-leading-icon-color | Leading / back icon colour | --md-sys-color-on-surface |
--md-search-trailing-icon-color | Trailing + clear icon colour | --md-sys-color-on-surface-variant |
--md-search-icon-color | Alias for trailing icon colour | same as trailing |
--md-search-icon-size | Icon font-size | 24px at density 0 |
--md-search-loading-color | In-bar loading indicator shape colour | --md-sys-color-primary |
--md-search-state-layer-color | Hover / press wash | --md-sys-color-on-surface |
--md-search-focus-indicator-color | Focus ring colour | --md-sys-color-secondary |
--md-search-focus-indicator-thickness | Focus ring thickness | 3px |
--md-search-focus-indicator-offset | Focus ring offset | 2px |
--md-search-avatar-size | Slotted avatar diameter | 40px |
--md-search-panel-min-block-size | Docked panel floor — 0 shrinks to content | 0px |
--md-search-panel-max-block-size | Docked panel maximum height; results beyond it scroll | min(400px, 60vh) |
--md-search-max-block-size | Open surface cap — full-screen overlay or docked panel (overrides panel max when set) | unset |
--md-search-docked-panel-shape | Contained docked results card corner radius | 16px |
--md-search-panel-offset | Gap between bar and docked panel | focus-ring offset + thickness + 3px (8px) |
--md-search-contained-docked-panel-offset | Contained docked: bar ↔ results gap | falls back to --md-search-panel-offset’s formula (8px) |
--md-search-empty-color | No-results empty-state text colour | --md-sys-color-on-surface-variant |
--md-search-empty-padding-block | No-results empty-state vertical padding | 24px |
--md-search-empty-padding-inline | No-results empty-state horizontal padding | 16px |
--md-search-bar-fullscreen-color | Divided full-screen bar background while open | --md-sys-color-surface-container-low |
--md-search-fullscreen-results-color | Opaque full-viewport results surface (no scrim) | --md-sys-color-surface-container-low |
--md-search-fullscreen-bar-offset-block-start | Full-screen inset from viewport top | max(16px, safe-area) |
--md-search-fullscreen-bar-offset-inline | Contained full-screen bar inset from inline edges | max(8px, safe-area) |
--md-search-fullscreen-bar-panel-gap | Contained full-screen gap between bar and panel | focus-ring offset + thickness + 3px (8px) for contained; 0px for divided, which wants a flush seam under its hairline |
--md-search-panel-padding-block-start | Full-screen panel padding above results | 0px |
--md-search-fullscreen-expand-duration | Full-screen open (slide + fade) duration | --md-sys-motion-duration-medium2 (300ms fallback) |
--md-search-fullscreen-expand-easing | Full-screen open easing | --md-sys-motion-easing-standard |
--md-search-fullscreen-collapse-duration | Full-screen close duration — keep it in sync with the component’s 300ms close timer | --md-sys-motion-duration-medium2 (300ms fallback) |
--md-search-fullscreen-collapse-easing | Full-screen close easing | --md-sys-motion-easing-standard |
--md-search-divider-color | Hairline colour (divided variant) | --md-sys-color-outline |
--md-search-divider-thickness | Hairline thickness | 1px |
--md-search-expand-inset | Docked/inline bar resting side margin | 24px |
--md-search-expand-focused-inset | Docked/inline bar focused side margin | 12px |
--md-search-expand-duration | Springy expand duration (also drives the synced ripple wave) | medium4 (400ms) |
--md-search-expand-easing | Springy back-out curve — overshoots ~17% then snaps back | cubic-bezier(0.34, 1.56, 0.64, 1) |
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<div style="display:flex;flex-direction:column;gap:20px;inline-size:100%;">
<md-search layout="docked" placeholder="Default" style="max-inline-size: 440px;"></md-search>
<md-search layout="docked" placeholder="Squared and short" style="max-inline-size: 440px; --md-search-container-shape: 8px; --md-search-container-height: 44px; --md-search-icon-button-size: 40px;"></md-search>
<md-search layout="docked" placeholder="Branded" style="max-inline-size: 440px; --md-search-container-color: var(--md-sys-color-primary-container); --md-search-input-color: var(--md-sys-color-on-primary-container);"></md-search>
</div>import { MdSearch } from '@awc-ui/react';
export function Demo() {
return (
<>
<div style={{ display: 'flex', flexDirection: 'column', gap: '20px', inlineSize: '100%' }}>
<MdSearch layout="docked" placeholder="Default" style={{ maxInlineSize: '440px' }}></MdSearch>
<MdSearch layout="docked" placeholder="Squared and short" style={{ maxInlineSize: '440px', '--md-search-container-shape': '8px', '--md-search-container-height': '44px', '--md-search-icon-button-size': '40px' }}></MdSearch>
<MdSearch layout="docked" placeholder="Branded" style={{ maxInlineSize: '440px', '--md-search-container-color': 'var(--md-sys-color-primary-container)', '--md-search-input-color': 'var(--md-sys-color-on-primary-container)' }}></MdSearch>
</div>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<div style="display:flex;flex-direction:column;gap:20px;inline-size:100%;">
<md-search layout="docked" placeholder="Default" style="max-inline-size: 440px;"></md-search>
<md-search layout="docked" placeholder="Squared and short" style="max-inline-size: 440px; --md-search-container-shape: 8px; --md-search-container-height: 44px; --md-search-icon-button-size: 40px;"></md-search>
<md-search layout="docked" placeholder="Branded" style="max-inline-size: 440px; --md-search-container-color: var(--md-sys-color-primary-container); --md-search-input-color: var(--md-sys-color-on-primary-container);"></md-search>
</div><script setup>
import '@awc-ui/core/define';
</script>
<template>
<div style="display:flex;flex-direction:column;gap:20px;inline-size:100%;">
<md-search layout="docked" placeholder="Default" style="max-inline-size: 440px;"></md-search>
<md-search layout="docked" placeholder="Squared and short" style="max-inline-size: 440px; --md-search-container-shape: 8px; --md-search-container-height: 44px; --md-search-icon-button-size: 40px;"></md-search>
<md-search layout="docked" placeholder="Branded" style="max-inline-size: 440px; --md-search-container-color: var(--md-sys-color-primary-container); --md-search-input-color: var(--md-sys-color-on-primary-container);"></md-search>
</div>
</template><script>
import '@awc-ui/core/define';
</script>
<div style="display:flex;flex-direction:column;gap:20px;inline-size:100%;">
<md-search layout="docked" placeholder="Default" style="max-inline-size: 440px;"></md-search>
<md-search layout="docked" placeholder="Squared and short" style="max-inline-size: 440px; --md-search-container-shape: 8px; --md-search-container-height: 44px; --md-search-icon-button-size: 40px;"></md-search>
<md-search layout="docked" placeholder="Branded" style="max-inline-size: 440px; --md-search-container-color: var(--md-sys-color-primary-container); --md-search-input-color: var(--md-sys-color-on-primary-container);"></md-search>
</div>Every default resolves through an md-sys-color role, so a bar that sets no
custom properties follows the theme on its own.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-search layout="docked" placeholder="Untouched defaults" style="max-inline-size: 460px;">
<md-avatar slot="trailing" initials="AL" size="small"></md-avatar>
</md-search>import { MdAvatar, MdSearch } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdSearch layout="docked" placeholder="Untouched defaults" style={{ maxInlineSize: '460px' }}>
<MdAvatar slot="trailing" initials="AL" size="small"></MdAvatar>
</MdSearch>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-search layout="docked" placeholder="Untouched defaults" style="max-inline-size: 460px;">
<md-avatar slot="trailing" initials="AL" size="small"></md-avatar>
</md-search><script setup>
import '@awc-ui/core/define';
</script>
<template>
<md-search layout="docked" placeholder="Untouched defaults" style="max-inline-size: 460px;">
<md-avatar slot="trailing" initials="AL" size="small"></md-avatar>
</md-search>
</template><script>
import '@awc-ui/core/define';
</script>
<md-search layout="docked" placeholder="Untouched defaults" style="max-inline-size: 460px;">
<md-avatar slot="trailing" initials="AL" size="small"></md-avatar>
</md-search>This is the single, unified expansion mechanism for all docked/inline
bars — contained and divided alike. The bar rests with a 24px side
margin (--md-search-expand-inset) and springs to a 12px side margin
(--md-search-expand-focused-inset) on focus / open, symmetrically (even
left + right). Each edge travels the 24 − 12 = 12px delta, so the container
grows evenly on both sides.
The default easing is a back-out spring curve: the bar briefly overshoots the target width by ~17 % and snaps back into place. Combined with a 400 ms duration this reads as deliberate, expressive, and a touch playful — the M3 expressive aesthetic — without needing the Web Animations API. The same duration drives the synced ripple wave so the press response and the bar settle land at the same instant.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<div style="display:grid;grid-template-columns:auto 1fr;gap:14px 16px;align-items:center;">
<span style="inline-size:4.5rem;opacity:.7;font-size:.8rem;font-family:ui-monospace,monospace;">default</span>
<md-search layout="docked" placeholder="24 → 12px" style="max-inline-size:440px;"></md-search>
<span style="inline-size:4.5rem;opacity:.7;font-size:.8rem;font-family:ui-monospace,monospace;">theatrical</span>
<md-search layout="docked" placeholder="48 → 12px" style="max-inline-size:440px; --md-search-expand-inset: 48px; --md-search-expand-focused-inset: 12px; --md-search-expand-duration: 350ms;"></md-search>
<span style="inline-size:4.5rem;opacity:.7;font-size:.8rem;font-family:ui-monospace,monospace;">no expand</span>
<md-search layout="docked" placeholder="Fixed margin" style="max-inline-size:440px; --md-search-expand-inset: 12px; --md-search-expand-focused-inset: 12px;"></md-search>
</div>import { MdSearch } from '@awc-ui/react';
export function Demo() {
return (
<>
<div style={{ display: 'grid', gridTemplateColumns: 'auto 1fr', gap: '14px 16px', alignItems: 'center' }}>
<span style={{ inlineSize: '4.5rem', opacity: '.7', fontSize: '.8rem', fontFamily: 'ui-monospace,monospace' }}>default</span>
<MdSearch layout="docked" placeholder="24 → 12px" style={{ maxInlineSize: '440px' }}></MdSearch>
<span style={{ inlineSize: '4.5rem', opacity: '.7', fontSize: '.8rem', fontFamily: 'ui-monospace,monospace' }}>theatrical</span>
<MdSearch layout="docked" placeholder="48 → 12px" style={{ maxInlineSize: '440px', '--md-search-expand-inset': '48px', '--md-search-expand-focused-inset': '12px', '--md-search-expand-duration': '350ms' }}></MdSearch>
<span style={{ inlineSize: '4.5rem', opacity: '.7', fontSize: '.8rem', fontFamily: 'ui-monospace,monospace' }}>no expand</span>
<MdSearch layout="docked" placeholder="Fixed margin" style={{ maxInlineSize: '440px', '--md-search-expand-inset': '12px', '--md-search-expand-focused-inset': '12px' }}></MdSearch>
</div>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<div style="display:grid;grid-template-columns:auto 1fr;gap:14px 16px;align-items:center;">
<span style="inline-size:4.5rem;opacity:.7;font-size:.8rem;font-family:ui-monospace,monospace;">default</span>
<md-search layout="docked" placeholder="24 → 12px" style="max-inline-size:440px;"></md-search>
<span style="inline-size:4.5rem;opacity:.7;font-size:.8rem;font-family:ui-monospace,monospace;">theatrical</span>
<md-search layout="docked" placeholder="48 → 12px" style="max-inline-size:440px; --md-search-expand-inset: 48px; --md-search-expand-focused-inset: 12px; --md-search-expand-duration: 350ms;"></md-search>
<span style="inline-size:4.5rem;opacity:.7;font-size:.8rem;font-family:ui-monospace,monospace;">no expand</span>
<md-search layout="docked" placeholder="Fixed margin" style="max-inline-size:440px; --md-search-expand-inset: 12px; --md-search-expand-focused-inset: 12px;"></md-search>
</div><script setup>
import '@awc-ui/core/define';
</script>
<template>
<div style="display:grid;grid-template-columns:auto 1fr;gap:14px 16px;align-items:center;">
<span style="inline-size:4.5rem;opacity:.7;font-size:.8rem;font-family:ui-monospace,monospace;">default</span>
<md-search layout="docked" placeholder="24 → 12px" style="max-inline-size:440px;"></md-search>
<span style="inline-size:4.5rem;opacity:.7;font-size:.8rem;font-family:ui-monospace,monospace;">theatrical</span>
<md-search layout="docked" placeholder="48 → 12px" style="max-inline-size:440px; --md-search-expand-inset: 48px; --md-search-expand-focused-inset: 12px; --md-search-expand-duration: 350ms;"></md-search>
<span style="inline-size:4.5rem;opacity:.7;font-size:.8rem;font-family:ui-monospace,monospace;">no expand</span>
<md-search layout="docked" placeholder="Fixed margin" style="max-inline-size:440px; --md-search-expand-inset: 12px; --md-search-expand-focused-inset: 12px;"></md-search>
</div>
</template><script>
import '@awc-ui/core/define';
</script>
<div style="display:grid;grid-template-columns:auto 1fr;gap:14px 16px;align-items:center;">
<span style="inline-size:4.5rem;opacity:.7;font-size:.8rem;font-family:ui-monospace,monospace;">default</span>
<md-search layout="docked" placeholder="24 → 12px" style="max-inline-size:440px;"></md-search>
<span style="inline-size:4.5rem;opacity:.7;font-size:.8rem;font-family:ui-monospace,monospace;">theatrical</span>
<md-search layout="docked" placeholder="48 → 12px" style="max-inline-size:440px; --md-search-expand-inset: 48px; --md-search-expand-focused-inset: 12px; --md-search-expand-duration: 350ms;"></md-search>
<span style="inline-size:4.5rem;opacity:.7;font-size:.8rem;font-family:ui-monospace,monospace;">no expand</span>
<md-search layout="docked" placeholder="Fixed margin" style="max-inline-size:440px; --md-search-expand-inset: 12px; --md-search-expand-focused-inset: 12px;"></md-search>
</div>/* Calmer, no overshoot — falls back to the standard M3 emphasized curve. */md-search.calm { --md-search-expand-easing: var(--md-sys-motion-easing-emphasized); --md-search-expand-duration: 400ms;}Set the full-width boolean prop to make a docked/inline bar span its
container edge-to-edge with no side gutters. It zeroes both expand insets
for that instance (resting + focused = 0), so there is no 24 → 12px margin
animation and the aligned results drawer fills the same full width. It has no
effect on full-screen (already edge-to-edge).
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-search layout="docked" full-width placeholder="Search"></md-search>import { MdSearch } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdSearch layout="docked" fullWidth placeholder="Search"></MdSearch>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-search layout="docked" full-width placeholder="Search"></md-search><script setup>
import '@awc-ui/core/define';
</script>
<template>
<md-search layout="docked" full-width placeholder="Search"></md-search>
</template><script>
import '@awc-ui/core/define';
</script>
<md-search layout="docked" full-width placeholder="Search"></md-search>full-width is purely a shorthand over the CSS vars — the identical effect is
available by zeroing both insets directly:
md-search.bleed { --md-search-expand-inset: 0; --md-search-expand-focused-inset: 0;}Long lists scroll inside an inner viewport (part="results-viewport") so the
scrollbar stays aligned with the slotted surface. Cap the scroll area with
--md-search-panel-max-block-size (docked) or --md-search-max-block-size
(full-screen overlay, or a docked override). The bar height stays on
--md-search-container-height.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-search layout="docked" open value="Eli" placeholder="Search contacts" style="max-inline-size:440px; --md-search-panel-max-block-size: 220px;">
<md-list slot="results">
<md-list-item headline="Eli Bassi"></md-list-item>
<md-list-item headline="Elias Fournier"></md-list-item>
<md-list-item headline="Elif Demir"></md-list-item>
<md-list-item headline="Elijah Mensah"></md-list-item>
<md-list-item headline="Elisa Moretti"></md-list-item>
<md-list-item headline="Ellis Ward"></md-list-item>
</md-list>
</md-search>import { MdList, MdListItem, MdSearch } from '@awc-ui/react';
export function Demo() {
return (
<>
<MdSearch layout="docked" open value="Eli" placeholder="Search contacts" style={{ maxInlineSize: '440px', '--md-search-panel-max-block-size': '220px' }}>
<MdList slot="results">
<MdListItem headline="Eli Bassi"></MdListItem>
<MdListItem headline="Elias Fournier"></MdListItem>
<MdListItem headline="Elif Demir"></MdListItem>
<MdListItem headline="Elijah Mensah"></MdListItem>
<MdListItem headline="Elisa Moretti"></MdListItem>
<MdListItem headline="Ellis Ward"></MdListItem>
</MdList>
</MdSearch>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<md-search layout="docked" open value="Eli" placeholder="Search contacts" style="max-inline-size:440px; --md-search-panel-max-block-size: 220px;">
<md-list slot="results">
<md-list-item headline="Eli Bassi"></md-list-item>
<md-list-item headline="Elias Fournier"></md-list-item>
<md-list-item headline="Elif Demir"></md-list-item>
<md-list-item headline="Elijah Mensah"></md-list-item>
<md-list-item headline="Elisa Moretti"></md-list-item>
<md-list-item headline="Ellis Ward"></md-list-item>
</md-list>
</md-search><script setup>
import '@awc-ui/core/define';
</script>
<template>
<md-search layout="docked" open value="Eli" placeholder="Search contacts" style="max-inline-size:440px; --md-search-panel-max-block-size: 220px;">
<md-list slot="results">
<md-list-item headline="Eli Bassi"></md-list-item>
<md-list-item headline="Elias Fournier"></md-list-item>
<md-list-item headline="Elif Demir"></md-list-item>
<md-list-item headline="Elijah Mensah"></md-list-item>
<md-list-item headline="Elisa Moretti"></md-list-item>
<md-list-item headline="Ellis Ward"></md-list-item>
</md-list>
</md-search>
</template><script>
import '@awc-ui/core/define';
</script>
<md-search layout="docked" open value="Eli" placeholder="Search contacts" style="max-inline-size:440px; --md-search-panel-max-block-size: 220px;">
<md-list slot="results">
<md-list-item headline="Eli Bassi"></md-list-item>
<md-list-item headline="Elias Fournier"></md-list-item>
<md-list-item headline="Elif Demir"></md-list-item>
<md-list-item headline="Elijah Mensah"></md-list-item>
<md-list-item headline="Elisa Moretti"></md-list-item>
<md-list-item headline="Ellis Ward"></md-list-item>
</md-list>
</md-search>CSS Parts — bar, state-layer, leading, leading-state-layer, input,
trailing, clear-button, voice-button, loading, loading-shape,
trigger, trigger-button, panel, panel-body, divider, empty,
results-host, results-viewport, status.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<style>
md-search.branded::part(bar) { border: 2px solid var(--md-sys-color-primary); }
md-search.branded::part(input) { font-weight: 600; }
md-search.branded::part(clear-button) { color: var(--md-sys-color-error); }
</style>
<md-search class="branded" layout="docked" value="invoice" placeholder="Search files" style="max-inline-size:440px; --md-search-container-color: var(--md-sys-color-surface-container-low);"></md-search>import { MdSearch } from '@awc-ui/react';
export function Demo() {
return (
<>
<style>
md-search.branded::part(bar) { border: 2px solid var(--md-sys-color-primary); }
md-search.branded::part(input) { font-weight: 600; }
md-search.branded::part(clear-button) { color: var(--md-sys-color-error); }
</style>
<MdSearch className="branded" layout="docked" value="invoice" placeholder="Search files" style={{ maxInlineSize: '440px', '--md-search-container-color': 'var(--md-sys-color-surface-container-low)' }}></MdSearch>
</>
);
}// app.module.ts — register the AWC UI elements once
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { AwcUiModule } from '@awc-ui/angular';
@NgModule({
imports: [AwcUiModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}
<!-- app.component.html -->
<style>
md-search.branded::part(bar) { border: 2px solid var(--md-sys-color-primary); }
md-search.branded::part(input) { font-weight: 600; }
md-search.branded::part(clear-button) { color: var(--md-sys-color-error); }
</style>
<md-search class="branded" layout="docked" value="invoice" placeholder="Search files" style="max-inline-size:440px; --md-search-container-color: var(--md-sys-color-surface-container-low);"></md-search><script setup>
import '@awc-ui/core/define';
</script>
<template>
<style>
md-search.branded::part(bar) { border: 2px solid var(--md-sys-color-primary); }
md-search.branded::part(input) { font-weight: 600; }
md-search.branded::part(clear-button) { color: var(--md-sys-color-error); }
</style>
<md-search class="branded" layout="docked" value="invoice" placeholder="Search files" style="max-inline-size:440px; --md-search-container-color: var(--md-sys-color-surface-container-low);"></md-search>
</template><script>
import '@awc-ui/core/define';
</script>
<style>
md-search.branded::part(bar) { border: 2px solid var(--md-sys-color-primary); }
md-search.branded::part(input) { font-weight: 600; }
md-search.branded::part(clear-button) { color: var(--md-sys-color-error); }
</style>
<md-search class="branded" layout="docked" value="invoice" placeholder="Search files" style="max-inline-size:440px; --md-search-container-color: var(--md-sys-color-surface-container-low);"></md-search>md-search::part(bar) { border: 2px solid var(--md-sys-color-primary);}md-app-bar ·
md-autocomplete ·
md-text-field ·
md-select ·
md-list ·
md-chip ·
md-icon-button
md-searchTwo 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?".
md-search 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.
System-prompt preamble · decision matrix · token reference · page recipes · anti-patterns. Paste into the system prompt at the start of a piece of work.