Raft 设计系统

组件 · 浮层

Drawer

A panel that slides in from the right for details and settings people look at for a while, and on a phone a bottom sheet for picking a place to go or reading a short explanation.

稳定也叫 Sheet, BottomSheet
import { Drawer } from "@botiverse/raft-ui";
本页尚未翻译
译文完成之前,这里显示英文原文。 阅读英文版

A drawer slides in from the right with details and settings that people look at for a while, scroll and edit, such as the details and members of #launch-week. The page stays in place behind it, under a scrim. On a phone the same component rises from the bottom as a bottom sheet: to pick a place to go, or to read a short explanation with one button.

drawer/compositions/responsive-placement

When to use

SituationUse
Details or settings people read, scroll and editDrawer, from the right
A large form filled in one pass: registering or editing an appThe wide Dialog, not a wide drawer (Forms)
Switching server, on a phoneNot a drawer: a popover under the top bar (Navigation). Working now is a popover, on a phone too
A sentence or two and one button, on a phoneA bottom sheet
Acting on one thingA menu (DropdownMenu, ContextMenu), never a bottom sheet
Picking a value, such as sort order or statusThe list of a Select, or radio items in a menu
ConfirmingAlertDialog
A glance on hoverPreviewCard
A thread or a profile kept open beside the conversationA side panel (ThreadPanel, Panel), which is not a drawer and has no scrim

Anatomy

  • Drawer: the root. Its swipeDirection sets the edge it comes from. Required.
  • DrawerTrigger: the button that opens it.
  • DrawerContent: the surface, portaled, with the scrim (DrawerOverlay) behind it when the drawer is modal. Required. It is layer-popover with a line-seam edge and shadow-float, like every other popup, not an ink border and a hard shadow: every floating surface uses the same shadow (Elevation).
  • DrawerHeader: the title row. In a drawer from the right it is 56px tall, with a line-seam under it and DrawerClose at its end.
  • DrawerTitle: the title. Required, unless the content has an aria-label.
  • DrawerDescription: a sentence under the title.
  • DrawerBody: the content. It scrolls.
  • DrawerFooter: the bottom bar. In a drawer from the right it has a line-seam over it.
  • DrawerActions: the buttons in the footer, in a row at the end in a drawer and stacked full width in a bottom sheet.
  • DrawerClose: closes the drawer. Without children it is the × icon button; with render it turns another button into a close button.
  • DrawerPortal and DrawerOverlay: lower-level parts.

Variants

VariantUse for
swipeDirection="right"The drawer: details and settings on desktop, at most 420px wide and the full window height
swipeDirection="down" (default)The bottom sheet, on a phone
snapPointsA bottom sheet that opens to a summary and expands to its full height. It shows the grab handle
drawer/compositions/snap-points

Behavior

  • A drawer slides in from its edge with ease-slide. It is the only overlay that moves, because the slide shows where it came from (Motion).
  • A modal drawer puts the same scrim as a dialog over the rest of the page: the page can't be used while the drawer is open, and the scrim says so. A click on the scrim closes it. A bottom sheet has the scrim too.
  • Swiping toward its edge dismisses a drawer.
  • A drawer opened from a drawer stacks on top of it, and the one below steps back.
  • On a phone, a drawer from the right fills the screen and works as a page: no ×, and Back at the top left.
  • A bottom sheet rises to at most 82% of the window height, with radius-dialog on its top corners, and keeps clear of the home indicator.
drawer/compositions/nested-close-callback

Content

  • Title it with the words of the button that opened it (Writing).
  • In a bottom sheet, write the explanation in one or two sentences, in foreground-muted, and its button as one action in Title Case: Got It.

Accessibility

  • Drawer wraps the Base UI Drawer. It provides the dialog semantics, the focus trap of a modal drawer, Esc, swipe to dismiss, and focus return to the trigger.
  • Give every drawer and bottom sheet a title: a visible DrawerTitle, or at least an aria-label on DrawerContent.
  • The × of DrawerClose is labeled "Close". On a phone, give the Back button that replaces it an aria-label ("Back").
  • The grab handle is hidden from assistive technology. Never make swiping the only way to close: keep ×, Back or a button in the sheet.

Rules

  • Keep a drawer to 420px at most. A form that needs more room is the wide Dialog.
  • Make the × the same icon button as in a panel header, with the same icon size, and put it last in DrawerHeader: its icon box ends on the margin, and a Back that replaces it on a phone starts on it (Alignment).
  • Put the buttons in the footer, never in the header. They are size="lg" (44px on a phone, 32px on a desktop): Cancel first, Save last (Forms).
  • Split a long form in a drawer into FormSection parts, the same sections as in the wide dialog, and save them all together with the footer's Save.
  • Under each yes-or-no checkbox in a drawer form, add a line that says what happens when it is unchecked. It is a checkbox, not a switch, because the form is saved (Forms).
  • Give every bottom sheet a grab handle at its top edge.
  • In a bottom sheet list, make each row 52px tall, with a 20px icon and the label in text-body, medium. Mark the current row with a check mark and bold text.

API

Drawer

