A dialog attached to a screen edge, with swipe-to-dismiss, snap points and side-panel layouts.
<tp-drawer>
Drawer
Default density
Compact density
Restart from the initial state
Show the copyable source
Notification settings
Done
Notification settings
Done
<tp-drawer label="Notifications" description="Choose what Acme sends to your inbox." show-swipe-handle>
<tp-button slot="trigger" variant="outline">Notification settings</tp-button>
<div style="display:grid;gap:var(--tp-space-4)">
<tp-field label="Mentions and replies" description="When someone mentions you or replies to your comment." orientation="horizontal">
<tp-switch name="mentions" default-checked></tp-switch>
</tp-field>
<tp-field label="Weekly digest" description="A summary of project activity every Monday." orientation="horizontal">
<tp-switch name="digest"></tp-switch>
</tp-field>
</div>
<tp-button slot="close">Done</tp-button>
</tp-drawer>
Usage
Drawer is a Dialog attached to an edge of the viewport. It shares Dialog’s slots (trigger, title, description, default body, footer, close), focus management and dismissal, and adds edge placement and swipe gestures. Swiping toward the closing edge dismisses it; the trigger and the close action keep working without gestures. Input controls, selection and native content scrolling keep their normal behavior. Touch dismissing waits for the content scroll edge. Footer content does not implicitly dismiss; use the close slot or registerCloseAction.
Side panels
edge takes a logical edge, so inline-end is the right side in left-to-right pages and flips in right-to-left ones. Set swipe-enabled="false" for a desktop side panel that should not react to drags.
Edges
Default density
Compact density
Restart from the initial state
Show the copyable source
backdrop="blur" keeps the dark fill and blurs the page behind it, which helps when the page content would compete with the drawer. It changes appearance only; modality decides whether a backdrop is shown at all. Dialog’s container modality is not supported because Drawer geometry is viewport-relative; it normalizes to modal with a drawer-modality-unsupported diagnostic. Non-modal and focus-trap-only modes hide the backdrop and preserve outside pointer interaction. The fill uses --tp-opacity-backdrop and remains visible where backdrop filtering is unsupported.
Blurred backdrop
Default density
Compact density
Restart from the initial state
Show the copyable source
ShareCopy link
ShareCopy link
<tp-drawer backdrop="blur" label="Share “Q3 roadmap”" description="Anyone with the link can view." show-swipe-handle>
<tp-button slot="trigger" variant="outline">Share</tp-button>
<tp-field label="Link">
<tp-input readonly default-value="https://acme.app/d/q3-roadmap"></tp-input>
</tp-field>
<tp-button slot="close">Copy link</tp-button>
</tp-drawer>
Snap points
Set snapPoints to let a sheet rest at partial heights. Numbers up to 1 are viewport fractions, larger numbers are pixels, and strings accept CSS lengths. With show-swipe-handle, the handle also works as a keyboard slider with the arrow keys, Home and End.
Snap points
Default density
Compact density
Restart from the initial state
Show the copyable source
Explore nearby
Drag the handle to expand the list, or release below the lowest point to close.
Done
Explore nearby
Drag the handle to expand the list, or release below the lowest point to close.
Done
<tp-drawer id="drawer-demo-snap" label="Nearby places" description="12 results within 2 km." show-swipe-handle>
<tp-button slot="trigger" variant="outline">Explore nearby</tp-button>
<p style="margin:0">Drag the handle to expand the list, or release below the lowest point to close.</p>
<tp-button slot="close" variant="outline">Done</tp-button>
</tp-drawer>
<script type="module">
const drawer = document.querySelector('#drawer-demo-snap');
drawer.snapPoints = [0.25, 0.5, 1];
drawer.defaultSnapPoint = 0.5;
</script>
onSnapPointChange receives each proposed snap point as a cancelable event, and setSnapPoint() moves the sheet from code.
Nested drawers
Put a Drawer inside another Drawer’s body to open a follow-up step on top of it. The parent stays open and recedes behind the new sheet; closing the child returns to the parent where it left off.
Nested
Default density
Compact density
Restart from the initial state
Show the copyable source
Manage members
Maya Chen, Jonas Weber, Amara Okafor and 3 others have access.
Invite memberSend inviteDone
Manage members
Maya Chen, Jonas Weber, Amara Okafor and 3 others have access.
Invite memberSend inviteDone
<tp-drawer label="Workspace members" description="Northwind · 6 members" show-swipe-handle>
<tp-button slot="trigger" variant="outline">Manage members</tp-button>
<p style="margin:0">Maya Chen, Jonas Weber, Amara Okafor and 3 others have access.</p>
<tp-drawer label="Invite a member" description="They get an email with a join link." show-swipe-handle>
<tp-button slot="trigger" variant="outline">Invite member</tp-button>
<tp-field label="Email">
<tp-input type="email" name="invite-email" placeholder="lee@northwind.co"></tp-input>
</tp-field>
<tp-button slot="close">Send invite</tp-button>
</tp-drawer>
<tp-button slot="close" variant="outline">Done</tp-button>
</tp-drawer>
Non-dismissible
Set the dismissible property to false when outside presses, Escape and swipes must not close the drawer, such as a required acknowledgement. The explicit close action still works.
Non-dismissible
Default density
Compact density
Restart from the initial state
Show the copyable source
Review terms
We changed how workspace data is retained after an account is closed. Review the summary before you continue.
I agree
Review terms
We changed how workspace data is retained after an account is closed. Review the summary before you continue.
I agree
<tp-drawer id="drawer-demo-terms" label="Updated terms of service" description="Effective November 1, 2026.">
<tp-button slot="trigger" variant="outline">Review terms</tp-button>
<p style="margin:0">We changed how workspace data is retained after an account is closed. Review the summary before you continue.</p>
<tp-button slot="close">I agree</tp-button>
</tp-drawer>
<script type="module">
document.querySelector('#drawer-demo-terms').dismissible = false;
</script>
Optional constituents
tp-drawer-swipe-area: a consumer-positioned opening region. Set for to a Drawer ID, .owner to a Drawer reference, or place it in that Drawer’s swipe-area slot. swipeDirection defaults opposite the closing direction; disabled defaults false. Native host class/style and the drawer-swipe-area part configure its region; no forced page-sized hit area is added.
tp-drawer-provider: optional common ancestor tracking open descendant Drawers. Nested Drawer lifecycle still belongs to Dialog. Removing the last Drawer clears active geometry.
tp-drawer-indent and tp-drawer-indent-background: optional native layout regions inside the nearest provider. Detaching resets geometry and subscriptions. Indent does not invent a page transform by default.
tp-drawer-virtual-keyboard-provider: optional ancestor that observes the real visual viewport, coalesces keyboard geometry and reveals an editable control inside the active descendant Drawer’s existing scroll region. It restores geometry after keyboard dismissal and disconnects its observers when removed. It does not own focus or create a second scroller.
Provider and keyboard-provider render their children without layout boxes.
API Reference
Every element also accepts motion-policy.
Attributes
AttributePropertyTypeDefaultDescriptionedgeedgeDrawerEdge'block-end'The viewport edge the drawer attaches to, in writing direction: inline-end is the right side of a left-to-right page.swipe-directionswipeDirectionDrawerDirection—The physical direction a swipe closes the drawer in; setting it picks the matching edge.sideside'left' | 'right' | 'top' | 'bottom'—The physical side the drawer is attached to; setting it picks the matching edge.snap-to-sequential-pointssnapToSequentialPointsbooleanfalseMakes a fast swipe stop at the next snap point instead of skipping past it.dismissibledismissiblebooleantrueWhether Escape, an outside press and a swipe close the drawer; set the property to false for a required acknowledgement, which the close action still ends.show-swipe-handleshowSwipeHandlebooleanfalseShows a handle on the closing edge; with several snap points it is also a slider the arrow keys move.swipe-enabledswipeEnabledbooleantrueWhether drags move and dismiss the drawer; false suits a desktop side panel.
Properties
PropertyTypeDefaultDescriptionsnapPointsreadonly DrawerSnapPoint[][]Heights a sheet can rest at: numbers up to 1 are fractions of the viewport, larger numbers pixels, strings CSS lengths.defaultSnapPointDrawerSnapPoint | null | undefined—The snap point a sheet opens at when snapPoint is not set; by default the first of snapPoints.onSnapPointChange((event: TpValueChangeEvent<DrawerSnapPoint | null>) => void) | undefined—Called with each snap point proposal before tp-snap-point-change is dispatched.snapPointDrawerSnapPoint | null—The snap point the sheet rests at; set it from the start to own it, writing back each accepted proposal.activeSnapPointDrawerSnapPoint | null—Alias of snapPoint.
Events
EventTypeDescriptiontp-snap-point-changeTpValueChangeEvent<DrawerSnapPoint | null>Cancelable proposal of the snap point to rest at, from a swipe, the handle's keys or setSnapPoint(); detail.value is a member of snapPoints.tp-open-changeTpSurfaceOpenChangeEventCancelable proposal to open or close, before the dialog changes; detail.value is the proposed state and detail.reason what asked (trigger-press, close-action, escape-key, outside-press, imperative-action and others). On a close, detail.preventUnmountOnClose() keeps the content mounted.tp-open-change-completeCustomEvent<{ open: boolean; }>After the dialog finishes opening or closing; detail.open.
Methods
MethodReturnsDescriptionsetSnapPoint(value, reason, sourceEvent?)booleanProposes a snap point from code, as a swipe would; returns whether it was accepted.
Slots
SlotDescriptionswipe-areaA Drawer Swipe Area that opens this drawer with a swipe from the edge.defaultThe body of the task.triggerThe control that opens the dialog, usually a Button.titleThe title, in place of label.descriptionThe description, in place of description.footerFooter content with no close behavior, such as a secondary action.closeAn action that closes the dialog when pressed, after its own handlers.
CSS parts
PartDescription::part(drawer)The host around the trigger and the drawer.