Skip to the content

Kongens Nytorv

Metro M1, M2 and M3.

Props

NameTypeDefaultDescription
anchor'left' | 'right' | 'bottom''left'The edge the sheet slides from and is swiped back to: left for the overview sheet, right for the detail panel, bottom for either on a phone. It sets data-anchor on the root and the viewport.
title*ReactNodeRequiredThe sheet's name, such as a stop or a destination, shown in the header and naming the dialog. Required, so every sheet draws its header with the title and the close button and is always named; a pattern with a header of its own (DetailPanel) passes that whole header as title, with slotProps.title.render a div, and names the dialog by its own aria-labelledby.
childrenReactNode—The sheet's body, below the header. It scrolls when it is taller than the sheet.
triggerReactElement<unknown, JSXElementConstructor<any> | string>—The button that opens the sheet, such as a Perron Button; it gets the trigger's ARIA and gets focus back on close. Without one, the sheet opens through open.
openboolean—Whether the sheet is open, for a controlled sheet.
defaultOpenbooleanfalseWhether the sheet starts open, for an uncontrolled sheet.
onOpen(event, details) => void—Called when the sheet asks to open: from the trigger.
onClose(event, reason, details) => void—Called when the sheet asks to close, with Base UI's reason: escape-key, close-press, outside-press, swipe, trigger-press, focus-out, close-watcher, imperative-action or none. details.cancel() keeps it open.
modalbooleanfalseWhether the sheet shuts out the rest of the page: focus trapped, the page's scroll locked, a scrim behind, closed by a press outside. Non-modal by default, so the map stays usable beside it; while non-modal it sets disablePointerDismissal, so a press on the map or focus leaving the sheet doesn't close it, and renders no backdrop.
snapPointsDrawerSnapPoint[]With anchor 'bottom': size.sheet-peek and 1The heights a bottom sheet snaps to: a fraction of the viewport from 0 to 1, pixels above 1, or a px or rem string. Ignored on a side sheet. A bottom sheet with no snapPoints snaps to size.sheet-peek and to full height, 1, starting at the peek.
snapPointnull | number | string—The snap point a bottom sheet is at, for a controlled snap point.
defaultSnapPointnull | number | stringThe first snap pointThe snap point a bottom sheet opens at, for an uncontrolled snap point.
onSnapPointChange(event, snapPoint, details) => void—Called when a bottom sheet asks to move to another snap point, by a swipe. It takes MUI's shape, (event, snapPoint, details), with event being details.event.
closeLabelstringThe locale'sOverrides "Close", the close button's label.
slotProps{ trigger?: Props<unknown> | undefined; portal?: DrawerPortalProps | undefined; backdrop?: DrawerBackdropProps | undefined; viewport?: DrawerViewportProps | undefined; title?: DrawerTitleProps | undefined; close?: DrawerCloseProps | undefined; content?: DrawerContentProps | undefined; }—Props for each part: trigger, portal, backdrop, viewport, title, close, content.
anchor'left' | 'right' | 'bottom', default 'left'
The edge the sheet slides from and is swiped back to: left for the overview sheet, right for the detail panel, bottom for either on a phone. It sets data-anchor on the root and the viewport.
title (required)ReactNode, default Required
The sheet's name, such as a stop or a destination, shown in the header and naming the dialog. Required, so every sheet draws its header with the title and the close button and is always named; a pattern with a header of its own (DetailPanel) passes that whole header as title, with slotProps.title.render a div, and names the dialog by its own aria-labelledby.
childrenReactNode, default —
The sheet's body, below the header. It scrolls when it is taller than the sheet.
triggerReactElement<unknown, JSXElementConstructor<any> | string>, default —
The button that opens the sheet, such as a Perron Button; it gets the trigger's ARIA and gets focus back on close. Without one, the sheet opens through open.
openboolean, default —
Whether the sheet is open, for a controlled sheet.
defaultOpenboolean, default false
Whether the sheet starts open, for an uncontrolled sheet.
onOpen(event, details) => void, default —
Called when the sheet asks to open: from the trigger.
onClose(event, reason, details) => void, default —
Called when the sheet asks to close, with Base UI's reason: escape-key, close-press, outside-press, swipe, trigger-press, focus-out, close-watcher, imperative-action or none. details.cancel() keeps it open.
modalboolean, default false
Whether the sheet shuts out the rest of the page: focus trapped, the page's scroll locked, a scrim behind, closed by a press outside. Non-modal by default, so the map stays usable beside it; while non-modal it sets disablePointerDismissal, so a press on the map or focus leaving the sheet doesn't close it, and renders no backdrop.
snapPointsDrawerSnapPoint[], default With anchor 'bottom': size.sheet-peek and 1
The heights a bottom sheet snaps to: a fraction of the viewport from 0 to 1, pixels above 1, or a px or rem string. Ignored on a side sheet. A bottom sheet with no snapPoints snaps to size.sheet-peek and to full height, 1, starting at the peek.
snapPointnull | number | string, default —
The snap point a bottom sheet is at, for a controlled snap point.
defaultSnapPointnull | number | string, default The first snap point
The snap point a bottom sheet opens at, for an uncontrolled snap point.
onSnapPointChange(event, snapPoint, details) => void, default —
Called when a bottom sheet asks to move to another snap point, by a swipe. It takes MUI's shape, (event, snapPoint, details), with event being details.event.
closeLabelstring, default The locale's
Overrides "Close", the close button's label.
slotProps{ trigger?: Props<unknown> | undefined; portal?: DrawerPortalProps | undefined; backdrop?: DrawerBackdropProps | undefined; viewport?: DrawerViewportProps | undefined; title?: DrawerTitleProps | undefined; close?: DrawerCloseProps | undefined; content?: DrawerContentProps | undefined; }, default —
Props for each part: trigger, portal, backdrop, viewport, title, close, content.