Prop类型默认值
actionsRefA ref to imperative actions. - `unmount`: Manually unmounts the drawer. Call this after any externally controlled closing animation finishes. - `close`: Closes the drawer imperatively when called.RefObject<DrawerRootActions | null>
childrenThe content of the drawer.ReactNode | PayloadChildRenderFunction<unknown>
defaultOpenWhether the drawer is initially open.booleanfalse
defaultSnapPointThe initial snap point value when uncontrolled.DrawerSnapPoint
defaultTriggerIdID of the trigger that the drawer is associated with. This is useful in conjunction with the `defaultOpen` prop to create an initially open drawer.string
disablePointerDismissalWhether to prevent the drawer from closing on outside presses. For non-modal drawers, this also prevents the drawer from closing when focus moves outside of it.booleanfalse
handleA handle to associate the drawer with a trigger. If specified, allows detached triggers to control the drawer's open state. Can be created with the Drawer.createHandle() method.DrawerPrimitive.Handle<unknown>
modalDetermines if the drawer enters a modal state when open. - `true`: user interaction is limited to just the drawer: focus is trapped, document page scroll is locked, and pointer interactions on outside elements are disabled. - `false`: user interaction with the rest of the document is allowed. - `'trap-focus'`: focus is trapped inside the drawer, but document page scroll is not locked and pointer interactions outside of it remain enabled.boolean | "trap-focus"true
onOpenChangeEvent handler called when the drawer is opened or closed.(open: boolean, eventDetails: DrawerPrimitive.Root.ChangeEventDetails) => void
onOpenChangeCompleteEvent handler called after any animations complete when the drawer is opened or closed.(open: boolean) => void
onSnapPointChangeCallback fired when the snap point changes.(snapPoint: DrawerSnapPoint | null, eventDetails: DrawerPrimitive.Root.SnapPointChangeEventDetails) => void
openWhether the drawer is currently open.boolean
snapPointThe currently active snap point. Use with `onSnapPointChange` to control the snap point.DrawerSnapPoint
snapPointsSnap points used to position the drawer. Use numbers between 0 and 1 to represent fractions of the viewport height, numbers greater than 1 as pixel values, or strings in `px`/`rem` units (for example, `'148px'` or `'30rem'`).DrawerSnapPoint[]
snapToSequentialPointsDisables velocity-based snap skipping so drag distance determines the next snap point.booleanfalse
swipeDirectionThe swipe direction used to dismiss the drawer.SwipeDirection"down"
triggerIdID of the trigger that the drawer is associated with. This is useful in conjunction with the `open` prop to create a controlled drawer. There's no need to specify this prop when the drawer is uncontrolled (that is, when the `open` prop is not set).string

DrawerActions, DrawerBody

Prop类型默认值
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

DrawerClose

Prop类型默认值
size`md` (32px) and `icon-md` for rows, cards, banners, toolbars and panel headers; `lg` and `icon-lg` (the control height) for dialog footers, forms and beside a field; `xl` for a full-width call to action. The size follows the place: see Button › Size by place."md" | "lg" | "xl" | "icon-md" | "icon-lg" | DeprecatedButtonSize
variant"primary" | "accent" | "warning" | "danger" | "default" | "outline" | "information" | "muted" | "success" | "danger-secondary" | "danger-outline" | "ghost" | "on-frame"
nativeButtonWhether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`).booleantrue
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

DrawerContent

Prop类型默认值
portalPropsDrawerPortalProps
finalFocusDetermines the element to focus when the drawer is closed.boolean | RefObject<HTMLElement | null> | ((closeType: InteractionType) => boolean | HTMLElement | null | void)
initialFocusDetermines the element to focus when the drawer is opened.boolean | RefObject<HTMLElement | null> | ((openType: InteractionType) => boolean | HTMLElement | null | void)
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

DrawerDescription, DrawerFooter, DrawerHeader

Prop类型默认值
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

DrawerOverlay

Prop类型默认值
forceRenderWhether the backdrop is forced to render even when nested.booleanfalse
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

DrawerPortal

Prop类型默认值
containerA parent element to render the portal element into.HTMLElement | ShadowRoot | RefObject<HTMLElement | ShadowRoot | null>
keepMountedWhether to keep the portal mounted in the DOM while the popup is hidden.booleanfalse
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

DrawerTitle

Prop类型默认值
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

DrawerTrigger

Prop类型默认值
handleA handle to associate the trigger with a drawer. Can be created with the Drawer.createHandle() method.DrawerPrimitive.Handle<unknown>
idID of the trigger. In addition to being forwarded to the rendered element, it is also used to specify the active trigger for drawers in controlled mode (with the Drawer.Root `triggerId` prop).string
nativeButtonWhether the component renders a native `<button>` element when replacing it via the `render` prop. Set to `false` if the rendered element is not a button (for example, `<div>`).booleantrue
payloadA payload to pass to the drawer when it is opened.{}
renderAllows you to replace the component's HTML element with a different tag, or compose it with another component.ReactElement | ((props, state) => ReactElement)

Also takes the attributes of its element.

改动

  • 0.6.0 · Breaking ChangesProps that no longer change anything are removed: Badge uppercase; Checkbox size, variant, color; Status pulse, attention (status never animates); Switch / SwitchThumb size; SidebarItem variant; SidebarItemCount / SidebarItemIndicator variant and appearance (the count is always the pink sticker, the indicator the yellow @); NotificationCenterActionButton variant; DrawerContent width (one-go forms use the wide Dialog); AppRailItemIndicator attention; AppRailItemAttention children; Steps size, track, renderIndicator, renderTraveler; FilePreviewBadge type, color. Remove them at call sites.