Skip to content

Drawer ​

A panel that slides in from the screen edge, often used for filters, details, and form editing.

Four directions ​

placement supports left / right / top / bottom. Horizontal placements manage width; vertical placements manage height — width / size controls the panel height for top / bottom.

Four placements

Top drawerBottom drawerLeft drawerRight drawer

Slides from the top; `size="small"` sets the height to 256px here.

Slides from the bottom — the mainstream form on mobile action sheets.

Status: All

Category: All

Sort: Created time

Right is the default direction; click the mask, the close button, or press Esc to close.

Vertical height (height) ​

height directly sets the height of top/bottom drawers (overriding the vertical mapping of width / size; silently ignored by left/right drawers). Plain numbers are treated as px; lengths and percentages pass through.

Vertical height (height)

Top drawer height=40vhBottom drawer height=280

height="40vh": 40% of the viewport height — taller than the default 320px.

height="280": a plain number is treated as pixels.

Animation and lifecycle events ​

Open/close transitions are animated (transform/opacity) and fall back to instant switching under prefers-reduced-motion. Lifecycle events: oas-open / oas-after-open / oas-close (detail carries the close source) / oas-after-close.

Lifecycle events

Open and watch events

Operate the footer buttons / mask / Esc and watch the messages (`oas-close` carries the close source).

Cancel-semantics event (oas-cancel) ​

Cancel-semantic closes (cancel button / mask click / Esc) dispatch oas-cancel (detail.source tells the origin), distinct from confirm closing (oas-ok); ✕ / swipe / programmatic closes do not dispatch it. Use it to clean up temporary state or track cancellations.

Cancel-semantics event

Open and watch cancel events

Close via "Cancel" / mask / Esc → a message shows oas-cancel; click "Save" → oas-ok; ✕ shows neither.

Close interception (before-close) ​

Listen to the cancelable oas-before-close (event.detail.source tells the close origin) and call preventDefault() to block closing — for unsaved-data protection.

Close interception

Open form drawer

Clicking "Discard" / ✕ / mask / Esc is all intercepted: "You have unsaved changes" and the drawer stays open. "Save" closes normally.

A footer slot with content hides the built-in OK/Cancel buttons; a header-actions slot renders extra actions right of the title; ok-text / cancel-text override button labels.

Slots and labels

Open custom drawerIn progress

The tag right of the title comes from the `header-actions` slot.

CancelSubmit task

Loading and async OK state ​

loading shows skeleton placeholders in the content area and disables the buttons (async detail loading); ok-loading puts the OK button into a loading state (spinner + no double trigger) for async submits.

loading / async OK

Open loading drawerOpen async submit drawer

Content loads asynchronously; this section is covered by skeletons.

Clicking "Submit" puts the OK button into loading; it closes automatically 2s later with a success message.

Resizable ​

resizable shows a drag rail on the free edge; drag it or use arrow keys to change width/height (clamped by resize-min / resize-max). oas-resize fires on release.

Resizable

Open resizable drawer

Drag the vertical rail on the left edge of the panel (or use arrow keys); the new width is reported after release.

Mobile gestures: swipe to close + snap points ​

swipeable enables drag-to-close (start from the handle / header; close on threshold or a fast flick). A bottom drawer with snap-points (viewport fractions or pixels) snaps to the nearest point on release.

Gesture drawer

Open snap drawerOpen swipe drawer

Drag the top handle: on release it snaps to 40% / 85% of the viewport height; drag past the threshold or flick to close.

Drag the handle downward to close.

Imperative API ​

drawer(options) opens a drawer without templates and returns a { close() } handle; when onOk returns a Promise, the OK button loading is managed automatically.

Imperative

drawer() basicdrawer() async submit

Nested drawers ​

Stacked drawers get automatic stack management: later openers sit on top (incremental z-index), Esc closes them layer by layer, and the focus trap only applies to the topmost drawer.

Nested layers

Open outer drawer

