A drawer without a backdrop allows a user to continue to interact with the rest of the page. This can be useful when you wants users to have the ability to interact with the page and the drawer at the same time.
<State initial={true}>
{([open, setOpen]) => (
<div>
<Button onClick={() => setOpen(!open)}>Toggle Drawer</Button>
<Drawer
header="Drawer Header"
open={open}
onClose={() => setOpen(false)}
portal={false}
focusTrapOptions={{ disabled: true }}
>
You can always set up this campaign at a later time.
</Drawer>
</div>
)}
</State>
Over all Content
<State initial={false}>
{([open, setOpen]) => (
<div>
<Button onClick={() => setOpen(!open)}>Toggle Drawer</Button>
<Drawer
header="Drawer Header"
open={open}
onClose={() => setOpen(false)}
>
You can always set up this campaign at a later time.
</Drawer>
</div>
)}
</State>
Closing a Drawer
Drawers without a backdrop are closed through an explicit control. This is typically through clicking a ×, a Drawer footer button, or a labeled button on the page. Clicking outside the drawer should not close it.
Drawer with a Backdrop
A backdrop restricts user interaction to just the Drawer. This is useful when a task is focused. Drawers with backdrops close similar to the Modal component.
<State initial={false}>
{([open, setOpen]) => (
<div>
<Button onClick={() => setOpen(true)}>Drawer with Backdrop</Button>
<Drawer
header="Drawer Header"
open={open}
onClose={() => setOpen(false)}
backdrop
>
You can always set up this campaign at a later time.
</Drawer>
</div>
)}
</State>
Non-closable Drawers
When a workflow is mandatory, the close functionalities — clicking the backdrop or × — can be removed. A user would have to complete a task to proceed.
<State initial={false}>
{([open, setOpen]) => (
<div>
<Button onClick={() => setOpen(true)}>Non-closable Drawer</Button>
<Drawer
header="Drawer Header"
open={open}
backdrop
footer={<Button primary onClick={() => setOpen(false)}>I Accept</Button>}
footerAlign="right"
>
You can always set up this campaign at a later time.
</Drawer>
</div>
)}
</State>
When to use a backdrop
When a Drawer is being used as an informational display, or supplementary to a page task, a backdrop is usually not needed on a Drawer. When a Drawer is an essential part of a workflow, or involves a lot of data manipulation where data conflicts with the page can occur, a backdrop is preferred.
Drawer Sizes
Drawer sizes mimic the sizes of the Modal. Each size has a built in 90% width value and a maximum width value. If a design needs to occupy 100% of the screen, consider using a Takeover.
<State initial={{open: false, width: 'l'}}>
{([state, setState]) => {
const toggleOpen = () => setState(prev => ({...prev, open: !state.open}));
const widthValue = (width) => setState(prev => ({...prev, width: width}));
const widths = ["xs", "s", "m", "l"];
return (
<div>
<ButtonGroup>
{widths.map((width, index) => (
<Button
key={index}
onClick={() => { widthValue(width); toggleOpen(); }}
>
<span className="tt-uppercase m-r-half">{width}</span>
Drawer
</Button>
))}
</ButtonGroup>
<Drawer
header="Drawer Header"
open={state.open}
onClose={() => toggleOpen()}
backdrop
width={state.width}
>
You can always set up this campaign at a later time.
</Drawer>
</div>
)
}}
</State>
Drawer headers typically have a title and a close icon. A header can also be customized to use other Anvil components. Any action element added to the header (such as a Button) should not close the Drawer, and should use a secondary style.
<Drawer
header="Drawer Header without Close"
open
portal={false}
focusTrapOptions={{ disabled: true }}
>
Drawer Content
</Drawer>
<Drawer
header="Drawer Header with Close"
open
portal={false}
onClose={() => {}}
focusTrapOptions={{ disabled: true }}
>
Drawer Content
</Drawer>
<Drawer
header="Drawer Header with a really long name that stretches to two lines"
open
portal={false}
onClose={() => {}}
focusTrapOptions={{ disabled: true }}
>
Drawer Content
</Drawer>
<Drawer
header={
<div>
<Headline>Icemaker Flex Line 1/4</Headline>
<BodyText>Bob's Wacky Wonders – Glendale, CA 01</BodyText>
</div>
}
open
portal={false}
focusTrapOptions={{ disabled: true }}
>
Drawer Content
</Drawer>
<Drawer
header={
<Stack className="w-100" alignItems="center" justifyContent="space-between">
<Headline>Invoice #12345</Headline>
<ButtonGroup>
<Button outline small>Print</Button>
<Button outline small>Download</Button>
</ButtonGroup>
</Stack>
}
open
portal={false}
focusTrapOptions={{ disabled: true }}
>
Drawer Content
</Drawer>
<Drawer
header={
<Stack className="w-100" alignItems="center" justifyContent="space-between">
<Stack spacing={2} alignItems="center">
<Avatar name="Rose Tico" autoColor size="m" />
<BodyText>Rose Tico</BodyText>
</Stack>
<Tag color="warning">Needs Approval</Tag>
</Stack>
}
open
portal={false}
onClose={() => {}}
focusTrapOptions={{ disabled: true }}
>
Drawer Content
</Drawer>
Drawer footers generally provide flow controls such as cancel and submit, or secondary actions for the Drawer. These controls can also close the Drawer when applicable. While it is possible to have multi-step Drawers, it should be avoided. Footers are not required, which can be useful when a Drawer has no actions or when real estate is critical.
<Drawer
header={"Right Aligned Footer"}
open
portal={false}
focusTrapOptions={{ disabled: true }}
footer={
<ButtonGroup>
<Button>Cancel</Button>
<Button primary>Apply and Save</Button>
</ButtonGroup>
}
>
Drawer Content
</Drawer>
<Drawer
header={"Split Button Footer"}
open
portal={false}
footerAlign="space-between"
focusTrapOptions={{ disabled: true }}
footer={
<React.Fragment>
<Button>Cancel</Button>
<Button primary>Apply and Save</Button>
</React.Fragment>
}
>
Drawer Content
</Drawer>
<Drawer
header={"Secondary Actions Footer"}
open
portal={false}
focusTrapOptions={{ disabled: true }}
footer={
<ButtonGroup>
<Button fill='subtle'>View Full Job</Button>
<Button>Close Job</Button>
</ButtonGroup>
}
>
Drawer Content
</Drawer>
<Drawer
header={"Multiple Actions"}
open
portal={false}
focusTrapOptions={{ disabled: true }}
footer={
<ButtonGroup>
<Button primary outline>See Availability</Button>
<Button primary>Update Invoice</Button>
</ButtonGroup>
}
>
Drawer Content
</Drawer>
Drawer Body
The body section is where most of the Drawer content exists.
<Drawer
header="Reschedule Job"
open
onClose={() => {}}
portal={false}
footerAlign="space-between"
footer={
<React.Fragment>
<Button>Cancel</Button>
<ButtonGroup>
<Button outline primary>View Availability</Button>
<Button primary>Reschedule</Button>
</ButtonGroup>
</React.Fragment>
}
width="l"
focusTrapOptions={{ disabled: true }}
>
<Headline size="small" className="m-b-3">Item #123456</Headline>
<Form>
<Form.Group widths="equal">
<Form.Input label="First Name" placeholder="Leia" />
<Form.Input label="Last Name" placeholder="Organa" />
</Form.Group>
<Form.Select
label="Home Planet"
placeholder="Choose Planet"
options={[
{key:1, value: 1, text: 'Alderaan'},
{key:2, value: 2, text: 'Bespin'},
{key:3, value: 3, text: 'Coruscant'},
{key:4, value: 4, text: 'Dagobah'},
{key:5, value: 5, text: 'Hoth'},
{key:6, value: 6, text: 'Kashyyyk'},
{key:7, value: 7, text: 'Naboo',},
{key:8, value: 8, text: 'Tatooine'},
{key:9, value: 9, text: 'Yavin'},
]}
/>
<BodyText size="small" bold className="m-b-2-i m-t-4-i">What is the reason for Rescheduling?</BodyText>
<Form.Field>
<Form.Togglebox
value="value1"
name="Toggle1"
id="Toggle1"
label={"Material"}
/>
<Form.Togglebox
value="value2"
name="Toggle1"
id="Toggle2"
label={"Equipment"}
/>
<Form.Togglebox
value="value3"
name="Toggle1"
id="Toggle3"
label={"Purchase Order"}
/>
<Form.Togglebox
value="value4"
name="Toggle1"
id="Toggle4"
label={"Labor"}
/>
</Form.Field>
</Form>
</Drawer>
Scrollable Content
Drawer header and footers are fixed to the top and bottom of the page. The drawer's body automatically scrolls when not enough space is available.
<State initial={false}>
{([open, setOpen]) => (
<div>
<Button onClick={() => setOpen(true)}>Show Scrolling Drawer</Button>
<Drawer
header="Available Foods"
open={open}
onClose={() => setOpen(false)}
backdrop
footer={
<ButtonGroup>
<Button>Cancel</Button>
<Button primary>Apply and Save</Button>
</ButtonGroup>
}
>
<BodyText>
Chia cillum etsy pabst, in paleo fashion axe.
Gastropub pariatur tilde wayfarers chia laboris.
Salvia cred franzen chambray cillum slow-carb.
Beard letterpress distillery, knausgaard readymade YOLO in.
Aesthetic ea aliqua, blog vaporware kombucha gluten-free art party VHS pariatur cray raclette.
</BodyText>
<BodyText>
Pop-up kale chips four dollar toast gastropub you probably haven't heard of them prism tote bag.
Paleo thundercats godard glossier +1, iceland anim.
Readymade sriracha occaecat, crucifix bicycle rights retro seitan exercitation craft beer kale
chips minim.
Do post-ironic wayfarers, seitan etsy small batch hammock green juice hexagon whatever hoodie
ipsum fashion axe copper mug.
Fingerstache put a bird on it palo santo craft beer.
</BodyText>
<BodyText>
Chia cillum etsy pabst, in paleo fashion axe.
Gastropub pariatur tilde wayfarers chia laboris.
Salvia cred franzen chambray cillum slow-carb.
Beard letterpress distillery, knausgaard readymade YOLO in.
Aesthetic ea aliqua, blog vaporware kombucha gluten-free art party VHS pariatur cray raclette.
</BodyText>
<BodyText>
Pop-up kale chips four dollar toast gastropub you probably haven't heard of them prism tote bag.
Paleo thundercats godard glossier +1, iceland anim.
Readymade sriracha occaecat, crucifix bicycle rights retro seitan exercitation craft beer kale
chips minim.
Do post-ironic wayfarers, seitan etsy small batch hammock green juice hexagon whatever hoodie
ipsum fashion axe copper mug.
Fingerstache put a bird on it palo santo craft beer.
</BodyText>
<BodyText>
Chia cillum etsy pabst, in paleo fashion axe.
Gastropub pariatur tilde wayfarers chia laboris.
Salvia cred franzen chambray cillum slow-carb.
Beard letterpress distillery, knausgaard readymade YOLO in.
Aesthetic ea aliqua, blog vaporware kombucha gluten-free art party VHS pariatur cray raclette.
</BodyText>
<BodyText>
Pop-up kale chips four dollar toast gastropub you probably haven't heard of them prism tote bag.
Paleo thundercats godard glossier +1, iceland anim.
Readymade sriracha occaecat, crucifix bicycle rights retro seitan exercitation craft beer kale
chips minim.
Do post-ironic wayfarers, seitan etsy small batch hammock green juice hexagon whatever hoodie
ipsum fashion axe copper mug.
Fingerstache put a bird on it palo santo craft beer.
</BodyText>
<BodyText>
Chia cillum etsy pabst, in paleo fashion axe.
Gastropub pariatur tilde wayfarers chia laboris.
Salvia cred franzen chambray cillum slow-carb.
Beard letterpress distillery, knausgaard readymade YOLO in.
Aesthetic ea aliqua, blog vaporware kombucha gluten-free art party VHS pariatur cray raclette.
</BodyText>
<BodyText>
Pop-up kale chips four dollar toast gastropub you probably haven't heard of them prism tote bag.
Paleo thundercats godard glossier +1, iceland anim.
Readymade sriracha occaecat, crucifix bicycle rights retro seitan exercitation craft beer kale
chips minim.
Do post-ironic wayfarers, seitan etsy small batch hammock green juice hexagon whatever hoodie
ipsum fashion axe copper mug.
Fingerstache put a bird on it palo santo craft beer.
</BodyText>
<BodyText>
Chia cillum etsy pabst, in paleo fashion axe.
Gastropub pariatur tilde wayfarers chia laboris.
Salvia cred franzen chambray cillum slow-carb.
Beard letterpress distillery, knausgaard readymade YOLO in.
Aesthetic ea aliqua, blog vaporware kombucha gluten-free art party VHS pariatur cray raclette.
</BodyText>
<BodyText>
Pop-up kale chips four dollar toast gastropub you probably haven't heard of them prism tote bag.
Paleo thundercats godard glossier +1, iceland anim.
Readymade sriracha occaecat, crucifix bicycle rights retro seitan exercitation craft beer kale
chips minim.
Do post-ironic wayfarers, seitan etsy small batch hammock green juice hexagon whatever hoodie
ipsum fashion axe copper mug.
Fingerstache put a bird on it palo santo craft beer.
</BodyText>
</Drawer>
</div>
)}
</State>
Examples
Drawer Content Modifying Main Content
<State initial={{
selected: [1, 3],
opened: false
}}>
{([state, setState]) => {
const options = [
{ key: 1, value: 1, text: "Mercury" },
{ key: 2, value: 2, text: "Venus" },
{ key: 3, value: 3, text: "Earth" },
{ key: 4, value: 4, text: "Mars" }
];
const toggleOpen = () => setState(prev => ({ ...prev, opened: !prev.opened }));
const onChange = (value, checked) => {
checked
? setState(prev => ({ ...prev, selected: [...prev.selected, value] }))
: setState(prev => ({ ...prev, selected: prev.selected.filter(i => i !== value) }));
};
return (
<Card>
<BodyText size="large" bold className="m-b-1">Items to Request</BodyText>
<ul className="p-l-0 lh-default" style={{listStyle: 'none'}}>
{state.selected.length === 0
? <BodyText subdued>No Items Requested</BodyText>
: options.map(
item => state.selected.includes(item.value) ? <li key={item.key}>{item.text}</li> : null
)
}
</ul>
<Button small fill="outline" primary onClick={toggleOpen}>Edit Selection</Button>
<Drawer
header="Available Items"
open={state.opened}
onClose={toggleOpen}
>
<Form><Form.Group grouped>
{options.map((item) => (
<Form.Checkbox
label={item.text}
key={item.key}
value={item.value}
checked={state.selected.includes(item.value)}
onChange={onChange}
/>
))}
</Form.Group></Form>
</Drawer>
</Card>
)
}}
</State>
Best Practices
- Drawers work well for:
- Completing quick sub-tasks in a flow.
- Aiding in completing a task on the page.
- Drawer actions shouldn't reset when closed.
- To completely hide the context of the page, use the Takeover component.
- For a small, floating container of information, use the Popover component.
- To more directly interrupt the user flow with content, use the modal component.
Importing
import { Drawer } from '@servicetitan/design-system';