Antares
Components

Drawer

An overlay panel that slides in from a screen edge, supporting sidebar navigation and bottom sheets.

Features

  • Slides in from any screen edge (top, bottom, left, right) with spring animation
  • Snap points with drag gestures and keyboard control
  • Numeric and string snap points (120, '50%', '200px') resolved against drawer size
  • Automatic RTL flipping for left/right placement
  • Focus trapping, backdrop dismiss, scroll lock via RAC Modal
  • prefers-reduced-motion support

Installation

npm install --save @godaddy/antares

Props

NameTypeDefault
placementDrawerPlacement

Edge the drawer slides in from. left/right flip in RTL.

animate?boolean | undefinedtrue

Enable/disable animation.

snapPoints?readonly DrawerSnapPoint[] | undefined

Snap point positions. Number = px, string = CSS length ('50%', '200px'). Percentages resolve against the drawer's rendered size. The largest resolved snap sets the drawer's minimum size along its constrained axis.

activeSnapPoint?DrawerSnapPoint | undefined

Current active snap point (controlled).

defaultActiveSnapPoint?DrawerSnapPoint | undefined

Initial active snap point (uncontrolled).

onSnapPointChange?((snapPoint: DrawerSnapPoint) => void) | undefined

Called when snap point changes.

snapLabels?string[] | undefined

Human-readable labels for snap points (for aria-valuetext).

maxSize?string | number | undefined'min(80vw, 400px)' for left/right, 'calc(100vh - 80px)' for top/bottom

Max size of the drawer along its constrained axis. Accepts CSS values.

showCloseButton?boolean | undefinedtrue for top/bottom, false for left/right

Show built-in X close button.

closeLabel?string | undefined'Close'

Accessible label for the close button.

snapSliderLabel?string | undefined'Resize drawer'

Accessible label for the snap-point resize slider.

title?React.ReactNode

Accessible title rendered as a heading inside the drawer.

id?string | undefined

Forwarded to the inner Dialog for aria-controls linkage.

children?React.ReactNode

Content to render inside the drawer.

className?string | undefined

Additional CSS class for the inner dialog content region.

shouldCloseOnInteractOutside?((element: Element) => boolean) | undefined

When user interacts with the argument element outside of the overlay ref, return true if onClose should be called. This gives you a chance to filter out interaction with elements that should not dismiss the overlay. By default, onClose will always be called on interaction outside the overlay ref.

isEntering?boolean | undefined

Whether the modal is currently performing an entry animation.

isExiting?boolean | undefined

Whether the modal is currently performing an exit animation.

UNSTABLE_portalContainer?Element | undefineddocument.body

The container element in which the overlay portal will be placed. This may have unknown behavior depending on where it is portalled to.

isDismissable?boolean | undefinedfalse

Whether to close the modal when the user interacts outside it.

isKeyboardDismissDisabled?boolean | undefinedfalse

Whether pressing the escape key to close the modal should be disabled.

isOpen?boolean | undefined

Whether the overlay is open by default (controlled).

defaultOpen?boolean | undefined

Whether the overlay is open by default (uncontrolled).

onOpenChange?((isOpen: boolean) => void) | undefined

Handler that is called when the overlay's open state changes.

render?DOMRenderFunction<"div", ModalRenderProps> | undefined

Overrides the default DOM element with a custom render function. This allows rendering existing components with built-in styles and behaviors such as router links, animation libraries, and pre-styled components. Requirements: - You must render the expected element type (e.g. if `<button>` is expected, you cannot render an `<a>`). - Only a single root DOM element can be rendered (no fragments). - You must pass through props and ref to the underlying DOM element, merging with your own prop as appropriate.

slot?string | null | undefined

A slot name for the component. Slots allow the component to receive props from a parent component. An explicit `null` value indicates that the local props completely override all props received from a parent.

Sub-components

DrawerTrigger

Wraps a trigger button and a <Drawer>. Handles open state and aria-controls linkage automatically. Based on RAC DialogTrigger.

DrawerHandle

Visual drag handle for touch gestures. Renders a grab bar with a 44px touch target (24px for pointer:fine). When inside a <Drawer> with snapPoints, the handle becomes interactive and drag-to-snap activates automatically. Without snapPoints, the handle renders as a decorative affordance only.

PropTypeDescription
classNamestringAdditional CSS class

Examples

Basic Usage

Use <DrawerTrigger> to wrap a trigger button and a <Drawer>. The drawer opens when the trigger is pressed.

import { Drawer, DrawerTrigger, Button, Text } from '@godaddy/antares';export function DefaultExample() {  return (    <DrawerTrigger>      <Button variant="primary">Open drawer</Button>      <Drawer placement="right" isDismissable title="Settings">        <Text>Drawer content goes here.</Text>      </Drawer>    </DrawerTrigger>  );}

Bottom Sheet

Use placement="bottom" with controlled state for a bottom sheet pattern.