Open an inner drawer from within the outer one and observe the stacking.

Open inner drawer

The inner drawer covers the outer one; press Esc to close the inner first, then again for the outer.

Render and mount strategies ​

destroy-on-close clears the content after the close animation (re-initialize on next open); append-to mounts the panel into a target container (escapes overflow clipping).

destroy-on-close / append-to

destroy-on-closeappend-to

Content is cleared after closing; fill it again before the next open (see the button below).

Fill content

The panel is mounted into a body-level container, so it is not clipped even inside an `overflow: hidden` host.

Header / close button / Esc switches ​

no-header hides the whole header, no-close-btn hides ✕, no-esc-close disables Esc closing — giving the host full control over close entries.

Close entries

Open bare drawer

No header, no ✕, no Esc — close via the button below or the mask (mask click still works).

Close

Initial focus ​

initial-focus specifies which element receives focus on open (a CSS selector; the panel is searched first, then light DOM); falls back to ✕ when nothing matches.

Initial focus

Focus input on open

Controlled visibility ​

visible is a controlled attribute: the host (button / JS) sets or removes it; remove visible after listening to events to close.

Controlled visibility (visible)

Open (set visible)Close (remove visible)

External buttons set / remove `visible` to control visibility without relying on the footer buttons.

Controlled / uncontrolled contract: the component is uncontrolled + event-notification. On every user close path (mask / ✕ / Esc / cancel) it removes its own visible attribute and emits oas-close (the source is in detail), so the host visible attribute always reflects the real state and never goes stale. For declarative binding (host state drives visible), sync the state to closed inside an oas-close handler so it is not written back after closing; intercepting oas-before-close with preventDefault() keeps the drawer open with visible untouched.

no-footer / no-mask-close

No footer buttonsNo mask close

The footer action area is hidden; only ✕ and Esc remain as close entries.

Clicking the mask won't close it; use ✕ / Esc or the footer buttons.

No mask mode (no-mask) ​

no-mask renders no mask layer (the page stays visible and unblocked) while clicking anywhere outside the drawer still closes it (reusing the mask click mask cancel semantics, emitting oas-close / oas-cancel). Suited for lightweight scenarios that keep the page visible, such as side tool panels or persistent filter rails; combined with no-mask-close, outside clicks no longer close (only ✕ / Esc / buttons remain). Non-modal semantics follow dialog.show(): aria-modal="false", no focus steal on open, no Tab trapping, no focus restore on close; focus semantics are captured at open time (toggling no-mask at runtime updates aria and the focus trap but does not change the focus-restore behavior of an already-open drawer).

No mask mode

Open no-mask drawer

The page is not dimmed; clicking anywhere outside the drawer closes it (Esc and ✕ still work).

Size presets ​

Size presets

small (256px)large (736px)

small tier: 256px, suited for auxiliary info on narrow screens.

large tier: 736px, suited for complex forms or detail scenarios.

Focus trap / scroll lock / z-index switches ​

no-focus-trap turns off the Tab focus trap (for drawers embedded in a host dialog where focus is managed by the outer layer); no-scroll-lock turns off the body scroll lock (the page keeps scrolling); z-index explicitly sets the panel tier (defaults to the overlay tier + stack-depth offset).

no-focus-trap / no-scroll-lock / z-index

Open switches demo drawer

This drawer does not trap Tab focus or lock page scrolling; the tier is explicitly set to z-index="10"; ✕ / Esc / mask close as usual.

Event binding (onoas-*) ​

Drawer events can be bound directly in the template via onoas-* attributes (onoas-ok / onoas-close / onoas-open …), equivalent to an @oas-ok listener in the host framework.

Event feedback

Open drawer

Click OK or Cancel / ✕ and watch the messages in the top-right corner.

API ​

oas-drawer ​

Attributes ​

