A wider screen, please

This page features interactive demos and side-by-side code samples that need at least 340 pixels of horizontal space. Please widen your browser window, or rotate your device, to continue.

Morpheus v0.1.0
v0.1.0

Drawer#

Component <neo-drawer> is a modal side-sheet on the platform <dialog>. Same browser-owned modal lifecycle as Dialog, anchored to one of four viewport edges via side (right default, left / top / bottom) and animated in with a slide transform. Dismiss via backdrop, Esc, or swipe toward the anchored edge; set dismissible="false" to force an explicit button choice.

Default
Sides
Frost glass
Non-dismissible
Scrolling body
Touch-dismiss
Deactivate Rename Duplicate
Delete
New state Toggle autoplay
Open drawer

Notification preferences

Choose how you'd like to be notified about updates and activity.

You can change these settings any time.

Cancel Save
HTML CSS

The selected state's markup and its scoped CSS. The CSS is wrapped in <style>@scope { ... }</style> and applies to the preview only. Edits change the preview above, applied live while Sync is on, or when you click Patch or Replace. Patch morphs the preview; Replace swaps it outright.

Replace Patch

Signals

Datastar signals declared in the selected state's markup. Change a value to drive the preview, applied live while Sync is on, or when you click Patch.

Patch

Reference#

Attributes #

NameTypeDefaultDescription
openboolean reflected observedfalseControls drawer visibility.
side"right" | "left" | "top" | "bottom" observedrightEdge the drawer slides in from.
dismissibletrue | falsetrueBackdrop click and Esc close the drawer. Set dismissible="false" to require an explicit close.
touch-dismiss"off" | CSS lengthonDrag-to-close gesture. On by default; set touch-dismiss="off" (or "false" / "no" / "none") to disable. The default close threshold is half the panel's relevant dimension along the swipe axis; pass a CSS length (e.g. 120, 120px, or 40%) to pin it explicitly. Touches that start on a same-axis scrollable child or on an element that owns a same-axis gesture (range input, slider, resizable handle, color field, or any [data-neo-drawer-touch-ignore] descendant) are ignored. Disabled when dismissible="false".

States #

NameDescription
:state(dragging)Set while a touch-dismiss drag is in flight.

Slots #

NameDescription
defaultA <dialog> with optional [data-neo-drawer-header], [data-neo-drawer-body], [data-neo-drawer-footer] children.
[data-neo-drawer-title]Accessible name for the drawer; its id wires the dialog's aria-labelledby.
[data-neo-drawer-description]Accessible description; its id wires the dialog's aria-describedby.
[data-neo-drawer-trigger]Element that opens the drawer on click.
[data-neo-drawer-close]Element that closes the drawer on click (matched via closest, so a descendant works too).
[data-neo-async-placeholder]Loading skeleton swapped in while the body morphs in.

Events #

NameDetailBubblesDescription
neo-drawer-opennoneyesFires when the drawer opens.
neo-drawer-closenoneyesFires when the drawer closes.

CSS variables #

NameDefaultDescription
--neo-drawer-width22remPanel width for the left and right sides.
--neo-drawer-height16remPanel height for the top and bottom sides.
--neo-drawer-enter-durationcalc(220ms * var(--neo-duration-scale, 1))Slide and fade transition duration. Surface colors, border, shadow, and padding reuse the --neo-dialog-* tokens.

Examples#

Sides#

Same markup, different side. The panel anchors to that edge and slides in along the matching axis; width vs height defaults swap automatically.

Left

Left drawer

Slides in from the left edge.

Common pattern for navigation sidebars on small screens.

Close
Right

Right drawer

The default side.

Common pattern for settings, filters, and detail panels.

Close
Top

Top drawer

Slides down from the top, useful for global announcements.

Close
Bottom

Bottom drawer

Mobile-style sheet.

The bottom-sheet idiom from mobile UIs. Actions sit at thumb reach.

Close

Frost glass#

Theme the surface by setting --neo-dialog-* tokens inline on the host.

  • The drawer reuses the dialog's surface tokens, so any recipe that frosts a centred dialog frosts the side sheet too.
  • Here a 22px backdrop blur on the surface plus a 6px overlay blur read through both layers as a deep frosted scene.
Open frost glass

Frost glass surface

Two stacked blurs: the ::backdrop frosts the page behind the drawer at 6px (with a soft 18% black tint), and the drawer surface adds another 22px on top of its own 42% dark wash.

Holds up in both light and dark modes. The panel stays dark and the blurred page reads through the glass either way.

Close OK

Non-dismissible#

For must-decide flows where dismissing implicitly would be ambiguous, set dismissible="false" on the host: backdrop click and Esc no longer close, so the user must pick a button.

Confirm subscription

Subscribe to updates?

Pick one. The dialog can't be dismissed.

We'll send a weekly digest of feature releases and changelog entries to your account email.

No thanks Subscribe

