md-bottom-sheet 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.
Supplementary content anchored to the bottom of the screen. It slides up
over the page with a drag handle, an optional headline and an actions row —
the mobile-first counterpart to md-side-sheet.
<!-- 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-button id="bs-share-btn" variant="filled" icon="share">Share</md-button>
<md-bottom-sheet id="bs-share" headline="Share to" closeable>
<md-list>
<md-list-item headline="Copy link" leading-icon="link"></md-list-item>
<md-list-item headline="Email" leading-icon="email"></md-list-item>
<md-list-item headline="Message" leading-icon="chat"></md-list-item>
</md-list>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-share-btn').addEventListener('click', () => {
document.getElementById('bs-share').show();
});
</script>// Icons need the Material Symbols stylesheet in index.html — see Installation.
import { useRef } from 'react';
import { MdBottomSheet, MdButton, MdList, MdListItem } from '@awc-ui/react';
export function Demo() {
const bsShareRef = useRef(null);
return (
<>
<MdButton id="bs-share-btn" variant="filled" icon="share" onClick={() => bsShareRef.current?.show()}>Share</MdButton>
<MdBottomSheet id="bs-share" ref={bsShareRef} headline="Share to" closeable>
<MdList>
<MdListItem headline="Copy link" leadingIcon="link"></MdListItem>
<MdListItem headline="Email" leadingIcon="email"></MdListItem>
<MdListItem headline="Message" leadingIcon="chat"></MdListItem>
</MdList>
</MdBottomSheet>
</>
);
}// 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-button id="bs-share-btn" variant="filled" icon="share" (click)="bsShare.show()">Share</md-button>
<md-bottom-sheet id="bs-share" #bsShare headline="Share to" closeable>
<md-list>
<md-list-item headline="Copy link" leading-icon="link"></md-list-item>
<md-list-item headline="Email" leading-icon="email"></md-list-item>
<md-list-item headline="Message" leading-icon="chat"></md-list-item>
</md-list>
</md-bottom-sheet><script setup>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsShare = ref(null);
</script>
<template>
<md-button id="bs-share-btn" variant="filled" icon="share" @click="bsShare?.show()">Share</md-button>
<md-bottom-sheet id="bs-share" ref="bsShare" headline="Share to" closeable>
<md-list>
<md-list-item headline="Copy link" leading-icon="link"></md-list-item>
<md-list-item headline="Email" leading-icon="email"></md-list-item>
<md-list-item headline="Message" leading-icon="chat"></md-list-item>
</md-list>
</md-bottom-sheet>
</template><script>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import '@awc-ui/core/define';
let bsShare;
</script>
<md-button id="bs-share-btn" variant="filled" icon="share" on:click={() => bsShare?.show()}>Share</md-button>
<md-bottom-sheet id="bs-share" bind:this={bsShare} headline="Share to" closeable>
<md-list>
<md-list-item headline="Copy link" leading-icon="link"></md-list-item>
<md-list-item headline="Email" leading-icon="email"></md-list-item>
<md-list-item headline="Message" leading-icon="chat"></md-list-item>
</md-list>
</md-bottom-sheet>
Already installed? See the
Installation guide for one-time package setup
(core + tokens, fonts). Each tab below shows two patterns for using
md-bottom-sheet 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-bottom-sheet) ─── -->
<script type="module">
import '@awc-ui/core/components/md-bottom-sheet';
</script>
<md-bottom-sheet></md-bottom-sheet>// ─── 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 { MdBottomSheet } from '@awc-ui/react';
export function Example() {
return <MdBottomSheet></MdBottomSheet>;
}
// ─── Option B: single import (tree-shake to only md-bottom-sheet) ───
// 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-bottom-sheet';
export function ExampleTreeShaken() {
return <md-bottom-sheet></md-bottom-sheet>;
}// ─── 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-bottom-sheet></md-bottom-sheet>`,
})
export class ExampleComponent {}
// ─── Option B: typed directive (tree-shake friendly) ───
// Pair with `import '@awc-ui/core/components/md-bottom-sheet'` in main.ts.
import { Component } from '@angular/core';
import { MdBottomSheet } from '@awc-ui/angular';
@Component({
standalone: true,
imports: [MdBottomSheet],
template: `<md-bottom-sheet></md-bottom-sheet>`,
})
export class ExampleTreeShakenComponent {}<!-- ─── Option A: typed Vue wrapper (registers all components) ─── -->
<script setup lang="ts">
import { MdBottomSheet } from '@awc-ui/vue';
</script>
<template>
<MdBottomSheet></MdBottomSheet>
</template>
<!-- ─── Option B: single import (tree-shake to only md-bottom-sheet) ─── -->
<script setup lang="ts">
import '@awc-ui/core/components/md-bottom-sheet';
</script>
<template>
<md-bottom-sheet></md-bottom-sheet>
</template><!-- ─── Option A: global registration (done once in main entry) ─── -->
<!-- main.ts: -->
<!-- import { defineCustomElements } from '@awc-ui/svelte'; -->
<!-- defineCustomElements(window); -->
<md-bottom-sheet></md-bottom-sheet>
<!-- ─── Option B: single import (tree-shake to only md-bottom-sheet) ─── -->
<script lang="ts">
import '@awc-ui/core/components/md-bottom-sheet';
</script>
<md-bottom-sheet></md-bottom-sheet>| Situation | Use instead |
|---|---|
| A blocking decision or critical information | md-dialog |
| Desktop supplementary content | md-side-sheet |
| Brief feedback | md-snackbar |
| A compact action list on desktop | md-menu |
| Primary content of the screen | A page |
| Explaining a control | md-tooltip |
| A full sub-task on mobile | md-dialog with fullscreen |
| Variant | Chrome | Use for |
|---|---|---|
standard | Flush to the bottom edge, top corners only rounded | The default tray: share targets, filters, action lists |
detached | Inset by --md-bottom-sheet-detached-margin on every edge, all four corners rounded, elevated | A sheet that should read as a floating card — a media player, a compact picker |
Both are modal — scrim, focus trap, body scroll lock, Escape and
drag-to-dismiss. Only the chrome differs.
Every demo on this page renders the sheet closed next to its trigger; press the button to open it.
Anchored to the bottom edge, rounded top corners only — the default.
Floating, inset from every edge, all four corners rounded.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-button id="bs-standard-btn" variant="filled">Standard</md-button>
<md-button id="bs-detached-btn" variant="tonal">Detached</md-button>
<md-bottom-sheet id="bs-standard" headline="Standard" closeable>
<p>Anchored to the bottom edge, rounded top corners only — the default.</p>
</md-bottom-sheet>
<md-bottom-sheet id="bs-detached" variant="detached" headline="Detached" closeable>
<p>Floating, inset from every edge, all four corners rounded.</p>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-standard-btn').addEventListener('click', () => {
document.getElementById('bs-standard').show();
});
document.getElementById('bs-detached-btn').addEventListener('click', () => {
document.getElementById('bs-detached').show();
});
</script>import { useRef } from 'react';
import { MdBottomSheet, MdButton } from '@awc-ui/react';
export function Demo() {
const bsStandardRef = useRef(null);
const bsDetachedRef = useRef(null);
return (
<>
<MdButton id="bs-standard-btn" variant="filled" onClick={() => bsStandardRef.current?.show()}>Standard</MdButton>
<MdButton id="bs-detached-btn" variant="tonal" onClick={() => bsDetachedRef.current?.show()}>Detached</MdButton>
<MdBottomSheet id="bs-standard" ref={bsStandardRef} headline="Standard" closeable>
<p>Anchored to the bottom edge, rounded top corners only — the default.</p>
</MdBottomSheet>
<MdBottomSheet id="bs-detached" ref={bsDetachedRef} variant="detached" headline="Detached" closeable>
<p>Floating, inset from every edge, all four corners rounded.</p>
</MdBottomSheet>
</>
);
}// 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-button id="bs-standard-btn" variant="filled" (click)="bsStandard.show()">Standard</md-button>
<md-button id="bs-detached-btn" variant="tonal" (click)="bsDetached.show()">Detached</md-button>
<md-bottom-sheet id="bs-standard" #bsStandard headline="Standard" closeable>
<p>Anchored to the bottom edge, rounded top corners only — the default.</p>
</md-bottom-sheet>
<md-bottom-sheet id="bs-detached" #bsDetached variant="detached" headline="Detached" closeable>
<p>Floating, inset from every edge, all four corners rounded.</p>
</md-bottom-sheet><script setup>
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsStandard = ref(null);
const bsDetached = ref(null);
</script>
<template>
<md-button id="bs-standard-btn" variant="filled" @click="bsStandard?.show()">Standard</md-button>
<md-button id="bs-detached-btn" variant="tonal" @click="bsDetached?.show()">Detached</md-button>
<md-bottom-sheet id="bs-standard" ref="bsStandard" headline="Standard" closeable>
<p>Anchored to the bottom edge, rounded top corners only — the default.</p>
</md-bottom-sheet>
<md-bottom-sheet id="bs-detached" ref="bsDetached" variant="detached" headline="Detached" closeable>
<p>Floating, inset from every edge, all four corners rounded.</p>
</md-bottom-sheet>
</template><script>
import '@awc-ui/core/define';
let bsStandard;
let bsDetached;
</script>
<md-button id="bs-standard-btn" variant="filled" on:click={() => bsStandard?.show()}>Standard</md-button>
<md-button id="bs-detached-btn" variant="tonal" on:click={() => bsDetached?.show()}>Detached</md-button>
<md-bottom-sheet id="bs-standard" bind:this={bsStandard} headline="Standard" closeable>
<p>Anchored to the bottom edge, rounded top corners only — the default.</p>
</md-bottom-sheet>
<md-bottom-sheet id="bs-detached" bind:this={bsDetached} variant="detached" headline="Detached" closeable>
<p>Floating, inset from every edge, all four corners rounded.</p>
</md-bottom-sheet>detached is the one to reach for when the sheet should read as a floating card
rather than a tray welded to the bottom edge:
<!-- 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-button id="bs-det3-btn" variant="filled">Detached sheet</md-button>
<md-bottom-sheet id="bs-det3" variant="detached" headline="Share" closeable style="--md-bottom-sheet-content-padding-inline: 8px;">
<md-list>
<md-list-item headline="Send in email" leading-icon="email"></md-list-item>
<md-list-item headline="Copy link" leading-icon="link"></md-list-item>
<md-list-item headline="Save to Drive" leading-icon="add_to_drive"></md-list-item>
</md-list>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-det3-btn').addEventListener('click', () => {
document.getElementById('bs-det3').show();
});
</script>// Icons need the Material Symbols stylesheet in index.html — see Installation.
import { useRef } from 'react';
import { MdBottomSheet, MdButton, MdList, MdListItem } from '@awc-ui/react';
export function Demo() {
const bsDet3Ref = useRef(null);
return (
<>
<MdButton id="bs-det3-btn" variant="filled" onClick={() => bsDet3Ref.current?.show()}>Detached sheet</MdButton>
<MdBottomSheet id="bs-det3" ref={bsDet3Ref} variant="detached" headline="Share" closeable style={{ '--md-bottom-sheet-content-padding-inline': '8px' }}>
<MdList>
<MdListItem headline="Send in email" leadingIcon="email"></MdListItem>
<MdListItem headline="Copy link" leadingIcon="link"></MdListItem>
<MdListItem headline="Save to Drive" leadingIcon="add_to_drive"></MdListItem>
</MdList>
</MdBottomSheet>
</>
);
}// 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-button id="bs-det3-btn" variant="filled" (click)="bsDet3.show()">Detached sheet</md-button>
<md-bottom-sheet id="bs-det3" #bsDet3 variant="detached" headline="Share" closeable style="--md-bottom-sheet-content-padding-inline: 8px;">
<md-list>
<md-list-item headline="Send in email" leading-icon="email"></md-list-item>
<md-list-item headline="Copy link" leading-icon="link"></md-list-item>
<md-list-item headline="Save to Drive" leading-icon="add_to_drive"></md-list-item>
</md-list>
</md-bottom-sheet><script setup>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsDet3 = ref(null);
</script>
<template>
<md-button id="bs-det3-btn" variant="filled" @click="bsDet3?.show()">Detached sheet</md-button>
<md-bottom-sheet id="bs-det3" ref="bsDet3" variant="detached" headline="Share" closeable style="--md-bottom-sheet-content-padding-inline: 8px;">
<md-list>
<md-list-item headline="Send in email" leading-icon="email"></md-list-item>
<md-list-item headline="Copy link" leading-icon="link"></md-list-item>
<md-list-item headline="Save to Drive" leading-icon="add_to_drive"></md-list-item>
</md-list>
</md-bottom-sheet>
</template><script>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import '@awc-ui/core/define';
let bsDet3;
</script>
<md-button id="bs-det3-btn" variant="filled" on:click={() => bsDet3?.show()}>Detached sheet</md-button>
<md-bottom-sheet id="bs-det3" bind:this={bsDet3} variant="detached" headline="Share" closeable style="--md-bottom-sheet-content-padding-inline: 8px;">
<md-list>
<md-list-item headline="Send in email" leading-icon="email"></md-list-item>
<md-list-item headline="Copy link" leading-icon="link"></md-list-item>
<md-list-item headline="Save to Drive" leading-icon="add_to_drive"></md-list-item>
</md-list>
</md-bottom-sheet>The states overview collects every combination in one Storybook page, and standard isolates the default variant.
| Slot | Holds |
|---|---|
(default) | The body. Scrolls internally when content exceeds the height |
headline | Rich title content — overrides the text of the headline prop |
close | A custom close affordance, replacing the built-in icon button |
actions | The action row pinned below the body |
show-drag-handle is on by default. closeable adds an explicit close
button and is off by default — turn it on, because the drag gesture is
pointer-only. top-divider and bottom-divider draw rules around the
scrolling body; the bottom rule only renders when there is an actions row to
separate.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-button id="bs-anatomy-btn" variant="filled">Full anatomy</md-button>
<md-button id="bs-nohandle-btn" variant="tonal">No drag handle</md-button>
<md-bottom-sheet id="bs-anatomy" headline="Filters" top-divider bottom-divider closeable>
<span slot="headline">Filters <span style="font-size: 0.6em; vertical-align: middle; color: var(--md-sys-color-primary);">3 active</span></span>
<md-list>
<md-list-item headline="Unread only"></md-list-item>
<md-list-item headline="Has attachment"></md-list-item>
<md-list-item headline="Starred"></md-list-item>
</md-list>
<md-button slot="actions" variant="text">Reset</md-button>
<md-button slot="actions" variant="filled">Apply</md-button>
</md-bottom-sheet>
<md-bottom-sheet id="bs-nohandle" headline="New task" show-drag-handle="false" closeable>
<md-text-field variant="outlined" label="Title" required></md-text-field>
<md-button slot="actions" variant="text">Cancel</md-button>
<md-button slot="actions" variant="filled">Add task</md-button>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-anatomy-btn').addEventListener('click', () => {
document.getElementById('bs-anatomy').show();
});
document.getElementById('bs-nohandle-btn').addEventListener('click', () => {
document.getElementById('bs-nohandle').show();
});
</script>import { useRef } from 'react';
import { MdBottomSheet, MdButton, MdList, MdListItem, MdTextField } from '@awc-ui/react';
export function Demo() {
const bsAnatomyRef = useRef(null);
const bsNohandleRef = useRef(null);
return (
<>
<MdButton id="bs-anatomy-btn" variant="filled" onClick={() => bsAnatomyRef.current?.show()}>Full anatomy</MdButton>
<MdButton id="bs-nohandle-btn" variant="tonal" onClick={() => bsNohandleRef.current?.show()}>No drag handle</MdButton>
<MdBottomSheet id="bs-anatomy" ref={bsAnatomyRef} headline="Filters" topDivider bottomDivider closeable>
<span slot="headline">Filters <span style={{ fontSize: '0.6em', verticalAlign: 'middle', color: 'var(--md-sys-color-primary)' }}>3 active</span></span>
<MdList>
<MdListItem headline="Unread only"></MdListItem>
<MdListItem headline="Has attachment"></MdListItem>
<MdListItem headline="Starred"></MdListItem>
</MdList>
<MdButton slot="actions" variant="text">Reset</MdButton>
<MdButton slot="actions" variant="filled">Apply</MdButton>
</MdBottomSheet>
{/* showDragHandle is a boolean prop: pass {false}, not the string "false". */}
<MdBottomSheet id="bs-nohandle" ref={bsNohandleRef} headline="New task" showDragHandle={false} closeable>
<MdTextField variant="outlined" label="Title" required></MdTextField>
<MdButton slot="actions" variant="text">Cancel</MdButton>
<MdButton slot="actions" variant="filled">Add task</MdButton>
</MdBottomSheet>
</>
);
}// 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-button id="bs-anatomy-btn" variant="filled" (click)="bsAnatomy.show()">Full anatomy</md-button>
<md-button id="bs-nohandle-btn" variant="tonal" (click)="bsNohandle.show()">No drag handle</md-button>
<md-bottom-sheet id="bs-anatomy" #bsAnatomy headline="Filters" top-divider bottom-divider closeable>
<span slot="headline">Filters <span style="font-size: 0.6em; vertical-align: middle; color: var(--md-sys-color-primary);">3 active</span></span>
<md-list>
<md-list-item headline="Unread only"></md-list-item>
<md-list-item headline="Has attachment"></md-list-item>
<md-list-item headline="Starred"></md-list-item>
</md-list>
<md-button slot="actions" variant="text">Reset</md-button>
<md-button slot="actions" variant="filled">Apply</md-button>
</md-bottom-sheet>
<md-bottom-sheet id="bs-nohandle" #bsNohandle headline="New task" show-drag-handle="false" closeable>
<md-text-field variant="outlined" label="Title" required></md-text-field>
<md-button slot="actions" variant="text">Cancel</md-button>
<md-button slot="actions" variant="filled">Add task</md-button>
</md-bottom-sheet><script setup>
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsAnatomy = ref(null);
const bsNohandle = ref(null);
</script>
<template>
<md-button id="bs-anatomy-btn" variant="filled" @click="bsAnatomy?.show()">Full anatomy</md-button>
<md-button id="bs-nohandle-btn" variant="tonal" @click="bsNohandle?.show()">No drag handle</md-button>
<md-bottom-sheet id="bs-anatomy" ref="bsAnatomy" headline="Filters" top-divider bottom-divider closeable>
<span slot="headline">Filters <span style="font-size: 0.6em; vertical-align: middle; color: var(--md-sys-color-primary);">3 active</span></span>
<md-list>
<md-list-item headline="Unread only"></md-list-item>
<md-list-item headline="Has attachment"></md-list-item>
<md-list-item headline="Starred"></md-list-item>
</md-list>
<md-button slot="actions" variant="text">Reset</md-button>
<md-button slot="actions" variant="filled">Apply</md-button>
</md-bottom-sheet>
<md-bottom-sheet id="bs-nohandle" ref="bsNohandle" headline="New task" show-drag-handle="false" closeable>
<md-text-field variant="outlined" label="Title" required></md-text-field>
<md-button slot="actions" variant="text">Cancel</md-button>
<md-button slot="actions" variant="filled">Add task</md-button>
</md-bottom-sheet>
</template><script>
import '@awc-ui/core/define';
let bsAnatomy;
let bsNohandle;
</script>
<md-button id="bs-anatomy-btn" variant="filled" on:click={() => bsAnatomy?.show()}>Full anatomy</md-button>
<md-button id="bs-nohandle-btn" variant="tonal" on:click={() => bsNohandle?.show()}>No drag handle</md-button>
<md-bottom-sheet id="bs-anatomy" bind:this={bsAnatomy} headline="Filters" top-divider bottom-divider closeable>
<span slot="headline">Filters <span style="font-size: 0.6em; vertical-align: middle; color: var(--md-sys-color-primary);">3 active</span></span>
<md-list>
<md-list-item headline="Unread only"></md-list-item>
<md-list-item headline="Has attachment"></md-list-item>
<md-list-item headline="Starred"></md-list-item>
</md-list>
<md-button slot="actions" variant="text">Reset</md-button>
<md-button slot="actions" variant="filled">Apply</md-button>
</md-bottom-sheet>
<md-bottom-sheet id="bs-nohandle" bind:this={bsNohandle} headline="New task" show-drag-handle="false" closeable>
<md-text-field variant="outlined" label="Title" required></md-text-field>
<md-button slot="actions" variant="text">Cancel</md-button>
<md-button slot="actions" variant="filled">Add task</md-button>
</md-bottom-sheet>The actions slot holds the footer buttons. The built-in close glyph can be
replaced by slotting your own control into close — and slotting one is the
request for a close affordance, so closeable is not required alongside it.
The second sheet below has no closeable attribute and still shows its “Done”
button.
This removes it for everyone in the thread.
A text button replaces the close glyph — no closeable attribute needed.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-button id="bs-actions-btn" variant="filled">Sheet with actions</md-button>
<md-button id="bs-customclose-btn" variant="outlined">Custom close control</md-button>
<md-bottom-sheet id="bs-actions" headline="Delete conversation?" closeable>
<p>This removes it for everyone in the thread.</p>
<md-button slot="actions" variant="text">Cancel</md-button>
<md-button slot="actions" variant="filled">Delete</md-button>
</md-bottom-sheet>
<md-bottom-sheet id="bs-customclose" headline="Filters">
<md-button slot="close" variant="text">Done</md-button>
<p>A text button replaces the close glyph.</p>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-actions-btn').addEventListener('click', () => {
document.getElementById('bs-actions').show();
});
document.getElementById('bs-customclose-btn').addEventListener('click', () => {
document.getElementById('bs-customclose').show();
});
</script>import { useRef } from 'react';
import { MdBottomSheet, MdButton } from '@awc-ui/react';
export function Demo() {
const bsActionsRef = useRef(null);
const bsCustomcloseRef = useRef(null);
return (
<>
<MdButton id="bs-actions-btn" variant="filled" onClick={() => bsActionsRef.current?.show()}>Sheet with actions</MdButton>
<MdButton id="bs-customclose-btn" variant="outlined" onClick={() => bsCustomcloseRef.current?.show()}>Custom close control</MdButton>
<MdBottomSheet id="bs-actions" ref={bsActionsRef} headline="Delete conversation?" closeable>
<p>This removes it for everyone in the thread.</p>
<MdButton slot="actions" variant="text">Cancel</MdButton>
<MdButton slot="actions" variant="filled">Delete</MdButton>
</MdBottomSheet>
<MdBottomSheet id="bs-customclose" ref={bsCustomcloseRef} headline="Filters">
<MdButton slot="close" variant="text">Done</MdButton>
<p>A text button replaces the close glyph.</p>
</MdBottomSheet>
</>
);
}// 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-button id="bs-actions-btn" variant="filled" (click)="bsActions.show()">Sheet with actions</md-button>
<md-button id="bs-customclose-btn" variant="outlined" (click)="bsCustomclose.show()">Custom close control</md-button>
<md-bottom-sheet id="bs-actions" #bsActions headline="Delete conversation?" closeable>
<p>This removes it for everyone in the thread.</p>
<md-button slot="actions" variant="text">Cancel</md-button>
<md-button slot="actions" variant="filled">Delete</md-button>
</md-bottom-sheet>
<md-bottom-sheet id="bs-customclose" #bsCustomclose headline="Filters">
<md-button slot="close" variant="text">Done</md-button>
<p>A text button replaces the close glyph.</p>
</md-bottom-sheet><script setup>
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsActions = ref(null);
const bsCustomclose = ref(null);
</script>
<template>
<md-button id="bs-actions-btn" variant="filled" @click="bsActions?.show()">Sheet with actions</md-button>
<md-button id="bs-customclose-btn" variant="outlined" @click="bsCustomclose?.show()">Custom close control</md-button>
<md-bottom-sheet id="bs-actions" ref="bsActions" headline="Delete conversation?" closeable>
<p>This removes it for everyone in the thread.</p>
<md-button slot="actions" variant="text">Cancel</md-button>
<md-button slot="actions" variant="filled">Delete</md-button>
</md-bottom-sheet>
<md-bottom-sheet id="bs-customclose" ref="bsCustomclose" headline="Filters">
<md-button slot="close" variant="text">Done</md-button>
<p>A text button replaces the close glyph.</p>
</md-bottom-sheet>
</template><script>
import '@awc-ui/core/define';
let bsActions;
let bsCustomclose;
</script>
<md-button id="bs-actions-btn" variant="filled" on:click={() => bsActions?.show()}>Sheet with actions</md-button>
<md-button id="bs-customclose-btn" variant="outlined" on:click={() => bsCustomclose?.show()}>Custom close control</md-button>
<md-bottom-sheet id="bs-actions" bind:this={bsActions} headline="Delete conversation?" closeable>
<p>This removes it for everyone in the thread.</p>
<md-button slot="actions" variant="text">Cancel</md-button>
<md-button slot="actions" variant="filled">Delete</md-button>
</md-bottom-sheet>
<md-bottom-sheet id="bs-customclose" bind:this={bsCustomclose} headline="Filters">
<md-button slot="close" variant="text">Done</md-button>
<p>A text button replaces the close glyph.</p>
</md-bottom-sheet>Reach for a slotted close when “Close” should read as an explicit affirmation — Done, Got it, Apply — rather than a dismissal glyph:
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-button id="bs-done-btn" variant="filled">Sheet with a Done button</md-button>
<md-bottom-sheet id="bs-done" headline="Sort by">
<md-button slot="close" variant="text">Done</md-button>
<md-list>
<md-list-item headline="Relevance"></md-list-item>
<md-list-item headline="Newest first"></md-list-item>
<md-list-item headline="Price"></md-list-item>
</md-list>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-done-btn').addEventListener('click', () => {
document.getElementById('bs-done').show();
});
</script>import { useRef } from 'react';
import { MdBottomSheet, MdButton, MdList, MdListItem } from '@awc-ui/react';
export function Demo() {
const bsDoneRef = useRef(null);
return (
<>
<MdButton id="bs-done-btn" variant="filled" onClick={() => bsDoneRef.current?.show()}>Sheet with a Done button</MdButton>
<MdBottomSheet id="bs-done" ref={bsDoneRef} headline="Sort by">
<MdButton slot="close" variant="text">Done</MdButton>
<MdList>
<MdListItem headline="Relevance"></MdListItem>
<MdListItem headline="Newest first"></MdListItem>
<MdListItem headline="Price"></MdListItem>
</MdList>
</MdBottomSheet>
</>
);
}// 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-button id="bs-done-btn" variant="filled" (click)="bsDone.show()">Sheet with a Done button</md-button>
<md-bottom-sheet id="bs-done" #bsDone headline="Sort by">
<md-button slot="close" variant="text">Done</md-button>
<md-list>
<md-list-item headline="Relevance"></md-list-item>
<md-list-item headline="Newest first"></md-list-item>
<md-list-item headline="Price"></md-list-item>
</md-list>
</md-bottom-sheet><script setup>
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsDone = ref(null);
</script>
<template>
<md-button id="bs-done-btn" variant="filled" @click="bsDone?.show()">Sheet with a Done button</md-button>
<md-bottom-sheet id="bs-done" ref="bsDone" headline="Sort by">
<md-button slot="close" variant="text">Done</md-button>
<md-list>
<md-list-item headline="Relevance"></md-list-item>
<md-list-item headline="Newest first"></md-list-item>
<md-list-item headline="Price"></md-list-item>
</md-list>
</md-bottom-sheet>
</template><script>
import '@awc-ui/core/define';
let bsDone;
</script>
<md-button id="bs-done-btn" variant="filled" on:click={() => bsDone?.show()}>Sheet with a Done button</md-button>
<md-bottom-sheet id="bs-done" bind:this={bsDone} headline="Sort by">
<md-button slot="close" variant="text">Done</md-button>
<md-list>
<md-list-item headline="Relevance"></md-list-item>
<md-list-item headline="Newest first"></md-list-item>
<md-list-item headline="Price"></md-list-item>
</md-list>
</md-bottom-sheet>The slotted-close story
asserts the whole path, including that it emits mdClose without mdCancel.
headline-align and content-align take start (default), center or end
and are logical — they mirror in RTL. scrim-dismissible defaults to
true; turn it off when an accidental tap outside would lose work.
Both the headline and the body are centred.
The scrim will not dismiss this sheet — pick one of the actions. Escape still works, and it fires mdCancel.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-button id="bs-centered-btn" variant="filled">Centred</md-button>
<md-button id="bs-persistent-btn" variant="tonal">Non-dismissible scrim</md-button>
<md-bottom-sheet id="bs-centered" headline="Choose a plan" headline-align="center" content-align="center" closeable>
<p>Both the headline and the body are centred.</p>
<md-button slot="actions" variant="filled">Continue</md-button>
</md-bottom-sheet>
<md-bottom-sheet id="bs-persistent" headline="Discard changes?" scrim-dismissible="false">
<p>The scrim is inert here. Escape still dismisses, and still fires mdCancel.</p>
<md-button slot="actions" variant="text">Keep editing</md-button>
<md-button slot="actions" variant="filled">Discard</md-button>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-centered-btn').addEventListener('click', () => {
document.getElementById('bs-centered').show();
});
document.getElementById('bs-persistent-btn').addEventListener('click', () => {
document.getElementById('bs-persistent').show();
});
</script>import { useRef } from 'react';
import { MdBottomSheet, MdButton } from '@awc-ui/react';
export function Demo() {
const bsCenteredRef = useRef(null);
const bsPersistentRef = useRef(null);
return (
<>
<MdButton id="bs-centered-btn" variant="filled" onClick={() => bsCenteredRef.current?.show()}>Centred</MdButton>
<MdButton id="bs-persistent-btn" variant="tonal" onClick={() => bsPersistentRef.current?.show()}>Non-dismissible scrim</MdButton>
<MdBottomSheet id="bs-centered" ref={bsCenteredRef} headline="Choose a plan" headlineAlign="center" contentAlign="center" closeable>
<p>Both the headline and the body are centred.</p>
<MdButton slot="actions" variant="filled">Continue</MdButton>
</MdBottomSheet>
{/* scrimDismissible is a boolean prop: pass {false}, not the string "false". */}
<MdBottomSheet id="bs-persistent" ref={bsPersistentRef} headline="Discard changes?" scrimDismissible={false}>
<p>The scrim is inert here. Escape still dismisses, and still fires mdCancel.</p>
<MdButton slot="actions" variant="text">Keep editing</MdButton>
<MdButton slot="actions" variant="filled">Discard</MdButton>
</MdBottomSheet>
</>
);
}// 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-button id="bs-centered-btn" variant="filled" (click)="bsCentered.show()">Centred</md-button>
<md-button id="bs-persistent-btn" variant="tonal" (click)="bsPersistent.show()">Non-dismissible scrim</md-button>
<md-bottom-sheet id="bs-centered" #bsCentered headline="Choose a plan" headline-align="center" content-align="center" closeable>
<p>Both the headline and the body are centred.</p>
<md-button slot="actions" variant="filled">Continue</md-button>
</md-bottom-sheet>
<md-bottom-sheet id="bs-persistent" #bsPersistent headline="Discard changes?" scrim-dismissible="false">
<p>The scrim is inert here. Escape still dismisses, and still fires mdCancel.</p>
<md-button slot="actions" variant="text">Keep editing</md-button>
<md-button slot="actions" variant="filled">Discard</md-button>
</md-bottom-sheet><script setup>
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsCentered = ref(null);
const bsPersistent = ref(null);
</script>
<template>
<md-button id="bs-centered-btn" variant="filled" @click="bsCentered?.show()">Centred</md-button>
<md-button id="bs-persistent-btn" variant="tonal" @click="bsPersistent?.show()">Non-dismissible scrim</md-button>
<md-bottom-sheet id="bs-centered" ref="bsCentered" headline="Choose a plan" headline-align="center" content-align="center" closeable>
<p>Both the headline and the body are centred.</p>
<md-button slot="actions" variant="filled">Continue</md-button>
</md-bottom-sheet>
<md-bottom-sheet id="bs-persistent" ref="bsPersistent" headline="Discard changes?" scrim-dismissible="false">
<p>The scrim is inert here. Escape still dismisses, and still fires mdCancel.</p>
<md-button slot="actions" variant="text">Keep editing</md-button>
<md-button slot="actions" variant="filled">Discard</md-button>
</md-bottom-sheet>
</template><script>
import '@awc-ui/core/define';
let bsCentered;
let bsPersistent;
</script>
<md-button id="bs-centered-btn" variant="filled" on:click={() => bsCentered?.show()}>Centred</md-button>
<md-button id="bs-persistent-btn" variant="tonal" on:click={() => bsPersistent?.show()}>Non-dismissible scrim</md-button>
<md-bottom-sheet id="bs-centered" bind:this={bsCentered} headline="Choose a plan" headline-align="center" content-align="center" closeable>
<p>Both the headline and the body are centred.</p>
<md-button slot="actions" variant="filled">Continue</md-button>
</md-bottom-sheet>
<md-bottom-sheet id="bs-persistent" bind:this={bsPersistent} headline="Discard changes?" scrim-dismissible="false">
<p>The scrim is inert here. Escape still dismisses, and still fires mdCancel.</p>
<md-button slot="actions" variant="text">Keep editing</md-button>
<md-button slot="actions" variant="filled">Discard</md-button>
</md-bottom-sheet>The drag handle is a pointer affordance: press it, pull down past the 100px threshold and release to dismiss — release above it and the sheet springs back.
Grab the handle at the top and pull down past the threshold — release below it and the sheet dismisses, release above it and it springs back.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-button id="bs-drag-btn" variant="filled">Drag me down to dismiss</md-button>
<md-bottom-sheet id="bs-drag" headline="Drag the handle" show-drag-handle closeable>
<p style="margin: 0;">Grab the handle at the top and pull down past the threshold — release below it and the sheet dismisses, release above it and it springs back.</p>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-drag-btn').addEventListener('click', () => {
document.getElementById('bs-drag').show();
});
</script>import { useRef } from 'react';
import { MdBottomSheet, MdButton } from '@awc-ui/react';
export function Demo() {
const bsDragRef = useRef(null);
return (
<>
<MdButton id="bs-drag-btn" variant="filled" onClick={() => bsDragRef.current?.show()}>Drag me down to dismiss</MdButton>
<MdBottomSheet id="bs-drag" ref={bsDragRef} headline="Drag the handle" showDragHandle closeable>
<p style={{ margin: '0' }}>Grab the handle at the top and pull down past the threshold — release below it and the sheet dismisses, release above it and it springs back.</p>
</MdBottomSheet>
</>
);
}// 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-button id="bs-drag-btn" variant="filled" (click)="bsDrag.show()">Drag me down to dismiss</md-button>
<md-bottom-sheet id="bs-drag" #bsDrag headline="Drag the handle" show-drag-handle closeable>
<p style="margin: 0;">Grab the handle at the top and pull down past the threshold — release below it and the sheet dismisses, release above it and it springs back.</p>
</md-bottom-sheet><script setup>
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsDrag = ref(null);
</script>
<template>
<md-button id="bs-drag-btn" variant="filled" @click="bsDrag?.show()">Drag me down to dismiss</md-button>
<md-bottom-sheet id="bs-drag" ref="bsDrag" headline="Drag the handle" show-drag-handle closeable>
<p style="margin: 0;">Grab the handle at the top and pull down past the threshold — release below it and the sheet dismisses, release above it and it springs back.</p>
</md-bottom-sheet>
</template><script>
import '@awc-ui/core/define';
let bsDrag;
</script>
<md-button id="bs-drag-btn" variant="filled" on:click={() => bsDrag?.show()}>Drag me down to dismiss</md-button>
<md-bottom-sheet id="bs-drag" bind:this={bsDrag} headline="Drag the handle" show-drag-handle closeable>
<p style="margin: 0;">Grab the handle at the top and pull down past the threshold — release below it and the sheet dismisses, release above it and it springs back.</p>
</md-bottom-sheet>| Method | Returns | Notes |
|---|---|---|
show() | Promise<void> | Opens the sheet and moves focus into it |
close() | Promise<void> | Closes it and fires mdClose — but not mdCancel |
A programmatic close() is how you tell “the user accepted” from “the user
dismissed”: the sheet below has no close glyph and an inert scrim, so its only
exit is the action button.
The scrim is a no-op here and there is no close glyph — the only way out is the action below, so the choice cannot be skipped by tapping away. (Escape still works, and it fires mdCancel.)
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-button id="bs-required-btn" variant="filled">Required action</md-button>
<md-bottom-sheet id="bs-required" headline="Accept the terms" scrim-dismissible="false">
<p>The only way out is the action below.</p>
<md-button slot="actions" variant="filled">I understand</md-button>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-required-btn').addEventListener('click', () => {
document.getElementById('bs-required').show();
});
</script>import { useRef } from 'react';
import { MdBottomSheet, MdButton } from '@awc-ui/react';
export function Demo() {
const bsRequiredRef = useRef(null);
return (
<>
<MdButton id="bs-required-btn" variant="filled" onClick={() => bsRequiredRef.current?.show()}>Required action</MdButton>
{/* scrimDismissible is a boolean prop: pass {false}, not the string "false". */}
<MdBottomSheet id="bs-required" ref={bsRequiredRef} headline="Accept the terms" scrimDismissible={false}>
<p>The only way out is the action below.</p>
<MdButton slot="actions" variant="filled" onClick={() => bsRequiredRef.current?.close()}>I understand</MdButton>
</MdBottomSheet>
</>
);
}// 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-button id="bs-required-btn" variant="filled" (click)="bsRequired.show()">Required action</md-button>
<md-bottom-sheet id="bs-required" #bsRequired headline="Accept the terms" scrim-dismissible="false">
<p>The only way out is the action below.</p>
<md-button slot="actions" variant="filled">I understand</md-button>
</md-bottom-sheet><script setup>
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsRequired = ref(null);
</script>
<template>
<md-button id="bs-required-btn" variant="filled" @click="bsRequired?.show()">Required action</md-button>
<md-bottom-sheet id="bs-required" ref="bsRequired" headline="Accept the terms" scrim-dismissible="false">
<p>The only way out is the action below.</p>
<md-button slot="actions" variant="filled">I understand</md-button>
</md-bottom-sheet>
</template><script>
import '@awc-ui/core/define';
let bsRequired;
</script>
<md-button id="bs-required-btn" variant="filled" on:click={() => bsRequired?.show()}>Required action</md-button>
<md-bottom-sheet id="bs-required" bind:this={bsRequired} headline="Accept the terms" scrim-dismissible="false">
<p>The only way out is the action below.</p>
<md-button slot="actions" variant="filled">I understand</md-button>
</md-bottom-sheet>Every dismissal route has its own story: the close button, dragging the handle down (and the drag threshold), Escape plus the focus trap, and the non-dismissible case where none of them apply.
Unlike md-card, a bottom sheet is meant to scroll its
body internally: the content area plain-scrolls once it outgrows
--md-bottom-sheet-max-height (80vh by default), while the header and the
actions row stay pinned. Never scroll it horizontally.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-button id="bs-scroll-btn" variant="filled">Long list</md-button>
<md-bottom-sheet id="bs-scroll" headline="Select a country" top-divider closeable>
<md-list>
<md-list-item headline="Argentina"></md-list-item>
<md-list-item headline="Brazil"></md-list-item>
<md-list-item headline="Canada"></md-list-item>
<md-list-item headline="Denmark"></md-list-item>
<md-list-item headline="Egypt"></md-list-item>
<md-list-item headline="France"></md-list-item>
</md-list>
<md-button slot="actions" variant="text">Cancel</md-button>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-scroll-btn').addEventListener('click', () => {
document.getElementById('bs-scroll').show();
});
</script>import { useRef } from 'react';
import { MdBottomSheet, MdButton, MdList, MdListItem } from '@awc-ui/react';
export function Demo() {
const bsScrollRef = useRef(null);
return (
<>
<MdButton id="bs-scroll-btn" variant="filled" onClick={() => bsScrollRef.current?.show()}>Long list</MdButton>
<MdBottomSheet id="bs-scroll" ref={bsScrollRef} headline="Select a country" topDivider closeable>
<MdList>
<MdListItem headline="Argentina"></MdListItem>
<MdListItem headline="Brazil"></MdListItem>
<MdListItem headline="Canada"></MdListItem>
<MdListItem headline="Denmark"></MdListItem>
<MdListItem headline="Egypt"></MdListItem>
<MdListItem headline="France"></MdListItem>
</MdList>
<MdButton slot="actions" variant="text">Cancel</MdButton>
</MdBottomSheet>
</>
);
}// 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-button id="bs-scroll-btn" variant="filled" (click)="bsScroll.show()">Long list</md-button>
<md-bottom-sheet id="bs-scroll" #bsScroll headline="Select a country" top-divider closeable>
<md-list>
<md-list-item headline="Argentina"></md-list-item>
<md-list-item headline="Brazil"></md-list-item>
<md-list-item headline="Canada"></md-list-item>
<md-list-item headline="Denmark"></md-list-item>
<md-list-item headline="Egypt"></md-list-item>
<md-list-item headline="France"></md-list-item>
</md-list>
<md-button slot="actions" variant="text">Cancel</md-button>
</md-bottom-sheet><script setup>
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsScroll = ref(null);
</script>
<template>
<md-button id="bs-scroll-btn" variant="filled" @click="bsScroll?.show()">Long list</md-button>
<md-bottom-sheet id="bs-scroll" ref="bsScroll" headline="Select a country" top-divider closeable>
<md-list>
<md-list-item headline="Argentina"></md-list-item>
<md-list-item headline="Brazil"></md-list-item>
<md-list-item headline="Canada"></md-list-item>
<md-list-item headline="Denmark"></md-list-item>
<md-list-item headline="Egypt"></md-list-item>
<md-list-item headline="France"></md-list-item>
</md-list>
<md-button slot="actions" variant="text">Cancel</md-button>
</md-bottom-sheet>
</template><script>
import '@awc-ui/core/define';
let bsScroll;
</script>
<md-button id="bs-scroll-btn" variant="filled" on:click={() => bsScroll?.show()}>Long list</md-button>
<md-bottom-sheet id="bs-scroll" bind:this={bsScroll} headline="Select a country" top-divider closeable>
<md-list>
<md-list-item headline="Argentina"></md-list-item>
<md-list-item headline="Brazil"></md-list-item>
<md-list-item headline="Canada"></md-list-item>
<md-list-item headline="Denmark"></md-list-item>
<md-list-item headline="Egypt"></md-list-item>
<md-list-item headline="France"></md-list-item>
</md-list>
<md-button slot="actions" variant="text">Cancel</md-button>
</md-bottom-sheet>Keep the sheet short enough to leave context visible. A sheet that covers the
whole viewport should be a full-screen md-dialog
instead.
Width and height come from the --md-bottom-sheet-width / -height bounds
rather than a prop, so one sheet can be a full-width tray on phones and a
narrower panel on a wide screen without changing markup. The reflow is built
in: below 640px the sheet is full-bleed, and at 640px and up it defaults to a
centred 640px panel capped at calc(100% - 112px). Setting either custom
property overrides the breakpoint’s fallback at both sizes.
Capped at 420px, centred on a wide viewport.
Fixed to 60vh regardless of content.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-button id="bs-narrow-btn" variant="filled">Narrow sheet</md-button>
<md-button id="bs-tall-btn" variant="outlined">Tall sheet</md-button>
<md-bottom-sheet id="bs-narrow" headline="Narrow" closeable style="--md-bottom-sheet-max-width: 420px;">
<p style="margin: 0;">Capped at 420px, centred on a wide viewport.</p>
</md-bottom-sheet>
<md-bottom-sheet id="bs-tall" headline="Tall" closeable style="--md-bottom-sheet-height: 60vh;">
<p style="margin: 0;">Fixed to 60vh regardless of content.</p>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-narrow-btn').addEventListener('click', () => {
document.getElementById('bs-narrow').show();
});
document.getElementById('bs-tall-btn').addEventListener('click', () => {
document.getElementById('bs-tall').show();
});
</script>import { useRef } from 'react';
import { MdBottomSheet, MdButton } from '@awc-ui/react';
export function Demo() {
const bsNarrowRef = useRef(null);
const bsTallRef = useRef(null);
return (
<>
<MdButton id="bs-narrow-btn" variant="filled" onClick={() => bsNarrowRef.current?.show()}>Narrow sheet</MdButton>
<MdButton id="bs-tall-btn" variant="outlined" onClick={() => bsTallRef.current?.show()}>Tall sheet</MdButton>
<MdBottomSheet id="bs-narrow" ref={bsNarrowRef} headline="Narrow" closeable style={{ '--md-bottom-sheet-max-width': '420px' }}>
<p style={{ margin: '0' }}>Capped at 420px, centred on a wide viewport.</p>
</MdBottomSheet>
<MdBottomSheet id="bs-tall" ref={bsTallRef} headline="Tall" closeable style={{ '--md-bottom-sheet-height': '60vh' }}>
<p style={{ margin: '0' }}>Fixed to 60vh regardless of content.</p>
</MdBottomSheet>
</>
);
}// 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-button id="bs-narrow-btn" variant="filled" (click)="bsNarrow.show()">Narrow sheet</md-button>
<md-button id="bs-tall-btn" variant="outlined" (click)="bsTall.show()">Tall sheet</md-button>
<md-bottom-sheet id="bs-narrow" #bsNarrow headline="Narrow" closeable style="--md-bottom-sheet-max-width: 420px;">
<p style="margin: 0;">Capped at 420px, centred on a wide viewport.</p>
</md-bottom-sheet>
<md-bottom-sheet id="bs-tall" #bsTall headline="Tall" closeable style="--md-bottom-sheet-height: 60vh;">
<p style="margin: 0;">Fixed to 60vh regardless of content.</p>
</md-bottom-sheet><script setup>
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsNarrow = ref(null);
const bsTall = ref(null);
</script>
<template>
<md-button id="bs-narrow-btn" variant="filled" @click="bsNarrow?.show()">Narrow sheet</md-button>
<md-button id="bs-tall-btn" variant="outlined" @click="bsTall?.show()">Tall sheet</md-button>
<md-bottom-sheet id="bs-narrow" ref="bsNarrow" headline="Narrow" closeable style="--md-bottom-sheet-max-width: 420px;">
<p style="margin: 0;">Capped at 420px, centred on a wide viewport.</p>
</md-bottom-sheet>
<md-bottom-sheet id="bs-tall" ref="bsTall" headline="Tall" closeable style="--md-bottom-sheet-height: 60vh;">
<p style="margin: 0;">Fixed to 60vh regardless of content.</p>
</md-bottom-sheet>
</template><script>
import '@awc-ui/core/define';
let bsNarrow;
let bsTall;
</script>
<md-button id="bs-narrow-btn" variant="filled" on:click={() => bsNarrow?.show()}>Narrow sheet</md-button>
<md-button id="bs-tall-btn" variant="outlined" on:click={() => bsTall?.show()}>Tall sheet</md-button>
<md-bottom-sheet id="bs-narrow" bind:this={bsNarrow} headline="Narrow" closeable style="--md-bottom-sheet-max-width: 420px;">
<p style="margin: 0;">Capped at 420px, centred on a wide viewport.</p>
</md-bottom-sheet>
<md-bottom-sheet id="bs-tall" bind:this={bsTall} headline="Tall" closeable style="--md-bottom-sheet-height: 60vh;">
<p style="margin: 0;">Fixed to 60vh regardless of content.</p>
</md-bottom-sheet>The sheet is fluid by default and caps at 80vh, so it always leaves some
context visible above it.
Full-bleed on a phone, capped and centred on a wide screen — one sheet, no breakpoint markup. Resize the window with this open and watch it re-centre.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-button id="bs-resp-btn" variant="filled">Open, then resize the window</md-button>
<md-bottom-sheet id="bs-resp" headline="Fluid by default" closeable style="--md-bottom-sheet-max-width: 640px;">
<p style="margin: 0;">Full-bleed on a phone, capped and centred on a wide screen — one sheet, no breakpoint markup. Resize the window with this open and watch it re-centre.</p>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-resp-btn').addEventListener('click', () => {
document.getElementById('bs-resp').show();
});
</script>import { useRef } from 'react';
import { MdBottomSheet, MdButton } from '@awc-ui/react';
export function Demo() {
const bsRespRef = useRef(null);
return (
<>
<MdButton id="bs-resp-btn" variant="filled" onClick={() => bsRespRef.current?.show()}>Open, then resize the window</MdButton>
<MdBottomSheet id="bs-resp" ref={bsRespRef} headline="Fluid by default" closeable style={{ '--md-bottom-sheet-max-width': '640px' }}>
<p style={{ margin: '0' }}>Full-bleed on a phone, capped and centred on a wide screen — one sheet, no breakpoint markup. Resize the window with this open and watch it re-centre.</p>
</MdBottomSheet>
</>
);
}// 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-button id="bs-resp-btn" variant="filled" (click)="bsResp.show()">Open, then resize the window</md-button>
<md-bottom-sheet id="bs-resp" #bsResp headline="Fluid by default" closeable style="--md-bottom-sheet-max-width: 640px;">
<p style="margin: 0;">Full-bleed on a phone, capped and centred on a wide screen — one sheet, no breakpoint markup. Resize the window with this open and watch it re-centre.</p>
</md-bottom-sheet><script setup>
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsResp = ref(null);
</script>
<template>
<md-button id="bs-resp-btn" variant="filled" @click="bsResp?.show()">Open, then resize the window</md-button>
<md-bottom-sheet id="bs-resp" ref="bsResp" headline="Fluid by default" closeable style="--md-bottom-sheet-max-width: 640px;">
<p style="margin: 0;">Full-bleed on a phone, capped and centred on a wide screen — one sheet, no breakpoint markup. Resize the window with this open and watch it re-centre.</p>
</md-bottom-sheet>
</template><script>
import '@awc-ui/core/define';
let bsResp;
</script>
<md-button id="bs-resp-btn" variant="filled" on:click={() => bsResp?.show()}>Open, then resize the window</md-button>
<md-bottom-sheet id="bs-resp" bind:this={bsResp} headline="Fluid by default" closeable style="--md-bottom-sheet-max-width: 640px;">
<p style="margin: 0;">Full-bleed on a phone, capped and centred on a wide screen — one sheet, no breakpoint markup. Resize the window with this open and watch it re-centre.</p>
</md-bottom-sheet>The responsiveness story steps through the breakpoints in Storybook.
Three worked examples — a form sheet, a media sheet and a filter panel — plus the canonical M3 share sheet.
The form sheet is the case that most often gets focus handling wrong: show()
moves focus to the first field, the focus guards keep Tab inside, and
Escape hands it back to the trigger.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-button id="bs-form-btn" variant="filled">Form inside a sheet</md-button>
<md-bottom-sheet id="bs-form" headline="Add a label" closeable>
<div style="display: grid; gap: 16px;">
<md-text-field label="Name" variant="outlined"></md-text-field>
<md-text-field label="Colour" variant="outlined"></md-text-field>
</div>
<md-button slot="actions" variant="text">Cancel</md-button>
<md-button slot="actions" variant="filled">Save</md-button>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-form-btn').addEventListener('click', () => {
document.getElementById('bs-form').show();
});
</script>import { useRef } from 'react';
import { MdBottomSheet, MdButton, MdTextField } from '@awc-ui/react';
export function Demo() {
const bsFormRef = useRef(null);
return (
<>
<MdButton id="bs-form-btn" variant="filled" onClick={() => bsFormRef.current?.show()}>Form inside a sheet</MdButton>
<MdBottomSheet id="bs-form" ref={bsFormRef} headline="Add a label" closeable>
<div style={{ display: 'grid', gap: '16px' }}>
<MdTextField label="Name" variant="outlined"></MdTextField>
<MdTextField label="Colour" variant="outlined"></MdTextField>
</div>
<MdButton slot="actions" variant="text">Cancel</MdButton>
<MdButton slot="actions" variant="filled">Save</MdButton>
</MdBottomSheet>
</>
);
}// 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-button id="bs-form-btn" variant="filled" (click)="bsForm.show()">Form inside a sheet</md-button>
<md-bottom-sheet id="bs-form" #bsForm headline="Add a label" closeable>
<div style="display: grid; gap: 16px;">
<md-text-field label="Name" variant="outlined"></md-text-field>
<md-text-field label="Colour" variant="outlined"></md-text-field>
</div>
<md-button slot="actions" variant="text">Cancel</md-button>
<md-button slot="actions" variant="filled">Save</md-button>
</md-bottom-sheet><script setup>
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsForm = ref(null);
</script>
<template>
<md-button id="bs-form-btn" variant="filled" @click="bsForm?.show()">Form inside a sheet</md-button>
<md-bottom-sheet id="bs-form" ref="bsForm" headline="Add a label" closeable>
<div style="display: grid; gap: 16px;">
<md-text-field label="Name" variant="outlined"></md-text-field>
<md-text-field label="Colour" variant="outlined"></md-text-field>
</div>
<md-button slot="actions" variant="text">Cancel</md-button>
<md-button slot="actions" variant="filled">Save</md-button>
</md-bottom-sheet>
</template><script>
import '@awc-ui/core/define';
let bsForm;
</script>
<md-button id="bs-form-btn" variant="filled" on:click={() => bsForm?.show()}>Form inside a sheet</md-button>
<md-bottom-sheet id="bs-form" bind:this={bsForm} headline="Add a label" closeable>
<div style="display: grid; gap: 16px;">
<md-text-field label="Name" variant="outlined"></md-text-field>
<md-text-field label="Colour" variant="outlined"></md-text-field>
</div>
<md-button slot="actions" variant="text">Cancel</md-button>
<md-button slot="actions" variant="filled">Save</md-button>
</md-bottom-sheet><!-- 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-button id="bs-music-btn" variant="filled" icon="play_arrow">Now playing</md-button>
<md-bottom-sheet id="bs-music" variant="detached">
<div style="text-align: center; padding-block: 8px;">
<div style="inline-size: 200px; block-size: 200px; margin: 0 auto 16px; border-radius: 16px; background: linear-gradient(135deg, var(--md-sys-color-primary-container), var(--md-sys-color-tertiary-container)); display: flex; align-items: center; justify-content: center;">
<span class="material-symbols-outlined" aria-hidden="true" style="font-size: 64px; color: var(--md-sys-color-on-primary-container);">music_note</span>
</div>
<div style="font-size: 20px; font-weight: 500; margin-block-end: 4px;">Song Title</div>
<div style="font-size: 14px; color: var(--md-sys-color-on-surface-variant);">Artist Name</div>
<div style="display: flex; justify-content: center; gap: 24px; margin-block-start: 24px;">
<md-icon-button variant="standard" icon="skip_previous" aria-label="Previous"></md-icon-button>
<md-icon-button variant="filled" icon="play_arrow" aria-label="Play"></md-icon-button>
<md-icon-button variant="standard" icon="skip_next" aria-label="Next"></md-icon-button>
</div>
</div>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-music-btn').addEventListener('click', () => {
document.getElementById('bs-music').show();
});
</script>// Icons need the Material Symbols stylesheet in index.html — see Installation.
import { useRef } from 'react';
import { MdBottomSheet, MdButton, MdIconButton } from '@awc-ui/react';
export function Demo() {
const bsMusicRef = useRef(null);
return (
<>
<MdButton id="bs-music-btn" variant="filled" icon="play_arrow" onClick={() => bsMusicRef.current?.show()}>Now playing</MdButton>
<MdBottomSheet id="bs-music" ref={bsMusicRef} variant="detached">
<div style={{ textAlign: 'center', paddingBlock: '8px' }}>
<div style={{ inlineSize: '200px', blockSize: '200px', margin: '0 auto 16px', borderRadius: '16px', background: 'linear-gradient(135deg, var(--md-sys-color-primary-container), var(--md-sys-color-tertiary-container))', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<span className="material-symbols-outlined" aria-hidden="true" style={{ fontSize: '64px', color: 'var(--md-sys-color-on-primary-container)' }}>music_note</span>
</div>
<div style={{ fontSize: '20px', fontWeight: '500', marginBlockEnd: '4px' }}>Song Title</div>
<div style={{ fontSize: '14px', color: 'var(--md-sys-color-on-surface-variant)' }}>Artist Name</div>
<div style={{ display: 'flex', justifyContent: 'center', gap: '24px', marginBlockStart: '24px' }}>
<MdIconButton variant="standard" icon="skip_previous" aria-label="Previous"></MdIconButton>
<MdIconButton variant="filled" icon="play_arrow" aria-label="Play"></MdIconButton>
<MdIconButton variant="standard" icon="skip_next" aria-label="Next"></MdIconButton>
</div>
</div>
</MdBottomSheet>
</>
);
}// 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-button id="bs-music-btn" variant="filled" icon="play_arrow" (click)="bsMusic.show()">Now playing</md-button>
<md-bottom-sheet id="bs-music" #bsMusic variant="detached">
<div style="text-align: center; padding-block: 8px;">
<div style="inline-size: 200px; block-size: 200px; margin: 0 auto 16px; border-radius: 16px; background: linear-gradient(135deg, var(--md-sys-color-primary-container), var(--md-sys-color-tertiary-container)); display: flex; align-items: center; justify-content: center;">
<span class="material-symbols-outlined" aria-hidden="true" style="font-size: 64px; color: var(--md-sys-color-on-primary-container);">music_note</span>
</div>
<div style="font-size: 20px; font-weight: 500; margin-block-end: 4px;">Song Title</div>
<div style="font-size: 14px; color: var(--md-sys-color-on-surface-variant);">Artist Name</div>
<div style="display: flex; justify-content: center; gap: 24px; margin-block-start: 24px;">
<md-icon-button variant="standard" icon="skip_previous" aria-label="Previous"></md-icon-button>
<md-icon-button variant="filled" icon="play_arrow" aria-label="Play"></md-icon-button>
<md-icon-button variant="standard" icon="skip_next" aria-label="Next"></md-icon-button>
</div>
</div>
</md-bottom-sheet><script setup>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsMusic = ref(null);
</script>
<template>
<md-button id="bs-music-btn" variant="filled" icon="play_arrow" @click="bsMusic?.show()">Now playing</md-button>
<md-bottom-sheet id="bs-music" ref="bsMusic" variant="detached">
<div style="text-align: center; padding-block: 8px;">
<div style="inline-size: 200px; block-size: 200px; margin: 0 auto 16px; border-radius: 16px; background: linear-gradient(135deg, var(--md-sys-color-primary-container), var(--md-sys-color-tertiary-container)); display: flex; align-items: center; justify-content: center;">
<span class="material-symbols-outlined" aria-hidden="true" style="font-size: 64px; color: var(--md-sys-color-on-primary-container);">music_note</span>
</div>
<div style="font-size: 20px; font-weight: 500; margin-block-end: 4px;">Song Title</div>
<div style="font-size: 14px; color: var(--md-sys-color-on-surface-variant);">Artist Name</div>
<div style="display: flex; justify-content: center; gap: 24px; margin-block-start: 24px;">
<md-icon-button variant="standard" icon="skip_previous" aria-label="Previous"></md-icon-button>
<md-icon-button variant="filled" icon="play_arrow" aria-label="Play"></md-icon-button>
<md-icon-button variant="standard" icon="skip_next" aria-label="Next"></md-icon-button>
</div>
</div>
</md-bottom-sheet>
</template><script>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import '@awc-ui/core/define';
let bsMusic;
</script>
<md-button id="bs-music-btn" variant="filled" icon="play_arrow" on:click={() => bsMusic?.show()}>Now playing</md-button>
<md-bottom-sheet id="bs-music" bind:this={bsMusic} variant="detached">
<div style="text-align: center; padding-block: 8px;">
<div style="inline-size: 200px; block-size: 200px; margin: 0 auto 16px; border-radius: 16px; background: linear-gradient(135deg, var(--md-sys-color-primary-container), var(--md-sys-color-tertiary-container)); display: flex; align-items: center; justify-content: center;">
<span class="material-symbols-outlined" aria-hidden="true" style="font-size: 64px; color: var(--md-sys-color-on-primary-container);">music_note</span>
</div>
<div style="font-size: 20px; font-weight: 500; margin-block-end: 4px;">Song Title</div>
<div style="font-size: 14px; color: var(--md-sys-color-on-surface-variant);">Artist Name</div>
<div style="display: flex; justify-content: center; gap: 24px; margin-block-start: 24px;">
<md-icon-button variant="standard" icon="skip_previous" aria-label="Previous"></md-icon-button>
<md-icon-button variant="filled" icon="play_arrow" aria-label="Play"></md-icon-button>
<md-icon-button variant="standard" icon="skip_next" aria-label="Next"></md-icon-button>
</div>
</div>
</md-bottom-sheet>A filter panel is the other common shape: chips and switches over a divider, with the actions row committing or clearing.
<!-- 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-button id="bs-filters-btn" variant="filled" icon="tune">Filters</md-button>
<md-bottom-sheet id="bs-filters" headline="Filters" closeable>
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<md-chip>Electronics</md-chip>
<md-chip>Clothing</md-chip>
<md-chip>Books</md-chip>
</div>
<md-divider></md-divider>
<div style="margin-block-start: 16px; display: grid; gap: 12px;">
<label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
<md-switch></md-switch>
In stock only
</label>
<label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
<md-switch></md-switch>
Free delivery
</label>
</div>
<md-button slot="actions" variant="text">Clear</md-button>
<md-button slot="actions" variant="filled">Apply</md-button>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-filters-btn').addEventListener('click', () => {
document.getElementById('bs-filters').show();
});
</script>// Icons need the Material Symbols stylesheet in index.html — see Installation.
import { useRef } from 'react';
import { MdBottomSheet, MdButton, MdChip, MdDivider, MdSwitch } from '@awc-ui/react';
export function Demo() {
const bsFiltersRef = useRef(null);
return (
<>
<MdButton id="bs-filters-btn" variant="filled" icon="tune" onClick={() => bsFiltersRef.current?.show()}>Filters</MdButton>
<MdBottomSheet id="bs-filters" ref={bsFiltersRef} headline="Filters" closeable>
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
<MdChip>Electronics</MdChip>
<MdChip>Clothing</MdChip>
<MdChip>Books</MdChip>
</div>
<MdDivider></MdDivider>
<div style={{ marginBlockStart: '16px', display: 'grid', gap: '12px' }}>
<label style={{ display: 'flex', alignItems: 'center', gap: '12px', cursor: 'pointer' }}>
<MdSwitch></MdSwitch>
In stock only
</label>
<label style={{ display: 'flex', alignItems: 'center', gap: '12px', cursor: 'pointer' }}>
<MdSwitch></MdSwitch>
Free delivery
</label>
</div>
<MdButton slot="actions" variant="text">Clear</MdButton>
<MdButton slot="actions" variant="filled">Apply</MdButton>
</MdBottomSheet>
</>
);
}// 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-button id="bs-filters-btn" variant="filled" icon="tune" (click)="bsFilters.show()">Filters</md-button>
<md-bottom-sheet id="bs-filters" #bsFilters headline="Filters" closeable>
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<md-chip>Electronics</md-chip>
<md-chip>Clothing</md-chip>
<md-chip>Books</md-chip>
</div>
<md-divider></md-divider>
<div style="margin-block-start: 16px; display: grid; gap: 12px;">
<label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
<md-switch></md-switch>
In stock only
</label>
<label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
<md-switch></md-switch>
Free delivery
</label>
</div>
<md-button slot="actions" variant="text">Clear</md-button>
<md-button slot="actions" variant="filled">Apply</md-button>
</md-bottom-sheet><script setup>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsFilters = ref(null);
</script>
<template>
<md-button id="bs-filters-btn" variant="filled" icon="tune" @click="bsFilters?.show()">Filters</md-button>
<md-bottom-sheet id="bs-filters" ref="bsFilters" headline="Filters" closeable>
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<md-chip>Electronics</md-chip>
<md-chip>Clothing</md-chip>
<md-chip>Books</md-chip>
</div>
<md-divider></md-divider>
<div style="margin-block-start: 16px; display: grid; gap: 12px;">
<label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
<md-switch></md-switch>
In stock only
</label>
<label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
<md-switch></md-switch>
Free delivery
</label>
</div>
<md-button slot="actions" variant="text">Clear</md-button>
<md-button slot="actions" variant="filled">Apply</md-button>
</md-bottom-sheet>
</template><script>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import '@awc-ui/core/define';
let bsFilters;
</script>
<md-button id="bs-filters-btn" variant="filled" icon="tune" on:click={() => bsFilters?.show()}>Filters</md-button>
<md-bottom-sheet id="bs-filters" bind:this={bsFilters} headline="Filters" closeable>
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
<md-chip>Electronics</md-chip>
<md-chip>Clothing</md-chip>
<md-chip>Books</md-chip>
</div>
<md-divider></md-divider>
<div style="margin-block-start: 16px; display: grid; gap: 12px;">
<label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
<md-switch></md-switch>
In stock only
</label>
<label style="display: flex; align-items: center; gap: 12px; cursor: pointer;">
<md-switch></md-switch>
Free delivery
</label>
</div>
<md-button slot="actions" variant="text">Clear</md-button>
<md-button slot="actions" variant="filled">Apply</md-button>
</md-bottom-sheet>And a share sheet — a plain list of destinations, the canonical M3 example:
<!-- 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-button id="bs-shareacts-btn" variant="filled" icon="share">Share</md-button>
<md-bottom-sheet id="bs-shareacts" headline="Share" closeable style="--md-bottom-sheet-content-padding-inline: 8px;">
<md-list>
<md-list-item headline="Send in email" leading-icon="email"></md-list-item>
<md-list-item headline="Copy link" leading-icon="link"></md-list-item>
<md-list-item headline="Copy to clipboard" leading-icon="content_copy"></md-list-item>
<md-list-item headline="Save to Drive" leading-icon="add_to_drive"></md-list-item>
<md-list-item headline="Print" leading-icon="print"></md-list-item>
</md-list>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-shareacts-btn').addEventListener('click', () => {
document.getElementById('bs-shareacts').show();
});
</script>// Icons need the Material Symbols stylesheet in index.html — see Installation.
import { useRef } from 'react';
import { MdBottomSheet, MdButton, MdList, MdListItem } from '@awc-ui/react';
export function Demo() {
const bsShareactsRef = useRef(null);
return (
<>
<MdButton id="bs-shareacts-btn" variant="filled" icon="share" onClick={() => bsShareactsRef.current?.show()}>Share</MdButton>
<MdBottomSheet id="bs-shareacts" ref={bsShareactsRef} headline="Share" closeable style={{ '--md-bottom-sheet-content-padding-inline': '8px' }}>
<MdList>
<MdListItem headline="Send in email" leadingIcon="email"></MdListItem>
<MdListItem headline="Copy link" leadingIcon="link"></MdListItem>
<MdListItem headline="Copy to clipboard" leadingIcon="content_copy"></MdListItem>
<MdListItem headline="Save to Drive" leadingIcon="add_to_drive"></MdListItem>
<MdListItem headline="Print" leadingIcon="print"></MdListItem>
</MdList>
</MdBottomSheet>
</>
);
}// 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-button id="bs-shareacts-btn" variant="filled" icon="share" (click)="bsShareacts.show()">Share</md-button>
<md-bottom-sheet id="bs-shareacts" #bsShareacts headline="Share" closeable style="--md-bottom-sheet-content-padding-inline: 8px;">
<md-list>
<md-list-item headline="Send in email" leading-icon="email"></md-list-item>
<md-list-item headline="Copy link" leading-icon="link"></md-list-item>
<md-list-item headline="Copy to clipboard" leading-icon="content_copy"></md-list-item>
<md-list-item headline="Save to Drive" leading-icon="add_to_drive"></md-list-item>
<md-list-item headline="Print" leading-icon="print"></md-list-item>
</md-list>
</md-bottom-sheet><script setup>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsShareacts = ref(null);
</script>
<template>
<md-button id="bs-shareacts-btn" variant="filled" icon="share" @click="bsShareacts?.show()">Share</md-button>
<md-bottom-sheet id="bs-shareacts" ref="bsShareacts" headline="Share" closeable style="--md-bottom-sheet-content-padding-inline: 8px;">
<md-list>
<md-list-item headline="Send in email" leading-icon="email"></md-list-item>
<md-list-item headline="Copy link" leading-icon="link"></md-list-item>
<md-list-item headline="Copy to clipboard" leading-icon="content_copy"></md-list-item>
<md-list-item headline="Save to Drive" leading-icon="add_to_drive"></md-list-item>
<md-list-item headline="Print" leading-icon="print"></md-list-item>
</md-list>
</md-bottom-sheet>
</template><script>
// Icons need the Material Symbols stylesheet in index.html — see Installation.
import '@awc-ui/core/define';
let bsShareacts;
</script>
<md-button id="bs-shareacts-btn" variant="filled" icon="share" on:click={() => bsShareacts?.show()}>Share</md-button>
<md-bottom-sheet id="bs-shareacts" bind:this={bsShareacts} headline="Share" closeable style="--md-bottom-sheet-content-padding-inline: 8px;">
<md-list>
<md-list-item headline="Send in email" leading-icon="email"></md-list-item>
<md-list-item headline="Copy link" leading-icon="link"></md-list-item>
<md-list-item headline="Copy to clipboard" leading-icon="content_copy"></md-list-item>
<md-list-item headline="Save to Drive" leading-icon="add_to_drive"></md-list-item>
<md-list-item headline="Print" leading-icon="print"></md-list-item>
</md-list>
</md-bottom-sheet>| Event | Cancelable | Detail | Fires |
|---|---|---|---|
mdOpen | no | void | The sheet has opened |
mdClose | no | void | Any close, including programmatic close() |
mdCancel | no | void | A dismissal: scrim click, Escape, drag-down, or the built-in close glyph |
| You want to… | Listen to |
|---|---|
| Restore focus, tear down state, run on every exit | mdClose |
| Treat the exit as “the user backed out” — discard a draft, log a dismissal | mdCancel |
| React to the sheet becoming visible — measure it, load its content | mdOpen |
Open the sheet below and leave it three different ways — the glyph, Escape,
and the Done button — and watch which events each route emits.
<md-button id="bs-ev-btn" variant="filled" icon="share">Share</md-button>
<md-bottom-sheet id="bs-ev" headline="Share to" closeable>
<md-list>
<md-list-item headline="Copy link" leading-icon="link"></md-list-item>
<md-list-item headline="Email" leading-icon="email"></md-list-item>
</md-list>
<md-button slot="actions" id="bs-ev-done" variant="filled">Done</md-button>
</md-bottom-sheet>
<script type="module">
const sheet = document.getElementById('bs-ev');
const btn = document.getElementById('bs-ev-btn');
const done = document.getElementById('bs-ev-done');
btn.addEventListener('click', () => sheet.show());
// A slotted / actions-row control is never auto-wired: call close() yourself.
done.addEventListener('click', () => sheet.close());
sheet.addEventListener('mdOpen', () => console.log('opened'));
sheet.addEventListener('mdCancel', () => console.log('dismissed')); // scrim / Esc / drag / glyph
sheet.addEventListener('mdClose', () => btn.focus({ preventScroll: true })); // any close
</script>import { MdBottomSheet, MdButton, MdList, MdListItem } from '@awc-ui/react';
import { useRef } from 'react';
export function ShareSheet() {
const sheet = useRef(null);
const btn = useRef(null);
return (
<>
<MdButton ref={btn} variant="filled" icon="share" onClick={() => sheet.current?.show()}>
Share
</MdButton>
<MdBottomSheet
ref={sheet}
headline="Share to"
closeable
onMdOpen={() => console.log('opened')}
onMdCancel={() => console.log('dismissed')}
onMdClose={() => btn.current?.focus({ preventScroll: true })}
>
<MdList>
<MdListItem headline="Copy link" leadingIcon="link" />
<MdListItem headline="Email" leadingIcon="email" />
</MdList>
<MdButton slot="actions" variant="filled" onClick={() => sheet.current?.close()}>
Done
</MdButton>
</MdBottomSheet>
</>
);
}import { Component, ElementRef, ViewChild } from '@angular/core';
type SheetEl = HTMLElement & { show: () => Promise<void>; close: () => Promise<void> };
@Component({
selector: 'app-share-sheet',
template: `
<md-button #btn variant="filled" icon="share" (click)="sheet.show()">Share</md-button>
<md-bottom-sheet
#sheet
headline="Share to"
closeable
(mdOpen)="onOpen()"
(mdCancel)="onCancel()"
(mdClose)="onClose()"
>
<md-list>
<md-list-item headline="Copy link" leading-icon="link"></md-list-item>
<md-list-item headline="Email" leading-icon="email"></md-list-item>
</md-list>
<md-button slot="actions" variant="filled" (click)="sheet.close()">Done</md-button>
</md-bottom-sheet>
`,
})
export class ShareSheetComponent {
@ViewChild('btn', { static: true }) btn!: ElementRef<HTMLElement>;
onOpen() { console.log('opened'); }
onCancel() { console.log('dismissed'); }
onClose() { this.btn.nativeElement.focus({ preventScroll: true }); }
}<script setup lang="ts">
import { ref } from 'vue';
type SheetEl = HTMLElement & { show: () => Promise<void>; close: () => Promise<void> };
const sheet = ref<SheetEl | null>(null);
const btn = ref<HTMLElement | null>(null);
</script>
<template>
<md-button ref="btn" variant="filled" icon="share" @click="sheet?.show()">Share</md-button>
<md-bottom-sheet
ref="sheet"
headline="Share to"
closeable
@mdOpen="() => console.log('opened')"
@mdCancel="() => console.log('dismissed')"
@mdClose="() => btn?.focus({ preventScroll: true })"
>
<md-list>
<md-list-item headline="Copy link" leading-icon="link"></md-list-item>
<md-list-item headline="Email" leading-icon="email"></md-list-item>
</md-list>
<md-button slot="actions" variant="filled" @click="sheet?.close()">Done</md-button>
</md-bottom-sheet>
</template><script lang="ts">
type SheetEl = HTMLElement & { show: () => Promise<void>; close: () => Promise<void> };
let sheet: SheetEl;
let btn: HTMLElement;
</script>
<md-button bind:this={btn} variant="filled" icon="share" on:click={() => sheet.show()}>
Share
</md-button>
<md-bottom-sheet
bind:this={sheet}
headline="Share to"
closeable
on:mdOpen={() => console.log('opened')}
on:mdCancel={() => console.log('dismissed')}
on:mdClose={() => btn.focus({ preventScroll: true })}
>
<md-list>
<md-list-item headline="Copy link" leading-icon="link"></md-list-item>
<md-list-item headline="Email" leading-icon="email"></md-list-item>
</md-list>
<md-button slot="actions" variant="filled" on:click={() => sheet.close()}>Done</md-button>
</md-bottom-sheet>| Property | Attribute | Type | Default | Reflects |
|---|---|---|---|---|
open | open | boolean | false | Yes |
variant | variant | 'standard' | 'detached' | 'standard' | Yes |
headline | headline | string | '' | — |
showDragHandle | show-drag-handle | boolean | true | Yes |
closeable | closeable | boolean | false | Yes |
scrimDismissible | scrim-dismissible | boolean | true | — |
topDivider | top-divider | boolean | false | Yes |
bottomDivider | bottom-divider | boolean | false | Yes |
sheetAriaLabel | aria-label | string | '' | — |
scrollShadow | scroll-shadow | boolean | true | Yes |
headlineAlign | headline-align | 'start' | 'center' | 'end' | 'start' | Yes |
contentAlign | content-align | 'start' | 'center' | 'end' | 'start' | Yes |
density | density | 0 | -1 | -2 | -3 | -4 | 0 | Yes |
| Method | Parameters |
|---|---|
show() | none |
close() | none |
| Slot | Description |
|---|---|
(default) | — |
headline | Custom headline content |
close | Custom close element |
actions | Bottom action buttons |
Override on the host element for per-instance theming:
| Property | Description |
|---|---|
--md-bottom-sheet-container-color | Container background |
--md-bottom-sheet-container-shape | Corner radius (top corners |
--md-bottom-sheet-content-color | Content text color |
--md-bottom-sheet-headline-color | Headline text color |
--md-bottom-sheet-scrim-color | Scrim overlay color |
--md-bottom-sheet-drag-handle-color | Drag handle indicator color |
--md-bottom-sheet-divider-color | Top / bottom divider color |
--md-bottom-sheet-icon-color | Close icon color |
--md-bottom-sheet-width | Inline size (default 100% |
--md-bottom-sheet-min-width | Minimum inline size |
--md-bottom-sheet-max-width | Maximum inline size |
--md-bottom-sheet-height | Block size (default `auto`) |
--md-bottom-sheet-min-height | Minimum block size |
--md-bottom-sheet-max-height | Maximum block size |
--md-bottom-sheet-detached-margin | Margin around the container |
--md-bottom-sheet-content-padding-inline | — |
Style internal elements through shadow DOM with ::part():
| Part | Description |
|---|---|
scrim | Scrim overlay |
container | Sheet surface |
drag-handle | Drag handle hit area |
drag-handle-indicator | Drag handle visible bar |
header | Header row |
headline | Headline text |
close | Close button wrapper |
divider-top | Divider between header and content |
content | Content area wrapper (inline padding) |
divider-bottom | Divider between content and actions |
actions | Bottom action bar |
role="dialog", aria-modal="true",
and while closed the whole panel is inert and aria-hidden so nothing
inside it is tab-reachable.show() moves focus to the first focusable control inside the sheet; a pair
of focus guards wraps Tab and Shift+Tab back around, and closing restores
focus to whatever was focused before — with preventScroll, so the page
doesn’t jump.Escape dismisses from anywhere on the page, firing mdCancel then
mdClose.headline (wired through aria-labelledby) or with
aria-label when there is no visible headline. A headline-less, unlabelled
sheet falls back to the literal name “Bottom sheet” — never ship that.aria-hidden and a pointer affordance only. Always
give keyboard and AT users a way out — closeable, or a cancel button in the
actions slot.Open the sheet below, then Tab past the last button: focus wraps back to the
first control instead of escaping to the page. Press Escape and focus lands
back on the trigger.
This sheet has no visible headline, so aria-label names it instead. Tab around — focus is trapped inside and returns to the opener on close.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-button id="bs-a11y-btn" variant="filled">Open a labelled sheet</md-button>
<md-bottom-sheet id="bs-a11y" aria-label="Payment options" closeable>
<p>No visible headline, so aria-label names the dialog.</p>
<md-button slot="actions" variant="text">Close</md-button>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-a11y-btn').addEventListener('click', () => {
document.getElementById('bs-a11y').show();
});
</script>import { useRef } from 'react';
import { MdBottomSheet, MdButton } from '@awc-ui/react';
export function Demo() {
const bsA11yRef = useRef(null);
return (
<>
<MdButton id="bs-a11y-btn" variant="filled" onClick={() => bsA11yRef.current?.show()}>Open a labelled sheet</MdButton>
<MdBottomSheet id="bs-a11y" ref={bsA11yRef} aria-label="Payment options" closeable>
<p>No visible headline, so aria-label names the dialog.</p>
<MdButton slot="actions" variant="text">Close</MdButton>
</MdBottomSheet>
</>
);
}// 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-button id="bs-a11y-btn" variant="filled" (click)="bsA11y.show()">Open a labelled sheet</md-button>
<md-bottom-sheet id="bs-a11y" #bsA11y aria-label="Payment options" closeable>
<p>No visible headline, so aria-label names the dialog.</p>
<md-button slot="actions" variant="text">Close</md-button>
</md-bottom-sheet><script setup>
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsA11y = ref(null);
</script>
<template>
<md-button id="bs-a11y-btn" variant="filled" @click="bsA11y?.show()">Open a labelled sheet</md-button>
<md-bottom-sheet id="bs-a11y" ref="bsA11y" aria-label="Payment options" closeable>
<p>No visible headline, so aria-label names the dialog.</p>
<md-button slot="actions" variant="text">Close</md-button>
</md-bottom-sheet>
</template><script>
import '@awc-ui/core/define';
let bsA11y;
</script>
<md-button id="bs-a11y-btn" variant="filled" on:click={() => bsA11y?.show()}>Open a labelled sheet</md-button>
<md-bottom-sheet id="bs-a11y" bind:this={bsA11y} aria-label="Payment options" closeable>
<p>No visible headline, so aria-label names the dialog.</p>
<md-button slot="actions" variant="text">Close</md-button>
</md-bottom-sheet>In Storybook: accessibility, focus restoration, the focus-guard wrap, a custom aria-label and opening already-open.
RTL — nothing on the sheet is hard-coded to a physical side. The header
row, the close affordance, the actions row and headline-align /
content-align are all logical, so the same markup mirrors under dir="rtl":
<div dir="rtl"> <md-bottom-sheet headline="مشاركة إلى" headline-align="start" closeable> <p>المحاذاة منطقية، لذا يتبع العنوان اتجاه القراءة.</p> </md-bottom-sheet></div>The close glyph sits at the inline end, and the headline starts at the inline start.
المحاذاة منطقية، لذا يتبع العنوان اتجاه القراءة.
<!-- 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" style="display:flex;flex-wrap:wrap;gap:8px;">
<md-button id="bs-ltr-btn" variant="filled">Share to</md-button>
<md-bottom-sheet id="bs-ltr" headline="Share to" top-divider closeable>
<p style="margin: 0;">The close glyph sits at the inline end, and the headline starts at the inline start.</p>
</md-bottom-sheet>
</div>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">rtl</span>
<div dir="rtl" style="display:flex;flex-wrap:wrap;gap:8px;">
<md-button id="bs-rtl-btn" variant="filled">مشاركة إلى</md-button>
<md-bottom-sheet id="bs-rtl" headline="مشاركة إلى" top-divider closeable>
<p style="margin: 0;">المحاذاة منطقية، لذا يتبع العنوان اتجاه القراءة.</p>
</md-bottom-sheet>
</div>
</div>
<script type="module">
document.getElementById('bs-ltr-btn').addEventListener('click', () => {
document.getElementById('bs-ltr').show();
});
document.getElementById('bs-rtl-btn').addEventListener('click', () => {
document.getElementById('bs-rtl').show();
});
</script>import { useRef } from 'react';
import { MdBottomSheet, MdButton } from '@awc-ui/react';
export function Demo() {
const bsLtrRef = useRef(null);
const bsRtlRef = useRef(null);
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" style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
<MdButton id="bs-ltr-btn" variant="filled" onClick={() => bsLtrRef.current?.show()}>Share to</MdButton>
<MdBottomSheet id="bs-ltr" ref={bsLtrRef} headline="Share to" topDivider closeable>
<p style={{ margin: '0' }}>The close glyph sits at the inline end, and the headline starts at the inline start.</p>
</MdBottomSheet>
</div>
<span style={{ inlineSize: '3.5rem', opacity: '.65', fontSize: '.75rem', fontFamily: 'ui-monospace,monospace' }}>rtl</span>
<div dir="rtl" style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
<MdButton id="bs-rtl-btn" variant="filled" onClick={() => bsRtlRef.current?.show()}>مشاركة إلى</MdButton>
<MdBottomSheet id="bs-rtl" ref={bsRtlRef} headline="مشاركة إلى" topDivider closeable>
<p style={{ margin: '0' }}>المحاذاة منطقية، لذا يتبع العنوان اتجاه القراءة.</p>
</MdBottomSheet>
</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" style="display:flex;flex-wrap:wrap;gap:8px;">
<md-button id="bs-ltr-btn" variant="filled" (click)="bsLtr.show()">Share to</md-button>
<md-bottom-sheet id="bs-ltr" #bsLtr headline="Share to" top-divider closeable>
<p style="margin: 0;">The close glyph sits at the inline end, and the headline starts at the inline start.</p>
</md-bottom-sheet>
</div>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">rtl</span>
<div dir="rtl" style="display:flex;flex-wrap:wrap;gap:8px;">
<md-button id="bs-rtl-btn" variant="filled" (click)="bsRtl.show()">مشاركة إلى</md-button>
<md-bottom-sheet id="bs-rtl" #bsRtl headline="مشاركة إلى" top-divider closeable>
<p style="margin: 0;">المحاذاة منطقية، لذا يتبع العنوان اتجاه القراءة.</p>
</md-bottom-sheet>
</div>
</div><script setup>
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsLtr = ref(null);
const bsRtl = ref(null);
</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" style="display:flex;flex-wrap:wrap;gap:8px;">
<md-button id="bs-ltr-btn" variant="filled" @click="bsLtr?.show()">Share to</md-button>
<md-bottom-sheet id="bs-ltr" ref="bsLtr" headline="Share to" top-divider closeable>
<p style="margin: 0;">The close glyph sits at the inline end, and the headline starts at the inline start.</p>
</md-bottom-sheet>
</div>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">rtl</span>
<div dir="rtl" style="display:flex;flex-wrap:wrap;gap:8px;">
<md-button id="bs-rtl-btn" variant="filled" @click="bsRtl?.show()">مشاركة إلى</md-button>
<md-bottom-sheet id="bs-rtl" ref="bsRtl" headline="مشاركة إلى" top-divider closeable>
<p style="margin: 0;">المحاذاة منطقية، لذا يتبع العنوان اتجاه القراءة.</p>
</md-bottom-sheet>
</div>
</div>
</template><script>
import '@awc-ui/core/define';
let bsLtr;
let bsRtl;
</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" style="display:flex;flex-wrap:wrap;gap:8px;">
<md-button id="bs-ltr-btn" variant="filled" on:click={() => bsLtr?.show()}>Share to</md-button>
<md-bottom-sheet id="bs-ltr" bind:this={bsLtr} headline="Share to" top-divider closeable>
<p style="margin: 0;">The close glyph sits at the inline end, and the headline starts at the inline start.</p>
</md-bottom-sheet>
</div>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">rtl</span>
<div dir="rtl" style="display:flex;flex-wrap:wrap;gap:8px;">
<md-button id="bs-rtl-btn" variant="filled" on:click={() => bsRtl?.show()}>مشاركة إلى</md-button>
<md-bottom-sheet id="bs-rtl" bind:this={bsRtl} headline="مشاركة إلى" top-divider closeable>
<p style="margin: 0;">المحاذاة منطقية، لذا يتبع العنوان اتجاه القراءة.</p>
</md-bottom-sheet>
</div>
</div>headline-align is logical, not physicalheadline-align="start" follows the reading direction. Hard-coding the same
intent with ::part(headline) { text-align: left } does not — it pins the
headline to the physical left even in Arabic. Both rows below are under
dir="rtl":
Logical: the headline follows the reading direction and lands on the right.
Physical: pinned to the left, fighting the reading direction.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<style>
#bs-align-wrong::part(headline) { text-align: left; }
</style>
<div dir="rtl" 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;">right</span>
<div style="display:flex;flex-wrap:wrap;gap:8px;">
<md-button id="bs-align-right-btn" variant="filled">headline-align="start"</md-button>
<md-bottom-sheet id="bs-align-right" headline="مشاركة إلى" headline-align="start" closeable>
<p style="margin: 0;">Logical: the headline follows the reading direction and lands on the right.</p>
</md-bottom-sheet>
</div>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">wrong</span>
<div style="display:flex;flex-wrap:wrap;gap:8px;">
<md-button id="bs-align-wrong-btn" variant="outlined">text-align: left</md-button>
<md-bottom-sheet id="bs-align-wrong" headline="مشاركة إلى" closeable>
<p style="margin: 0;">Physical: pinned to the left, fighting the reading direction.</p>
</md-bottom-sheet>
</div>
</div>
<script type="module">
document.getElementById('bs-align-right-btn').addEventListener('click', () => {
document.getElementById('bs-align-right').show();
});
document.getElementById('bs-align-wrong-btn').addEventListener('click', () => {
document.getElementById('bs-align-wrong').show();
});
</script>import { useRef } from 'react';
import { MdBottomSheet, MdButton } from '@awc-ui/react';
export function Demo() {
const bsAlignRightRef = useRef(null);
const bsAlignWrongRef = useRef(null);
return (
<>
<style>
#bs-align-wrong::part(headline) { text-align: left; }
</style>
<div dir="rtl" style={{ display: 'grid', gridTemplateColumns: 'auto 1fr', gap: '14px 16px', alignItems: 'center' }}>
<span style={{ inlineSize: '3.5rem', opacity: '.65', fontSize: '.75rem', fontFamily: 'ui-monospace,monospace' }}>right</span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
<MdButton id="bs-align-right-btn" variant="filled" onClick={() => bsAlignRightRef.current?.show()}>headline-align="start"</MdButton>
<MdBottomSheet id="bs-align-right" ref={bsAlignRightRef} headline="مشاركة إلى" headlineAlign="start" closeable>
<p style={{ margin: '0' }}>Logical: the headline follows the reading direction and lands on the right.</p>
</MdBottomSheet>
</div>
<span style={{ inlineSize: '3.5rem', opacity: '.65', fontSize: '.75rem', fontFamily: 'ui-monospace,monospace' }}>wrong</span>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
<MdButton id="bs-align-wrong-btn" variant="outlined" onClick={() => bsAlignWrongRef.current?.show()}>text-align: left</MdButton>
<MdBottomSheet id="bs-align-wrong" ref={bsAlignWrongRef} headline="مشاركة إلى" closeable>
<p style={{ margin: '0' }}>Physical: pinned to the left, fighting the reading direction.</p>
</MdBottomSheet>
</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 -->
<style>
#bs-align-wrong::part(headline) { text-align: left; }
</style>
<div dir="rtl" 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;">right</span>
<div style="display:flex;flex-wrap:wrap;gap:8px;">
<md-button id="bs-align-right-btn" variant="filled" (click)="bsAlignRight.show()">headline-align="start"</md-button>
<md-bottom-sheet id="bs-align-right" #bsAlignRight headline="مشاركة إلى" headline-align="start" closeable>
<p style="margin: 0;">Logical: the headline follows the reading direction and lands on the right.</p>
</md-bottom-sheet>
</div>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">wrong</span>
<div style="display:flex;flex-wrap:wrap;gap:8px;">
<md-button id="bs-align-wrong-btn" variant="outlined" (click)="bsAlignWrong.show()">text-align: left</md-button>
<md-bottom-sheet id="bs-align-wrong" #bsAlignWrong headline="مشاركة إلى" closeable>
<p style="margin: 0;">Physical: pinned to the left, fighting the reading direction.</p>
</md-bottom-sheet>
</div>
</div><script setup>
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsAlignRight = ref(null);
const bsAlignWrong = ref(null);
</script>
<template>
<style>
#bs-align-wrong::part(headline) { text-align: left; }
</style>
<div dir="rtl" 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;">right</span>
<div style="display:flex;flex-wrap:wrap;gap:8px;">
<md-button id="bs-align-right-btn" variant="filled" @click="bsAlignRight?.show()">headline-align="start"</md-button>
<md-bottom-sheet id="bs-align-right" ref="bsAlignRight" headline="مشاركة إلى" headline-align="start" closeable>
<p style="margin: 0;">Logical: the headline follows the reading direction and lands on the right.</p>
</md-bottom-sheet>
</div>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">wrong</span>
<div style="display:flex;flex-wrap:wrap;gap:8px;">
<md-button id="bs-align-wrong-btn" variant="outlined" @click="bsAlignWrong?.show()">text-align: left</md-button>
<md-bottom-sheet id="bs-align-wrong" ref="bsAlignWrong" headline="مشاركة إلى" closeable>
<p style="margin: 0;">Physical: pinned to the left, fighting the reading direction.</p>
</md-bottom-sheet>
</div>
</div>
</template><script>
import '@awc-ui/core/define';
let bsAlignRight;
let bsAlignWrong;
</script>
<style>
#bs-align-wrong::part(headline) { text-align: left; }
</style>
<div dir="rtl" 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;">right</span>
<div style="display:flex;flex-wrap:wrap;gap:8px;">
<md-button id="bs-align-right-btn" variant="filled" on:click={() => bsAlignRight?.show()}>headline-align="start"</md-button>
<md-bottom-sheet id="bs-align-right" bind:this={bsAlignRight} headline="مشاركة إلى" headline-align="start" closeable>
<p style="margin: 0;">Logical: the headline follows the reading direction and lands on the right.</p>
</md-bottom-sheet>
</div>
<span style="inline-size:3.5rem;opacity:.65;font-size:.75rem;font-family:ui-monospace,monospace;">wrong</span>
<div style="display:flex;flex-wrap:wrap;gap:8px;">
<md-button id="bs-align-wrong-btn" variant="outlined" on:click={() => bsAlignWrong?.show()}>text-align: left</md-button>
<md-bottom-sheet id="bs-align-wrong" bind:this={bsAlignWrong} headline="مشاركة إلى" closeable>
<p style="margin: 0;">Physical: pinned to the left, fighting the reading direction.</p>
</md-bottom-sheet>
</div>
</div>density="-1…-4" compacts the padding, the header and the actions row together.
Rung 0 is the uncompacted default, not a value you set — there is no
[density="0"] rule to opt back into (see Density and direction
together). On the sheets below the header goes 48 → 43 → 38 → 37 → 36px (a 40 → 36 → 32px close button over a shrinking header gap) and the content gutter 24 →
22 → 20 → 18 → 16px. The header flattens out after -2; the gutter keeps
tightening to the floor, and the actions row steps separately — it reads the
shared --md-sys-spacing-* tokens, which only move at -2 and -3. See
Density.
Nothing stops two sheets being open at once — there is no singleton registry, and each open sheet installs its own document-level focus trap, so a second one fights the first. Compare the rungs by opening each in turn.
Same content at every rung — compare the header, the content gutter and the actions row.
Same content at every rung — compare the header, the content gutter and the actions row.
Same content at every rung — compare the header, the content gutter and the actions row.
Same content at every rung — compare the header, the content gutter and the actions row.
Same content at every rung — compare the header, the content gutter and the actions row.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-button id="bs-d2-btn" variant="outlined">Open</md-button>
<md-bottom-sheet id="bs-d2" headline="Density -2" density="-2" closeable>
<p>Same content at every rung — compare the header, the content gutter and the actions row.</p>
<md-button slot="actions" variant="text">Close</md-button>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-d2-btn').addEventListener('click', () => {
document.getElementById('bs-d2').show();
});
</script>import { useRef } from 'react';
import { MdBottomSheet, MdButton } from '@awc-ui/react';
export function Demo() {
const bsD2Ref = useRef(null);
return (
<>
<MdButton id="bs-d2-btn" variant="outlined" onClick={() => bsD2Ref.current?.show()}>Open</MdButton>
<MdBottomSheet id="bs-d2" ref={bsD2Ref} headline="Density -2" density="-2" closeable>
<p>Same content at every rung — compare the header, the content gutter and the actions row.</p>
<MdButton slot="actions" variant="text">Close</MdButton>
</MdBottomSheet>
</>
);
}// 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-button id="bs-d2-btn" variant="outlined" (click)="bsD2.show()">Open</md-button>
<md-bottom-sheet id="bs-d2" #bsD2 headline="Density -2" density="-2" closeable>
<p>Same content at every rung — compare the header, the content gutter and the actions row.</p>
<md-button slot="actions" variant="text">Close</md-button>
</md-bottom-sheet><script setup>
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsD2 = ref(null);
</script>
<template>
<md-button id="bs-d2-btn" variant="outlined" @click="bsD2?.show()">Open</md-button>
<md-bottom-sheet id="bs-d2" ref="bsD2" headline="Density -2" density="-2" closeable>
<p>Same content at every rung — compare the header, the content gutter and the actions row.</p>
<md-button slot="actions" variant="text">Close</md-button>
</md-bottom-sheet>
</template><script>
import '@awc-ui/core/define';
let bsD2;
</script>
<md-button id="bs-d2-btn" variant="outlined" on:click={() => bsD2?.show()}>Open</md-button>
<md-bottom-sheet id="bs-d2" bind:this={bsD2} headline="Density -2" density="-2" closeable>
<p>Same content at every rung — compare the header, the content gutter and the actions row.</p>
<md-button slot="actions" variant="text">Close</md-button>
</md-bottom-sheet>A global data-density ancestor and dir="rtl" compose — and a local
density="-1…-4" on one sheet overrides the inherited rung without touching the
direction. It only ever tightens, though: to loosen a sheet back out of an
inherited rung you set --md-sys-density-scale yourself.
This sheet inherits data-density=-2 from its ancestor, and mirrors with it.
A local density of -1 through -4 does win over the inherited rung — this sheet tightens past its ancestor's -2, still RTL.
density="0" would change nothing here. Setting --md-sys-density-scale: 0 inline is the real reset — roomy again, still RTL.
<!-- 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-wrap:wrap;gap:8px;">
<md-button id="bs-dd-inherit-btn" variant="filled">يرث -2</md-button>
<md-button id="bs-dd-tighter-btn" variant="outlined">أكثف -4</md-button>
<md-button id="bs-dd-reset-btn" variant="outlined">إعادة الضبط إلى 0</md-button>
<md-bottom-sheet id="bs-dd-inherit" headline="كثافة موروثة" closeable>
<p style="margin: 0;">This sheet inherits data-density=-2 from its ancestor, and mirrors with it.</p>
</md-bottom-sheet>
<md-bottom-sheet id="bs-dd-tighter" headline="كثافة محلية -4" density="-4" closeable>
<p style="margin: 0;">A local density of -1 through -4 does win over the inherited rung — this sheet tightens past its ancestor's -2, still RTL.</p>
</md-bottom-sheet>
<md-bottom-sheet id="bs-dd-reset" headline="إعادة الضبط" style="--md-sys-density-scale: 0" closeable>
<p style="margin: 0;">density="0" would change nothing here. Setting --md-sys-density-scale: 0 inline is the real reset — roomy again, still RTL.</p>
</md-bottom-sheet>
</div>
<script type="module">
document.getElementById('bs-dd-inherit-btn').addEventListener('click', () => {
document.getElementById('bs-dd-inherit').show();
});
document.getElementById('bs-dd-tighter-btn').addEventListener('click', () => {
document.getElementById('bs-dd-tighter').show();
});
document.getElementById('bs-dd-reset-btn').addEventListener('click', () => {
document.getElementById('bs-dd-reset').show();
});
</script>import { useRef } from 'react';
import { MdBottomSheet, MdButton } from '@awc-ui/react';
export function Demo() {
const bsDdInheritRef = useRef(null);
const bsDdTighterRef = useRef(null);
const bsDdResetRef = useRef(null);
return (
<>
<div dir="rtl" data-density="-2" style={{ display: 'flex', flexWrap: 'wrap', gap: '8px' }}>
<MdButton id="bs-dd-inherit-btn" variant="filled" onClick={() => bsDdInheritRef.current?.show()}>يرث -2</MdButton>
<MdButton id="bs-dd-tighter-btn" variant="outlined" onClick={() => bsDdTighterRef.current?.show()}>أكثف -4</MdButton>
<MdButton id="bs-dd-reset-btn" variant="outlined" onClick={() => bsDdResetRef.current?.show()}>إعادة الضبط إلى 0</MdButton>
<MdBottomSheet id="bs-dd-inherit" ref={bsDdInheritRef} headline="كثافة موروثة" closeable>
<p style={{ margin: '0' }}>This sheet inherits data-density=-2 from its ancestor, and mirrors with it.</p>
</MdBottomSheet>
<MdBottomSheet id="bs-dd-tighter" ref={bsDdTighterRef} headline="كثافة محلية -4" density="-4" closeable>
<p style={{ margin: '0' }}>A local density of -1 through -4 does win over the inherited rung — this sheet tightens past its ancestor's -2, still RTL.</p>
</MdBottomSheet>
<MdBottomSheet id="bs-dd-reset" ref={bsDdResetRef} headline="إعادة الضبط" style={{ '--md-sys-density-scale': '0' }} closeable>
<p style={{ margin: '0' }}>density="0" would change nothing here. Setting --md-sys-density-scale: 0 inline is the real reset — roomy again, still RTL.</p>
</MdBottomSheet>
</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-wrap:wrap;gap:8px;">
<md-button id="bs-dd-inherit-btn" variant="filled" (click)="bsDdInherit.show()">يرث -2</md-button>
<md-button id="bs-dd-tighter-btn" variant="outlined" (click)="bsDdTighter.show()">أكثف -4</md-button>
<md-button id="bs-dd-reset-btn" variant="outlined" (click)="bsDdReset.show()">إعادة الضبط إلى 0</md-button>
<md-bottom-sheet id="bs-dd-inherit" #bsDdInherit headline="كثافة موروثة" closeable>
<p style="margin: 0;">This sheet inherits data-density=-2 from its ancestor, and mirrors with it.</p>
</md-bottom-sheet>
<md-bottom-sheet id="bs-dd-tighter" #bsDdTighter headline="كثافة محلية -4" density="-4" closeable>
<p style="margin: 0;">A local density of -1 through -4 does win over the inherited rung — this sheet tightens past its ancestor's -2, still RTL.</p>
</md-bottom-sheet>
<md-bottom-sheet id="bs-dd-reset" #bsDdReset headline="إعادة الضبط" style="--md-sys-density-scale: 0" closeable>
<p style="margin: 0;">density="0" would change nothing here. Setting --md-sys-density-scale: 0 inline is the real reset — roomy again, still RTL.</p>
</md-bottom-sheet>
</div><script setup>
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsDdInherit = ref(null);
const bsDdTighter = ref(null);
const bsDdReset = ref(null);
</script>
<template>
<div dir="rtl" data-density="-2" style="display:flex;flex-wrap:wrap;gap:8px;">
<md-button id="bs-dd-inherit-btn" variant="filled" @click="bsDdInherit?.show()">يرث -2</md-button>
<md-button id="bs-dd-tighter-btn" variant="outlined" @click="bsDdTighter?.show()">أكثف -4</md-button>
<md-button id="bs-dd-reset-btn" variant="outlined" @click="bsDdReset?.show()">إعادة الضبط إلى 0</md-button>
<md-bottom-sheet id="bs-dd-inherit" ref="bsDdInherit" headline="كثافة موروثة" closeable>
<p style="margin: 0;">This sheet inherits data-density=-2 from its ancestor, and mirrors with it.</p>
</md-bottom-sheet>
<md-bottom-sheet id="bs-dd-tighter" ref="bsDdTighter" headline="كثافة محلية -4" density="-4" closeable>
<p style="margin: 0;">A local density of -1 through -4 does win over the inherited rung — this sheet tightens past its ancestor's -2, still RTL.</p>
</md-bottom-sheet>
<md-bottom-sheet id="bs-dd-reset" ref="bsDdReset" headline="إعادة الضبط" style="--md-sys-density-scale: 0" closeable>
<p style="margin: 0;">density="0" would change nothing here. Setting --md-sys-density-scale: 0 inline is the real reset — roomy again, still RTL.</p>
</md-bottom-sheet>
</div>
</template><script>
import '@awc-ui/core/define';
let bsDdInherit;
let bsDdTighter;
let bsDdReset;
</script>
<div dir="rtl" data-density="-2" style="display:flex;flex-wrap:wrap;gap:8px;">
<md-button id="bs-dd-inherit-btn" variant="filled" on:click={() => bsDdInherit?.show()}>يرث -2</md-button>
<md-button id="bs-dd-tighter-btn" variant="outlined" on:click={() => bsDdTighter?.show()}>أكثف -4</md-button>
<md-button id="bs-dd-reset-btn" variant="outlined" on:click={() => bsDdReset?.show()}>إعادة الضبط إلى 0</md-button>
<md-bottom-sheet id="bs-dd-inherit" bind:this={bsDdInherit} headline="كثافة موروثة" closeable>
<p style="margin: 0;">This sheet inherits data-density=-2 from its ancestor, and mirrors with it.</p>
</md-bottom-sheet>
<md-bottom-sheet id="bs-dd-tighter" bind:this={bsDdTighter} headline="كثافة محلية -4" density="-4" closeable>
<p style="margin: 0;">A local density of -1 through -4 does win over the inherited rung — this sheet tightens past its ancestor's -2, still RTL.</p>
</md-bottom-sheet>
<md-bottom-sheet id="bs-dd-reset" bind:this={bsDdReset} headline="إعادة الضبط" style="--md-sys-density-scale: 0" closeable>
<p style="margin: 0;">density="0" would change nothing here. Setting --md-sys-density-scale: 0 inline is the real reset — roomy again, still RTL.</p>
</md-bottom-sheet>
</div>Density — the rung drives --md-sys-density-scale, which the container
shape, the header height, the content padding and the typescale all read from,
so a single attribute retunes the whole surface.
i18n — translate headline, aria-label, the action labels and the body;
the localization story
switches all four at once. Longer translations push the sheet taller — re-check
that it still leaves context visible under the 80vh cap.
| Custom property | Purpose | Default |
|---|---|---|
--md-bottom-sheet-container-color | Sheet surface | surface-container-low |
--md-bottom-sheet-container-shape | Corner radius | max(16px, 28px + density × 2px) |
--md-bottom-sheet-headline-color | Headline text | on-surface |
--md-bottom-sheet-content-color | Body text | on-surface |
--md-bottom-sheet-scrim-color | Backdrop | rgba(0, 0, 0, 0.32) |
--md-bottom-sheet-drag-handle-color | Handle indicator | on-surface-variant |
--md-bottom-sheet-divider-color | Top / bottom rules | outline-variant |
--md-bottom-sheet-icon-color | Close glyph | on-surface-variant |
--md-bottom-sheet-width / -min-width / -max-width | Inline-size bounds — the width fallbacks change at the 640px breakpoint | Below 640px: 100% / 0 / 100%. At 640px and up: 640px / 0 / calc(100% - 112px) |
--md-bottom-sheet-height / -min-height / -max-height | Block-size bounds | auto / 0 / 80vh |
--md-bottom-sheet-detached-margin | Inset for variant="detached" | 16px |
--md-bottom-sheet-content-padding-inline | Body inline padding | max(12px, 24px + density × 2px) |
Recoloured surface, squared corners and a capped width.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<md-button id="bs-themed-btn" variant="filled">Themed sheet</md-button>
<md-bottom-sheet
id="bs-themed"
variant="detached"
headline="Themed"
closeable
style="--md-bottom-sheet-container-color: var(--md-sys-color-primary-container); --md-bottom-sheet-headline-color: var(--md-sys-color-on-primary-container); --md-bottom-sheet-content-color: var(--md-sys-color-on-primary-container); --md-bottom-sheet-container-shape: 8px; --md-bottom-sheet-max-width: 420px;">
<p style="margin: 0;">Recoloured surface, squared corners and a capped width.</p>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-themed-btn').addEventListener('click', () => {
document.getElementById('bs-themed').show();
});
</script>import { useRef } from 'react';
import { MdBottomSheet, MdButton } from '@awc-ui/react';
export function Demo() {
const bsThemedRef = useRef(null);
return (
<>
<MdButton id="bs-themed-btn" variant="filled" onClick={() => bsThemedRef.current?.show()}>Themed sheet</MdButton>
<MdBottomSheet
id="bs-themed" ref={bsThemedRef}
variant="detached"
headline="Themed"
closeable
style={{ '--md-bottom-sheet-container-color': 'var(--md-sys-color-primary-container)', '--md-bottom-sheet-headline-color': 'var(--md-sys-color-on-primary-container)', '--md-bottom-sheet-content-color': 'var(--md-sys-color-on-primary-container)', '--md-bottom-sheet-container-shape': '8px', '--md-bottom-sheet-max-width': '420px' }}>
<p style={{ margin: '0' }}>Recoloured surface, squared corners and a capped width.</p>
</MdBottomSheet>
</>
);
}// 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-button id="bs-themed-btn" variant="filled" (click)="bsThemed.show()">Themed sheet</md-button>
<md-bottom-sheet
id="bs-themed" #bsThemed
variant="detached"
headline="Themed"
closeable
style="--md-bottom-sheet-container-color: var(--md-sys-color-primary-container); --md-bottom-sheet-headline-color: var(--md-sys-color-on-primary-container); --md-bottom-sheet-content-color: var(--md-sys-color-on-primary-container); --md-bottom-sheet-container-shape: 8px; --md-bottom-sheet-max-width: 420px;">
<p style="margin: 0;">Recoloured surface, squared corners and a capped width.</p>
</md-bottom-sheet><script setup>
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsThemed = ref(null);
</script>
<template>
<md-button id="bs-themed-btn" variant="filled" @click="bsThemed?.show()">Themed sheet</md-button>
<md-bottom-sheet
id="bs-themed" ref="bsThemed"
variant="detached"
headline="Themed"
closeable
style="--md-bottom-sheet-container-color: var(--md-sys-color-primary-container); --md-bottom-sheet-headline-color: var(--md-sys-color-on-primary-container); --md-bottom-sheet-content-color: var(--md-sys-color-on-primary-container); --md-bottom-sheet-container-shape: 8px; --md-bottom-sheet-max-width: 420px;">
<p style="margin: 0;">Recoloured surface, squared corners and a capped width.</p>
</md-bottom-sheet>
</template><script>
import '@awc-ui/core/define';
let bsThemed;
</script>
<md-button id="bs-themed-btn" variant="filled" on:click={() => bsThemed?.show()}>Themed sheet</md-button>
<md-bottom-sheet
id="bs-themed" bind:this={bsThemed}
variant="detached"
headline="Themed"
closeable
style="--md-bottom-sheet-container-color: var(--md-sys-color-primary-container); --md-bottom-sheet-headline-color: var(--md-sys-color-on-primary-container); --md-bottom-sheet-content-color: var(--md-sys-color-on-primary-container); --md-bottom-sheet-container-shape: 8px; --md-bottom-sheet-max-width: 420px;">
<p style="margin: 0;">Recoloured surface, squared corners and a capped width.</p>
</md-bottom-sheet>CSS parts — scrim, container, drag-handle, drag-handle-indicator,
header, headline, close, divider-top, content, divider-bottom and
actions. Note that the sheet surface is container, not surface; there is
no surface part.
Outlined surface, italic headline, a wider drag handle and a tinted scrim.
<!-- index.html — register the AWC UI elements once -->
<script type="module">
import '@awc-ui/core/define';
</script>
<style>
#bs-parts::part(container) { border: 2px solid var(--md-sys-color-primary); }
#bs-parts::part(headline) { font-style: italic; letter-spacing: .04em; }
#bs-parts::part(drag-handle-indicator) { inline-size: 48px; }
#bs-parts::part(scrim) { background: color-mix(in srgb, var(--md-sys-color-primary) 30%, transparent); }
</style>
<md-button id="bs-parts-btn" variant="filled">Styled parts</md-button>
<md-bottom-sheet id="bs-parts" headline="Styled with ::part()" closeable>
<p style="margin: 0;">Outlined surface, italic headline, a wider drag handle and a tinted scrim.</p>
</md-bottom-sheet>
<script type="module">
document.getElementById('bs-parts-btn').addEventListener('click', () => {
document.getElementById('bs-parts').show();
});
</script>import { useRef } from 'react';
import { MdBottomSheet, MdButton } from '@awc-ui/react';
export function Demo() {
const bsPartsRef = useRef(null);
return (
<>
<style>
#bs-parts::part(container) { border: 2px solid var(--md-sys-color-primary); }
#bs-parts::part(headline) { font-style: italic; letter-spacing: .04em; }
#bs-parts::part(drag-handle-indicator) { inline-size: 48px; }
#bs-parts::part(scrim) { background: color-mix(in srgb, var(--md-sys-color-primary) 30%, transparent); }
</style>
<MdButton id="bs-parts-btn" variant="filled" onClick={() => bsPartsRef.current?.show()}>Styled parts</MdButton>
<MdBottomSheet id="bs-parts" ref={bsPartsRef} headline="Styled with ::part()" closeable>
<p style={{ margin: '0' }}>Outlined surface, italic headline, a wider drag handle and a tinted scrim.</p>
</MdBottomSheet>
</>
);
}// 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>
#bs-parts::part(container) { border: 2px solid var(--md-sys-color-primary); }
#bs-parts::part(headline) { font-style: italic; letter-spacing: .04em; }
#bs-parts::part(drag-handle-indicator) { inline-size: 48px; }
#bs-parts::part(scrim) { background: color-mix(in srgb, var(--md-sys-color-primary) 30%, transparent); }
</style>
<md-button id="bs-parts-btn" variant="filled" (click)="bsParts.show()">Styled parts</md-button>
<md-bottom-sheet id="bs-parts" #bsParts headline="Styled with ::part()" closeable>
<p style="margin: 0;">Outlined surface, italic headline, a wider drag handle and a tinted scrim.</p>
</md-bottom-sheet><script setup>
import { ref } from 'vue';
import '@awc-ui/core/define';
const bsParts = ref(null);
</script>
<template>
<style>
#bs-parts::part(container) { border: 2px solid var(--md-sys-color-primary); }
#bs-parts::part(headline) { font-style: italic; letter-spacing: .04em; }
#bs-parts::part(drag-handle-indicator) { inline-size: 48px; }
#bs-parts::part(scrim) { background: color-mix(in srgb, var(--md-sys-color-primary) 30%, transparent); }
</style>
<md-button id="bs-parts-btn" variant="filled" @click="bsParts?.show()">Styled parts</md-button>
<md-bottom-sheet id="bs-parts" ref="bsParts" headline="Styled with ::part()" closeable>
<p style="margin: 0;">Outlined surface, italic headline, a wider drag handle and a tinted scrim.</p>
</md-bottom-sheet>
</template><script>
import '@awc-ui/core/define';
let bsParts;
</script>
<style>
#bs-parts::part(container) { border: 2px solid var(--md-sys-color-primary); }
#bs-parts::part(headline) { font-style: italic; letter-spacing: .04em; }
#bs-parts::part(drag-handle-indicator) { inline-size: 48px; }
#bs-parts::part(scrim) { background: color-mix(in srgb, var(--md-sys-color-primary) 30%, transparent); }
</style>
<md-button id="bs-parts-btn" variant="filled" on:click={() => bsParts?.show()}>Styled parts</md-button>
<md-bottom-sheet id="bs-parts" bind:this={bsParts} headline="Styled with ::part()" closeable>
<p style="margin: 0;">Outlined surface, italic headline, a wider drag handle and a tinted scrim.</p>
</md-bottom-sheet>md-bottom-sheet::part(container) { border: 2px solid var(--md-sys-color-primary);}
md-bottom-sheet::part(drag-handle-indicator) { inline-size: 48px;}Check any override in both themes — the scrim and the sheet surface sit at different elevations in each, so a colour that reads well on one can flatten on the other. The dark-theme story is the counterpart to the themed instance above.
md-side-sheet ·
md-dialog ·
md-snackbar ·
md-menu ·
md-list ·
md-icon-button
md-bottom-sheetTwo 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-bottom-sheet 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.