Use it

import { Sheet } from '@mylder/perron'

Ask your agent

get_reference({ library: "base-ui", component: "Drawer" })

Keyboard and ARIA

From the Base UI reference for each part. "From" says who does it: Base UI, or Perron.

KeyDoesFrom
Enter, SpaceOn the trigger, opens the sheet and moves focus to the popup; Tab then reaches the close buttonBase UI
Enter, SpaceOn the close button, closes the sheet with reason close-press, and focus returns to the trigger or to the element focused beforeBase UI
EscCloses the topmost open sheet with reason escape-key, and focus returns as aboveBase UI
Tab, Shift+TabIn a modal sheet, moves through the sheet's tabbable elements and wraps inside itBase UI
Tab, Shift+TabIn a sheet that is not modal, moves on into the page; the sheet stays openBase UI, with disablePointerDismissal set by Perron
Tab, Shift+TabStops on the body while it scrolls, and the arrow keys, PageUp, PageDown, Home and End scroll itPerron, with tabIndex 0 while the body overflows
ElementRoleAccessible name fromStates announced
triggerbuttonThe caller's elementaria-expanded, aria-haspopup="dialog", with aria-controls naming the popup
rootdialogtitle, through aria-labelledbyWhile modal, everything outside it is aria-hidden
titleheading, level 2titleNone
closebuttonThe owned close string, or closeLabelNone
backdropNone: presentationalNoneNone

What axe won't catch, from the contract, each with how this component meets it:

  • Everything the eye is told, the ear is told, for every shape each prop accepts: the close button's icon is aria-hidden and the CloseButton part gives it the owned label; the body scrolls at every snap point and takes focus while it does, so everything in it can be reached by keys and screen readers without the grab; the close button and Esc dismiss it.
  • Every landmark and named region has an accessible name: the dialog is named by its required title, through Base UI's aria-labelledby.
  • Every default label can be overridden: closeLabel.
  • Every shape the type accepts renders, in every variant: title and children show a string, an element and a list alike, in each anchor: the Left, Right and Bottom stories' play tests open a sheet with an element title and a string body, a list title and an element body, and a string title and a list body, each named by its title.
  • A hidden copy kept for animation is aria-hidden, and a collapsed region is inert: none here; a closed sheet is unmounted, or hidden when slotProps.portal.keepMounted keeps it.
  • Native semantics first: <button> for the trigger and the close button, <h2> for the title, and Base UI's dialog role on the root.
  • Focus moves in reading order and returns on close: title, close button, then the content; Base UI returns focus to the trigger or to the element focused before.
  • Every target is at least size.target-min square: the close button, size.control.
  • Loops stop when the system asks for less motion: none here; the slide reads duration.sheet, which the tokens shorten to 0.01 ms.

Owned strings

Every word the component shows or announces on its own. Reuse a key packages/perron/src/locale/en.ts already has; a new key gets its English here, and agents write the Danish, Swedish and Norwegian in the locale files.

KeyEnglishOverrideSeen or heard where
closeClosecloseLabelThe close button in the header, announced as its name

Do and don't

For a caller using the component, with the reason. Each row is a rule: a must row's Don't is never right, a should row's needs a reason, and Checked by says what catches the Don't in a caller's code.

RuleLevelDoDon'tChecked by
DD-1shouldUse a sheet for a panel beside the map, left open while the rider pans and picks vehicles: it is not modal by defaultUse a modal sheet to ask for a decision; that is a Dialog, which needs no swipe or snap points (Base UI's own advice)review
DD-2mustPick anchor bottom on a phone, under breakpoint.phone, in the screen's codeExpect the sheet to turn into a bottom sheet on its own; it sizes to its viewport and holds no media queryreview
DD-3mustName the sheet with title: the stop, line or destination it showsLeave the title out, or draw a heading of your own in children: the dialog takes its name from titlereview
DD-4mustHandle onClose and let Esc close the topmost sheet, as Accessibility orders: a dialog, then a detail panelCancel every close of a sheet the rider can't otherwise leave; cancel only the reasons a screen must refuse, such as swipe on the overview sheetreview
DD-5mustMark a horizontal carousel inside a bottom sheet with data-base-ui-swipe-ignore, so its drags don't swipe the sheet; put a Slider in as it is, since it marks its own controlLet a control that drags sideways fight the sheet's swipereview
DD-6mustKeep one primary action per sheetPut two brand buttons in one sheet; a screen has one brand action at mostreview