AttributeDescriptionTypeDefault
append-toPortal mount point: moves the mask and panel into an isolated shadow inside the target container (body or CSS selector, styles stay scoped), escaping host overflow clipping; moved back to the host shadow when the attribute is removed or unmatched——
cancel-textCancel button text; falls back to locale drawer.cancel——
destroy-on-closeClear the host children after the close animation finishes (re-rendered on next open)boolean—
heightExplicit height for vertical drawers (placement top/bottom), overriding width/size; ignored by left/right——
initial-focusFocus the element matching this selector on open (panel first, then host light DOM); falls back to the ✕ button / first focusable——
loadingBody loading state: content hidden with a skeleton placeholder; OK/Cancel buttons disabled tooboolean—
no-close-btnHide the ✕ close button in the title barboolean—
no-esc-closeDisable closing with Escboolean—
no-focus-trapDisable the focus trap (Tab no longer cycles inside the drawer)boolean—
no-footerHide footer action buttonsboolean—
no-headerHide the whole header (also removes the aria-labelledby association)boolean—
no-maskNo-mask mode: no mask rendered; clicking outside still closes with mask cancel semanticsboolean—
no-mask-closeDisable closing on mask clickboolean—
no-scroll-lockDo not lock body scroll while openboolean—
ok-loadingOK button loading state: spinner + disabled (aria-busy), blocking repeated triggersboolean—
ok-textOK button text; falls back to locale drawer.ok——
placementSlide directionstringright
resizableShow the edge resize rail: drag to resize along the main axis (width/height per placement), arrow keys step ±8px, Home/End jump to min/maxboolean—
resize-maxResize upper bound (px, default 1000)string1000
resize-minResize lower bound (px, default 160)string160
sizePreset size or a concrete value: small (256px) / medium (378px) / large (736px), or write directly like 512px, 40%——
snap-pointsSnap points array (comma-separated; values ≤1 are viewport-height fractions, >1 are pixels, e.g. 0.35,0.6): bottom/top drawers snap to the nearest point on release and start at the highest point; bottom + snap-points implies gestures——
swipeableEnable gestures: drag from the handle/title bar to close (past 35% of the panel size or a fast fling); implied by snap-points (bottom/top)boolean—
titleTitle text (rendered into the visible title region; absorbed from the host on read so no native hover tooltip remains; pass an empty string to clear); use the "title" slot for rich contentstring—
visibleWhether shownboolean—
widthDrawer width (px or percentage), takes precedence over size——
z-indexExplicit z-index base: stacked on --oas-z-index-base (the panel sits 1 above the mask); defaults to the overlay tier --oas-z-overlay, incrementing with stack depth when nested——

Events ​

EventDescription
oas-after-closeClose animation finished (canonical, aligned with the after-* family); no detail. Scroll unlock and focus restore happen after this
oas-after-openOpen animation finished (canonical, aligned with the after-* family); no detail; same as oas-opened
oas-before-closeEmitted before a close request (✕ / cancel / mask / Esc / OK / swipe), cancelable, detail: { source }; preventDefault blocks the close
oas-cancelDispatched on cancel-semantic closes (cancel button / mask / Esc), detail: { source }
oas-closeClose: cancel button / ✕ / mask click / Esc, detail: { source }
oas-closed[Compat alias] Close animation finished, same as oas-after-close; will be removed later
oas-okClicked "OK"
oas-openEmitted when opening starts (after the scroll lock, before initial focus); no detail
oas-opened[Compat alias] Open animation finished, same as oas-after-open; will be removed later
oas-resizeEmitted after drag-resize / arrow-key adjust / snap; detail: { size } (current main-axis size in px)

Slots ​

NameDescription
defaultDrawer body content
footerFooter action area
header-actionsAction area on the right of the title bar
titleRich title content slot; overrides the title attribute text when present

CSS Variables ​

CSS VariableDefault
--oas-glass-blurnone
--oas-glass-ringtransparent

role="dialog" + aria-modal="true"; focus moves in on open (default ✕, overridable via initial-focus) and is restored on close.

Released under the MIT OR Apache-2.0 License.