Overlays
Drawer
Display a modal dialog that slides out from the edge of the screen.
Import
tsximport { Drawer } from "@hope-ui/core";
tsximport { Drawer } from "@hope-ui/core";
- Drawer: The wrapper that provides props, state, and context to its children.
- Drawer.Overlay: The backdrop behind the drawer.
- Drawer.Content: The drawer itself.
- Drawer.CloseButton: A button to close the drawer.
- Drawer.Heading: An optional heading for the drawer.
- Drawer.Description: An optional description for the drawer.
Usage
tsxfunction BasicUsageExample() {const [isOpen, setIsOpen] = createSignal(false);return (<><Button onClick={() => setIsOpen(true)}>Open Drawer</Button><Drawer isOpen={isOpen()} onClose={() => setIsOpen(false)}><Drawer.Overlay /><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><p>The content of the Drawer.</p></Drawer.Content></Drawer></>);}
tsxfunction BasicUsageExample() {const [isOpen, setIsOpen] = createSignal(false);return (<><Button onClick={() => setIsOpen(true)}>Open Drawer</Button><Drawer isOpen={isOpen()} onClose={() => setIsOpen(false)}><Drawer.Overlay /><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><p>The content of the Drawer.</p></Drawer.Content></Drawer></>);}
Initial focus
When the drawer opens, focus is automatically set on the first focusable element in Drawer.Content.
Use the data-autofocus attribute on any element to mark it as the initial focus element.
tsxfunction InitialFocusExample() {const [isOpen, setIsOpen] = createSignal(false);return (<><Button onClick={() => setIsOpen(true)}>Open Drawer</Button><Drawer isOpen={isOpen()} onClose={() => setIsOpen(false)}><Drawer.Overlay /><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><Text mb={4}>The content of the Drawer.</Text><HStack justifyContent="flex-end" spacing={4}><Button data-autofocus _focus={{ color: "red" }}>Action</Button></HStack></Drawer.Content></Drawer></>);}
tsxfunction InitialFocusExample() {const [isOpen, setIsOpen] = createSignal(false);return (<><Button onClick={() => setIsOpen(true)}>Open Drawer</Button><Drawer isOpen={isOpen()} onClose={() => setIsOpen(false)}><Drawer.Overlay /><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><Text mb={4}>The content of the Drawer.</Text><HStack justifyContent="flex-end" spacing={4}><Button data-autofocus _focus={{ color: "red" }}>Action</Button></HStack></Drawer.Content></Drawer></>);}
You can also use a different css selector by using the initialFocusSelector prop.
tsxfunction CustomInitialFocusExample() {const [isOpen, setIsOpen] = createSignal(false);return (<><Button onClick={() => setIsOpen(true)}>Open Drawer</Button><DrawerisOpen={isOpen()}onClose={() => setIsOpen(false)}initialFocusSelector="#initial-focus"><Drawer.Overlay /><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><Text mb={4}>The content of the Drawer.</Text><HStack justifyContent="flex-end" spacing={4}><Button id="initial-focus" _focus={{ color: "red" }}>Action</Button></HStack></Drawer.Content></Drawer></>);}
tsxfunction CustomInitialFocusExample() {const [isOpen, setIsOpen] = createSignal(false);return (<><Button onClick={() => setIsOpen(true)}>Open Drawer</Button><DrawerisOpen={isOpen()}onClose={() => setIsOpen(false)}initialFocusSelector="#initial-focus"><Drawer.Overlay /><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><Text mb={4}>The content of the Drawer.</Text><HStack justifyContent="flex-end" spacing={4}><Button id="initial-focus" _focus={{ color: "red" }}>Action</Button></HStack></Drawer.Content></Drawer></>);}
If the drawer contains no focusable element, focus is set to the Drawer.Content itself.
Restore focus
When the drawer closes, focus is set back to the trigger element. Use the restoreFocusSelector prop and pass in a css selector to target another element.
tsxfunction CustomRestoreFocusExample() {const [isOpen, setIsOpen] = createSignal(false);return (<HStack spacing={4}><Button onClick={() => setIsOpen(true)}>Open Drawer</Button><DrawerisOpen={isOpen()}onClose={() => setIsOpen(false)}restoreFocusSelector="[data-finalfocus]"><Drawer.Overlay /><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><p>The content of the Drawer.</p></Drawer.Content></Drawer><Button data-finalfocus _focus={{ color: "red" }}>Final focus element</Button></HStack>);}
tsxfunction CustomRestoreFocusExample() {const [isOpen, setIsOpen] = createSignal(false);return (<HStack spacing={4}><Button onClick={() => setIsOpen(true)}>Open Drawer</Button><DrawerisOpen={isOpen()}onClose={() => setIsOpen(false)}restoreFocusSelector="[data-finalfocus]"><Drawer.Overlay /><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><p>The content of the Drawer.</p></Drawer.Content></Drawer><Button data-finalfocus _focus={{ color: "red" }}>Final focus element</Button></HStack>);}
Focus trap
By default, focus is lock inside the drawer for accessibility reason. Set the trapFocus prop to false if you want to disable this behavior.
tsxfunction DisableFocusTrapExample() {const [isOpen, setIsOpen] = createSignal(false);return (<><Button onClick={() => setIsOpen(true)}>Open Drawer</Button><Drawer isOpen={isOpen()} onClose={() => setIsOpen(false)} trapFocus={false}><Drawer.Overlay /><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><p>The content of the Drawer.</p></Drawer.Content></Drawer></>);}
tsxfunction DisableFocusTrapExample() {const [isOpen, setIsOpen] = createSignal(false);return (<><Button onClick={() => setIsOpen(true)}>Open Drawer</Button><Drawer isOpen={isOpen()} onClose={() => setIsOpen(false)} trapFocus={false}><Drawer.Overlay /><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><p>The content of the Drawer.</p></Drawer.Content></Drawer></>);}
Prevent scroll
For accessibility reason scrolling on the main document behind the drawer is blocked by default. Set the preventScroll prop to false to allow scrolling behind the drawer.
tsxfunction DisablePreventScrollExample() {const [isOpen, setIsOpen] = createSignal(false);return (<><Button onClick={() => setIsOpen(true)}>Open Drawer</Button><Drawer isOpen={isOpen()} onClose={() => setIsOpen(false)} preventScroll={false}><Drawer.Overlay /><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><p>The content of the Drawer.</p></Drawer.Content></Drawer></>);}
tsxfunction DisablePreventScrollExample() {const [isOpen, setIsOpen] = createSignal(false);return (<><Button onClick={() => setIsOpen(true)}>Open Drawer</Button><Drawer isOpen={isOpen()} onClose={() => setIsOpen(false)} preventScroll={false}><Drawer.Overlay /><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><p>The content of the Drawer.</p></Drawer.Content></Drawer></>);}
Heading and description
Use Drawer.Heading to add an accessible heading to the drawer, it renders an h2 by default.
Similarly Drawer.Description is used add an accessible description and renders a p by default.
Under the hood, Hope UI will set the correct aria-labelledby and aria-describedby attributes.
tsxfunction HeadingAndDescriptionExample() {const [isOpen, setIsOpen] = createSignal(false);return (<><Button onClick={() => setIsOpen(true)}>Open Drawer</Button><Drawer isOpen={isOpen()} onClose={() => setIsOpen(false)}><Drawer.Overlay /><Drawer.Content p={4}><HStack alignItems="flex-start" justifyContent="space-between" mb={4}><VStack alignItems="flex-start"><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.DescriptionfontSize="sm"color={{ light: "neutral.600", dark: "neutral.300" }}mb={4}>Description</Drawer.Description></VStack><Drawer.CloseButton /></HStack><p>The content of the Drawer.</p></Drawer.Content></Drawer></>);}
tsxfunction HeadingAndDescriptionExample() {const [isOpen, setIsOpen] = createSignal(false);return (<><Button onClick={() => setIsOpen(true)}>Open Drawer</Button><Drawer isOpen={isOpen()} onClose={() => setIsOpen(false)}><Drawer.Overlay /><Drawer.Content p={4}><HStack alignItems="flex-start" justifyContent="space-between" mb={4}><VStack alignItems="flex-start"><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.DescriptionfontSize="sm"color={{ light: "neutral.600", dark: "neutral.300" }}mb={4}>Description</Drawer.Description></VStack><Drawer.CloseButton /></HStack><p>The content of the Drawer.</p></Drawer.Content></Drawer></>);}
Open/close transitions
The contentTransitionOptions and overlayTransitionOptions props allows to customize the drawer content and overlay open/close transitions. You can use predefined transitions or create custom ones.
tsxfunction TransitionExample() {const [isOpen, setIsOpen] = createSignal(false);return (<><Button onClick={() => setIsOpen(true)}>Open Drawer</Button><DrawerisOpen={isOpen()}onClose={() => setIsOpen(false)}contentTransitionOptions={{transition: "slide-up",duration: 400,exitDuration: 250,easing: "ease-out",exitEasing: "ease-in",}}><Drawer.Overlay /><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><p>The content of the Drawer.</p></Drawer.Content></Drawer></>);}
tsxfunction TransitionExample() {const [isOpen, setIsOpen] = createSignal(false);return (<><Button onClick={() => setIsOpen(true)}>Open Drawer</Button><DrawerisOpen={isOpen()}onClose={() => setIsOpen(false)}contentTransitionOptions={{transition: "slide-up",duration: 400,exitDuration: 250,easing: "ease-out",exitEasing: "ease-in",}}><Drawer.Overlay /><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><p>The content of the Drawer.</p></Drawer.Content></Drawer></>);}
To learn more about the transition API check out the createTransition documentation.
Placement
To change the edge where the Drawer should appear, use the placement prop.
tsxfunction PlacementExample() {const [placement, setPlacement] = createSignal<DrawerProps["placement"]>("right");const [isOpen, setIsOpen] = createSignal(false);const handleClick = (newPlacement: DrawerProps["placement"]) => {setPlacement(newPlacement);setIsOpen(true);};const placements: Array<DrawerProps["placement"]> = ["top", "right", "bottom", "left"];return (<><HStack spacing={4}><For each={placements}>{placement => <Button onClick={() => handleClick(placement)}>{placement}</Button>}</For></HStack><Drawer isOpen={isOpen()} onClose={() => setIsOpen(false)} placement={placement()}><Drawer.Overlay /><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><p>The content of the Drawer.</p></Drawer.Content></Drawer></>);}
tsxfunction PlacementExample() {const [placement, setPlacement] = createSignal<DrawerProps["placement"]>("right");const [isOpen, setIsOpen] = createSignal(false);const handleClick = (newPlacement: DrawerProps["placement"]) => {setPlacement(newPlacement);setIsOpen(true);};const placements: Array<DrawerProps["placement"]> = ["top", "right", "bottom", "left"];return (<><HStack spacing={4}><For each={placements}>{placement => <Button onClick={() => handleClick(placement)}>{placement}</Button>}</For></HStack><Drawer isOpen={isOpen()} onClose={() => setIsOpen(false)} placement={placement()}><Drawer.Overlay /><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><p>The content of the Drawer.</p></Drawer.Content></Drawer></>);}
Size
You can change the drawer width by setting the size prop.
tsxfunction SizeExample() {const [size, setSize] = createSignal<DrawerProps["size"]>("md");const [isOpen, setIsOpen] = createSignal(false);const handleClick = (newSize: DrawerProps["size"]) => {setSize(newSize);setIsOpen(true);};const sizes: Array<DrawerProps["size"]> = ["xs", "sm", "md", "lg", "xl", "full"];return (<><HStack spacing={4}><For each={sizes}>{size => <Button onClick={() => handleClick(size)}>{size}</Button>}</For></HStack><Drawer isOpen={isOpen()} onClose={() => setIsOpen(false)} size={size()}><Show when={size() !== "full"}><Drawer.Overlay /></Show><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><p>The content of the Drawer.</p></Drawer.Content></Drawer></>);}
tsxfunction SizeExample() {const [size, setSize] = createSignal<DrawerProps["size"]>("md");const [isOpen, setIsOpen] = createSignal(false);const handleClick = (newSize: DrawerProps["size"]) => {setSize(newSize);setIsOpen(true);};const sizes: Array<DrawerProps["size"]> = ["xs", "sm", "md", "lg", "xl", "full"];return (<><HStack spacing={4}><For each={sizes}>{size => <Button onClick={() => handleClick(size)}>{size}</Button>}</For></HStack><Drawer isOpen={isOpen()} onClose={() => setIsOpen(false)} size={size()}><Show when={size() !== "full"}><Drawer.Overlay /></Show><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><p>The content of the Drawer.</p></Drawer.Content></Drawer></>);}
size prop has no effect when the placement is top or bottom.Styling the backdrop
You can use style props and sx on the Drawer.Overlay component to customize the drawer backdrop.
tsxfunction CustomBackdropExample() {const [isOpen, setIsOpen] = createSignal(false);return (<><Button onClick={() => setIsOpen(true)}>Open Drawer</Button><Drawer isOpen={isOpen()} onClose={() => setIsOpen(false)}><Drawer.Overlaysx={{bg: "blackAlpha.300",backdropFilter: "blur(10px) hue-rotate(90deg)",}}/><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><p>The content of the Drawer.</p></Drawer.Content></Drawer></>);}
tsxfunction CustomBackdropExample() {const [isOpen, setIsOpen] = createSignal(false);return (<><Button onClick={() => setIsOpen(true)}>Open Drawer</Button><Drawer isOpen={isOpen()} onClose={() => setIsOpen(false)}><Drawer.Overlaysx={{bg: "blackAlpha.300",backdropFilter: "blur(10px) hue-rotate(90deg)",}}/><Drawer.Content p={4}><HStack justifyContent="space-between" mb={4}><Drawer.Heading fontWeight="semibold">Title</Drawer.Heading><Drawer.CloseButton /></HStack><p>The content of the Drawer.</p></Drawer.Content></Drawer></>);}
Please be aware that not every browser supports the backdrop-filter CSS property used in the
example above.
Accessibility
ARIA roles and attributes
- The
Drawer.Contenthasaria-drawerset totrue. - The
Drawer.Contenthasaria-labelledbyset to theidof theDrawer.Heading, if any. - The
Drawer.Contenthasaria-describedbyset to theidof theDrawer.Description, if any.
Keyboard support and focus management
- If the
trapFocusprop istrue, focus is locked within the drawer. - If the
initialFocusSelectorprop is set, focus is moved to the matching element, otherwise focus moves to theDrawer.Content. - When the drawer closes, focus returns to the trigger element unless another focusable element takes focus.
- If the
closeOnOverlayClickprop istrue, clicking on the overlay closes the drawer. - When focus is within the
Drawer.ContentandcloseOnEscprop istrue, pressing esc closes the drawer. - If the
preventScrollprop istrue, scrolling is blocked on the elements behind the drawer.
Theming
Use the DrawerTheme type to override Drawer styles and default props when extending Hope UI theme.
tsximport { extendTheme, DrawerTheme } from "@hope-ui/core";const theme = extendTheme({components: {Drawer: {// ...overrides} as DrawerTheme,},});
tsximport { extendTheme, DrawerTheme } from "@hope-ui/core";const theme = extendTheme({components: {Drawer: {// ...overrides} as DrawerTheme,},});
Component parts
| Name | Static selector | Description |
|---|---|---|
| root | hope-Drawer-root | Root element |
| content | hope-Drawer-content | Drawer content |
| overlay | hope-Drawer-overlay | Drawer overlay/backdrop |
| heading | hope-Drawer-heading | Drawer heading |
| description | hope-Drawer-description | Drawer description |
Props
Drawer
| Name | Type | Description | Default value |
|---|---|---|---|
| isOpen* | boolean | Whether the drawer should be shown. | |
| onClose* | () => void | A function that will be called to close the drawer. | |
| id | string | The id of the drawer content. | |
| size | "xs" | "sm" | "md" | "lg" | "xl" | "full" | The size of the drawer. | |
| placement | "top" | "right" | "bottom" | "left" | The placement of the drawer | right |
| contentTransitionOptions | TransitionOptions | Options passed to the drawer content transition. | |
| overlayTransitionOptions | TransitionOptions | Options passed to the overlay transition. | |
| closeOnOverlayClick | boolean | Whether the drawer should close when the overlay is clicked. | true |
| closeOnEsc | boolean | Whether the drawer should close when the user hit the Esc key. | true |
| preventScroll | boolean | Whether the scroll should be disabled on the body when the drawer opens. | true |
| trapFocus | boolean | Whether the focus will be locked into the drawer. | true |
| initialFocusSelector | string | A query selector to retrieve the element that should receive focus once the drawer opens. | |
| restoreFocusSelector | string | A query selector to retrieve the element that should receive focus once the drawer closes. | |
| onOverlayClick | () => void | A function that will be called when the overlay is clicked. | |
| onEscKeyDown | () => void | A function that will be called when the Esc key is pressed and focus is within the drawer. |
Other components props
Drawer.CloseButtonrenders a CloseButton and supports all of its props.Drawer.Overlay,Drawer.Content,Drawer.HeadingandDrawer.Descriptionsupports commons Hope UI props (style props,sxandas).