Scrolling body#

The body region scrolls independently while the header and footer stay pinned, common for a settings drawer with many fields.

Open settings

Settings

Header stays put while the body scrolls.

Setting row 1: placeholder copy to make the body overflow.

Setting row 2: placeholder copy to make the body overflow.

Setting row 3: placeholder copy to make the body overflow.

Setting row 4: placeholder copy to make the body overflow.

Setting row 5: placeholder copy to make the body overflow.

Setting row 6: placeholder copy to make the body overflow.

Setting row 7: placeholder copy to make the body overflow.

Setting row 8: placeholder copy to make the body overflow.

Setting row 9: placeholder copy to make the body overflow.

Setting row 10: placeholder copy to make the body overflow.

Setting row 11: placeholder copy to make the body overflow.

Setting row 12: placeholder copy to make the body overflow.

Setting row 13: placeholder copy to make the body overflow.

Setting row 14: placeholder copy to make the body overflow.

Setting row 15: placeholder copy to make the body overflow.

Setting row 16: placeholder copy to make the body overflow.

Setting row 17: placeholder copy to make the body overflow.

Setting row 18: placeholder copy to make the body overflow.

Setting row 19: placeholder copy to make the body overflow.

Setting row 20: placeholder copy to make the body overflow.

Setting row 21: placeholder copy to make the body overflow.

Setting row 22: placeholder copy to make the body overflow.

Setting row 23: placeholder copy to make the body overflow.

Setting row 24: placeholder copy to make the body overflow.

Setting row 25: placeholder copy to make the body overflow.

Setting row 26: placeholder copy to make the body overflow.

Setting row 27: placeholder copy to make the body overflow.

Setting row 28: placeholder copy to make the body overflow.

Setting row 29: placeholder copy to make the body overflow.

Setting row 30: placeholder copy to make the body overflow.

Cancel Save

Touch-dismiss#

Mobile-style drag-to-close, on by default. A single-touch swipe in the closing direction follows the finger; release past the threshold or a fast flick closes, otherwise it snaps back.

  • Axis follows side: left/right drawers take horizontal swipes, top/bottom vertical.
  • The threshold defaults to half the panel's relevant dimension; pass a CSS length (here touch-dismiss="120px") to pin it, or touch-dismiss="off" to opt out.
  • Try the preview on a touchscreen, or with browser device-toolbar touch emulation.

The gesture stands down for descendants that own a same-axis gesture of their own (same-axis scrollable containers, range inputs, sliders, resizable handles, color fields, or any element marked [data-neo-drawer-touch-ignore]), and is disabled when dismissible="false". The <neo-slider> in the panel below demonstrates this: dragging the slider thumb horizontally never closes the drawer.

Open drawer

Filters

Swipe right past 120px to dismiss.

This example only works on touch devices. Use a touchscreen or your browser's device-toolbar touch emulation.

Try swiping the panel toward its anchored edge. Release past the 120px threshold to close, or short of it to snap back. A fast flick closes regardless of distance.

Cancel Apply

Opened from a dialog#

Open a drawer from inside a dialog. The dialog's Connected devices… button triggers a nested <neo-drawer> that slides in over the modal, so both overlays are open together; each keeps the page behind it from scrolling while its own content scrolls. Open the dialog, then click Connected devices….

Account settings

Account settings

Open the advanced panel to manage connected devices without leaving this dialog.

Connected devices…

Connected devices

Opened on top of the dialog; both overlays are open together.

  • MacBook Pro: this device
  • iPhone 15: last active 2h ago
  • iPad Air: last active yesterday
Close
Done

Examples: Datastar#

Load the body on every open. A [data-neo-async-placeholder] child holds skeleton bars; opening posts to the server, whose response morph-patches real content in to replace it.

  • On close the host reinstates the placeholder after the slide-out finishes (so it doesn't flash through the closing panel), so each open shows it again until the next response resolves.
  • The editor below is bound to the drawer_async_html signal the server reads at request time; edit it to change the next response.

Async loading#

Loading states only show with simulated delay. Raise network latency or handler delay in the Settings panel.

Load profile

Team members

Loaded fresh from the server each time you open this drawer.

Close

Async load with failure swap#

Async load that shows a failure state and a retry button when the request keeps failing.

  • The loader lives in the drawer's [data-neo-async-placeholder] slot, so close/reopen restores it; on success the server morphs <div id="<host-id>-body"> in place.
  • Once Datastar exhausts its retry budget, a failure view with a [data-neo-popover-async-retry] button replaces the body without closing the drawer.
  • Same failure-swap contract as PopoverAsync.

Loading states only show with simulated delay. Raise network latency or handler delay in the Settings panel.

Load profile

Team member

Loaded fresh from the server each time you open this drawer.

Close

Settings

Theme

Default Sunshine Ocean Matrix Rose

Accessibility

Playgrounds

Server Simulator