import { useState } from 'react';import { Drawer, Button, Text } from '@godaddy/antares';export function BottomSheetExample() {  const [open, setOpen] = useState(false);  return (    <>      <Button variant="primary" onPress={() => setOpen(true)}>        Open bottom sheet      </Button>      <Drawer placement="bottom" isOpen={open} onOpenChange={setOpen} isDismissable title="Options">        <Text>Bottom sheet content with close button.</Text>      </Drawer>    </>  );}

Snap Points

Use snapPoints with <DrawerHandle /> for a resizable bottom sheet with touch gesture support.

import { useState } from 'react';import { Drawer, DrawerHandle, Button, Text } from '@godaddy/antares';export function SnapPointsExample() {  const [open, setOpen] = useState(false);  return (    <>      <Button variant="primary" onPress={() => setOpen(true)}>        Open snap sheet      </Button>      <Drawer        placement="bottom"        isOpen={open}        onOpenChange={setOpen}        isDismissable        snapPoints={[148, 355]}        defaultActiveSnapPoint={148}        snapLabels={['Collapsed', 'Expanded']}        title="Snap Sheet"      >        <DrawerHandle />        <Text>Drag the handle or use keyboard to resize.</Text>      </Drawer>    </>  );}

Percentage Snap Points

Use string snap points like '30%' and '60%' for sizes relative to the drawer's rendered dimensions.

import { useState } from 'react';import { Drawer, DrawerHandle, Button, Text } from '@godaddy/antares';export function PercentSnapPointsExample() {  const [open, setOpen] = useState(false);  return (    <>      <Button variant="primary" onPress={() => setOpen(true)}>        Open percent snap      </Button>      <Drawer        placement="bottom"        isOpen={open}        onOpenChange={setOpen}        isDismissable        snapPoints={['30%', '60%']}        defaultActiveSnapPoint="30%"        snapLabels={['Collapsed', 'Expanded']}        title="Percent Snap"      >        <DrawerHandle />        <Text>Snap points use percentage of drawer size.</Text>      </Drawer>    </>  );}

Controlled Snap Points

Use activeSnapPoint and onSnapPointChange for full control over the active snap point.

Active snap: 355px
import { useState } from 'react';import { Drawer, DrawerHandle, Button, Flex, Text, type DrawerSnapPoint } from '@godaddy/antares';const SNAP_POINTS = [148, 355];export function ControlledSnapExample() {  const [open, setOpen] = useState(false);  const [snap, setSnap] = useState<DrawerSnapPoint>(355);  return (    <>      <Button variant="primary" onPress={() => setOpen(true)}>        Open controlled snap      </Button>      <Text>Active snap: {snap}px</Text>      <Drawer        placement="bottom"        isOpen={open}        onOpenChange={setOpen}        isDismissable        snapPoints={SNAP_POINTS}        activeSnapPoint={snap}        onSnapPointChange={setSnap}        snapLabels={['Collapsed', 'Expanded']}        title="Controlled Snap"      >        <DrawerHandle />        <Flex gap="md">          <Button variant="primary" onPress={() => setSnap(148)}>            Collapse          </Button>          <Button variant="primary" onPress={() => setSnap(355)}>            Expand          </Button>        </Flex>      </Drawer>    </>  );}

Nested Popover

Popovers rendered inside a drawer stay open without dismissing the drawer.

import { useState } from 'react';import { Drawer, Button, Text, Popover, PopoverTrigger } from '@godaddy/antares';export function NestedPopoverExample() {  const [open, setOpen] = useState(false);  return (    <>      <Button variant="primary" onPress={() => setOpen(true)}>        Open drawer      </Button>      <Drawer placement="right" isOpen={open} onOpenChange={setOpen} isDismissable title="Drawer with Popover">        <PopoverTrigger>          <Button variant="primary">Open popover</Button>          <Popover>            <Text>Popover inside drawer</Text>          </Popover>        </PopoverTrigger>      </Drawer>    </>  );}

Accessibility

Keyboard

KeyAction
EscapeClose the drawer (when isDismissable or not isKeyboardDismissDisabled)
TabMove focus within the drawer (focus is trapped)
Arrow Up / RightMove to next snap point (when snap slider is focused)
Arrow Down / LeftMove to previous snap point
HomeJump to first snap point
EndJump to last snap point

In RTL, horizontal arrow key direction is reversed for the snap slider.

ARIA

  • role="dialog" with aria-modal="true" on the drawer panel (via RAC Modal)
  • Dialog accessible name via title (renders a heading) or aria-label (for titleless drawers)
  • aria-label="Close" on the built-in close button
  • role="slider" on the snap point keyboard control with aria-valuenow, aria-valuemin, aria-valuemax, aria-valuetext
  • aria-hidden="true" on the visual drag handle (decorative)

Best Practices

  • Always provide a title or aria-label prop so the dialog has an accessible name.
  • Use isDismissable to allow closing via backdrop click.
  • Don't nest drawers.

On this page