Alternatives
- Side drawer — Used for ligh forms and information content to support the user's main activity without disrupting it.
- Product menu — Used to display a collapsable menu as an overlay.
BottomDrawerController
Each usage of a BottomDrawer
needs to be wrapped in a BottomDrawerController
. Generally, the usage of BottomDrawerController WON'T look like it does in the live examples. The live examples are illustrating what you can do with them. The actual usage of the bottom drawers will look more like the code below. What's important to note here is that the BottomDrawerController
is outside of the component that renders the drawer. This is so that state is cleared out of the drawer when it's closed.
Good ✅
const SomeDrawer = () => { return <BottomDrawer>{/** ... content goes here */}</BottomDrawer>;};return ( <BottomDrawerController> <SomeDrawer /> </BottomDrawerController>);
Bad ❌
return ( <BottomDrawerController> <BottomDrawer>{/** ... content goes here */}</BottomDrawer> </BottomDrawerController>);
Opening and closing a bottom drawer
The consumer is responsible for tracking and updating open/closed states. BottomDrawerController
takes an isOpen
prop to track the open/closed state of the drawer and an onClose
prop which is used when the user presses Escape
.
const [isOpen, toggleIsOpen] = React.useReducer((bool) => !bool, false);return ( <BottomDrawerController isOpen={isOpen} onClose={toggleIsOpen}> {/** ... bottom drawer stuff goes here */} </BottomDrawerController>);
Why can't I conditionally render?
We need the BottomDrawerController
component so that we can do an exit transition for the drawer. If you just did isOpen && <MyDrawer />
, the drawer would unmount immediately so it wouldn't be possible to have an exit transition.
BottomDrawer
A bottom drawer is a modal dialog that slides in from the bottom of the screen. They are used to display helpful information and large forms (including datatables) that support the users main activity without interrupting it.
There can only be one instance of a bottom drawer open at a time. Bottom drawers don't stack like side drawers. Bottom drawers, side drawers and product menus cannot be used together i.e a bottom drawer should never have a side drawer or a product menu inside it and vice versa.
Height
The bottom drawer supports 3 different types of heights through the height
prop:
- half: The drawer fills 50% of the height of the viewport.
<BottomDrawer height="half"></BottomDrawer>
- full: The drawer fills the entier height of the viewport.
<BottomDrawer height="full"></BottomDrawer>
- number: Custom height provided by the consumer. This option is there to support edge cases and should be seldom used.
<BottomDrawer height={800}></BottomDrawer>
A bottom drawer should maintain the same height throughout it's life cycle as having the content jump around can be a frustrating experience for the user.
Focus
By default the bottom drawer will focus the outter most element on open. It's possible to focus a different element instead by using the initialFocusRef
prop. Check out the full example here.
Accessibility props
To maintain accessibility in scenarios when opting for a custom implementation of header and/or title, the aria-label
and aria-labelledby
props can be used. Check out the examples below for more details:
Composition
BottomDrawer
content is composed using a combination of primitives and custom implementations. The primitives cover the most common use cases and where they aren't sufficient, it's possible to opt out in favor of a custom implementation.
Bottom drawer primitives follow a component as namespace strategy i.e all the primitives relating to BottomDrawer
are exposed as BottomDrawer.SomePrimitive
. Below is a list of all the primitives, their purpose and some sample code.
The code samples are meant to convey the general idea, for concrete examples on composition using primitives and custom implementations check out the patterns section.
BottomDrawer.Header
Used to display the title and some optional subtext or actions. To be used inside BottomDrawer
or BottomDrawer.Form
.
<BottomDrawer> <BottomDrawer.Header> {/** ... header content goes here **/} </BottomDrawer.Header></BottomDrawer>
OR
<BottomDrawer> <BottomDrawer.Form> <BottomDrawer.Header> {/** ... header content goes here **/} </BottomDrawer.Header> </BottomDrawer.Form></BottomDrawer>
Behaviour and styling
This is a container component that provides horizontal and vertical padding and renders children as flex row.
Variance, when and how to opt out?
The header is a convenience layout primitive and hence can be easily opted out of. However, there should be little reason to opt out of it entirely. In case of a full opt out, it's the consumers responsibility to provide proper padding and spacing between elements.
BottomDrawer.Title
Title of the drawer. To be used inside BottomDrawer.Header
or custom header implementation.
<BottomDrawer.Header> <BottomDrawer.Title>Title</BottomDrawer.Title></BottomDrawer.Header>
Variance, when and how to opt out?
The title primitive is a level 3 heading
under the hood and it should suffice in most cases. It exists so that consumers don't have to guess the heading level every time. The title should be fairly consistent across most drawers and hence warrant little reason for opting out.
Custom implementations of title must be connected to the BottomDrawer
by using the aria-labelledby
prop to support proper accessibility.
<BottomDrawer aria-labelledby="custom-title"> <BottomDrawer.Header> <Heading id='custom-title' level="2">Large title</Heading> </BottomDrawer.Header> <BottomDrawer>
BottomDrawer.Actions
Container for the primary CTA of the drawer. To be used inside BottomDrawer.Header
.
<BottomDrawer> <BottomDrawer.Header> <BottomDrawer.Actions>{/** ... actions go here */}</BottomDrawer.Actions> </BottomDrawer.Header></BottomDrawer>
Behaviour and styles
This is a container component that renders children as flex row so it seamlessly accepts buttons as children.
<BottomDrawer> <BottomDrawer.Header> <BottomDrawer.Actions> <Button label="Cancel" variant="text" onClick={() => {}} /> <Button label="Done" onClick={() => {}} /> </BottomDrawer.Actions> </BottomDrawer.Header></BottomDrawer>
Variance, when and how to opt out?
This is a convenience layout primitive and hence can be opted out of easily. However, there should be little reason to entirely opt out of it entirely.
When rolling a customer implementation, it's the consumers responsibility to provide proper padding and spacing between elements.
Check out this sample on how to opt out of the primitive.
BottomDrawer.Body
Container for the main drawer content. To be used inside BottomDrawer
or BottomDrawer.Form
<BottomDrawer> <BottomDrawer.Body> {/** ... bottom drawer content goes here */} </BottomDrawer.Body></BottomDrawer>
OR
<BottomDrawer> <BottomDrawer.Form> <BottomDrawer.Body> {/** ... scrollable content goes here */} </BottomDrawer.Body> </BottomDrawer.Form></BottomDrawer>
Behaviour and styles
This is a container component that contains the main drawer content.
It does not apply any padding, it's the consumer's responsibility to apply the correct padding to the body. In most cases, a Box
with padding will suffice. Be sure to align the horizontal padding with the header content.
<BottomDrawer> <BottomDrawer.Body> <Box paddingX="xlarge" paddingY="large"> {/** ... we now have padding */} </Box> </BottomDrawer.Body></BottomDrawer>
Overflowing content is scrolled.
Variance, when and how to opt out?
Although the body content itself may have a lot of variance, it is not recommended to opt out of using BottomDrawer.Body
as it provides some critical scrolling and border behaviours. These behaviours are difficult to implement correctly and hence one should have very good reasons for opting out of this primitive.
BottomDrawer.Form
A wrapper that connects the submit action in the footer to the form.
All of the drawer content and primitives must be wrapped inside BottomDrawer.Form
if the drawer is a form, otherwise your submit action will not work correctly.
Note: The
submit
function of the form must be passed to theonSubmit
prop ofBottomDrawer.Form
and there must be abutton
oftype="submit"
present in the actions.
<BottomDrawer> <BottomDrawer.Form onSubmit={() => {}}> <BottomDrawer.Header> {/** ... header content goes here */} <BottomDrawer.Actions> <Button type="submit" label="Done" /> </BottomDrawer.Actions> </BottomDrawer.Header> <BottomDrawer.Body>{/** ... body content goes here */}</BottomDrawer.Body> </BottomDrawer></BottomDrawer>
If the drawer is not a form then this component must not be used.
Receipes
Below are some of the most common use cases of bottom drawers and how to compose them using primitives and custom code.
Basic form
A simple form to add/edit an entity and then return to the main flow.
Content
Simple supporting text that offers an explanation of the users main flow.
Custom header actions
Sample code for opting out of the BottomDrawer.Actions
primitive.
Custom title
When using a custom title instead of BottomDrawer.Title
, the aria-labelledby
prop must be
provided to BottomDrawer
.
Simple supporting text that offers an explanation of the users main flow.
Loading states
When display a loading state on initial load, the aria-label
prop must be provided to
BottomDrawer
till the title is rendered.
Focus a different element on open
By default, the outter most element will be focused when the drawer opens. You can provide an initialFocusRef
to focus a different element instead.