Documentation Index
Fetch the complete documentation index at: https://anvil.servicetitan.com/llms.txt
Use this file to discover all available pages before exploring further.
Implementation
Drawer Props
Drawer.Content Props
Common Examples
import { Button, Drawer } from "@servicetitan/anvil2";
function ExampleComponent() {
return (
<Drawer open>
<Drawer.Header>Header text</Drawer.Header>
<Drawer.Content>Body text</Drawer.Content>
<Drawer.Footer>
<Button appearance="primary">Footer Button</Button>
</Drawer.Footer>
</Drawer>
);
}
Closing Drawers
Clicking a Drawer.CancelButton or the close button in the Drawer.Header will trigger the onClose handler. A callback can be added to a Drawer.CancelButton or any Button in the Drawer.Footer to further control the open state of the Drawer.Closing callbacks
In addition to onClose–which indicates a user has chosen to close a drawer–the component also offers two animation callbacks onCloseAnimationStart and onCloseAnimationComplete.You may use these callbacks to perform additional actions related to the presentation of the Drawer. For example, if you have a drawer containing a form, you may wish to reset the form state after the close animation has played so that the user doesn’t see an empty form briefly when closing the drawer.const [open, setOpen] = useState(false);
const [field, setField] = useState("");
const handleSubmit = () => {
setOpen(false);
};
const handleDrawerAnimationComplete = () => {
setField("");
};
return (
<>
<Button onClick={() => setOpen(true)}>Open</Button>
<Drawer
open={open}
onClose={() => setOpen(false)}
onCloseAnimationComplete={handleDrawerAnimationComplete}
>
<Drawer.Header>What is your favorite color?</Drawer.Header>
<Drawer.Content>
<TextField
label="Favorite color"
value={field}
onChange={(e) => setField(e.target.value)}
style={{ width: "100%" }}
autoComplete="off"
/>
</Drawer.Content>
<Drawer.Footer>
<Button onClick={() => setOpen(false)}>Cancel</Button>
<Button appearance="primary" onClick={handleSubmit}>
Submit
</Button>
</Drawer.Footer>
</Drawer>
</>
);
Sticky Content
Use the sticky prop on Drawer.Content to keep important UI elements (like search fields or filters) visible while other content scrolls. This is useful for drawers with long, scrollable content.import { Button, Drawer, Flex, TextField, Text } from "@servicetitan/anvil2";
import { useState } from "react";
function ExampleComponent() {
const [isOpen, setIsOpen] = useState(true);
return (
<Drawer open={isOpen} onClose={() => setIsOpen(false)}>
<Drawer.Header>Filter Items</Drawer.Header>
<Drawer.Content sticky>
<TextField placeholder="Search items..." />
</Drawer.Content>
<Drawer.Content>
<Flex direction="column" gap="2">
{Array.from({ length: 50 }, (_, i) => (
<Text key={i}>Item {i + 1}</Text>
))}
</Flex>
</Drawer.Content>
<Drawer.Footer sticky>
<Flex justifyContent="space-between" grow="1">
<Button>Reset</Button>
<Flex gap="3">
<Drawer.CancelButton>Cancel</Drawer.CancelButton>
<Button appearance="primary">Apply</Button>
</Flex>
</Flex>
</Drawer.Footer>
</Drawer>
);
}
The search field will remain visible at the top while the list of items scrolls below it.Drawers and Toasts
Due to the way the HTML dialog element renders in the browser’s top layer, the Drawer component includes an internal Toaster for rendering toast messages. This should be unnoticeable to implementors and users, but there may be edge cases the result in toasts not rendering as expected.Please reach out to us in the #ask-designsystem channel on Slack if any edge cases related to drawers and toasts are found!Anti-Patterns
Conditional rendering
The openness should be controlled by the open prop and not by conditional rendering — this is an anti-pattern for Drawer.❌
{condition && <Drawer open>...</Drawer>}
✅
<Drawer open={condition}>...</Drawer>
Resetting content
HTML Dialog show/hide content but it doesn’t remove from the DOM which means the reset doesn’t happen automatically. To do the reset, use key on <Drawer.Content> . Since <Drawer.Content> is always present in DOM, you can add conditional to, or in, the <Drawer.Content> as well.More details
Drawer uses HTML Dialog which is powered by HTML top-layers. This puts them on top of EVERYTHING regardless z-index and we use the HTML top-layer to avoid stacking context and z-index issues, ensuring Drawer always appear above all content without needing manual z-index adjustments. Drawer internally has custom mechanism to ensure Toasts to be on top for ServiceTitan app and requires Drawer to be present on page load.<Drawer
open={true}
onClose={() => setIsOpen(false)}
disableCloseOnClickOutside={false}
disableCloseOnEscape={false}
size="medium"
onCloseAnimationComplete={() => {}}
onCloseAnimationStart={() => {}}
onOpenAnimationComplete={() => {}}
onOpenAnimationStart={() => {}}
>
<Drawer.Header>Header</Drawer.Header>
<Drawer.Content>Content</Drawer.Content>
<Drawer.Footer>
<Button>Action</Button>
</Drawer.Footer>
</Drawer>
Drawer Props
In addition to the props listed below, the Drawer component can accept any valid HTML dialog props.disableCloseOnClickOutside
When true, clicking outside the drawer will not close it.
When true, pressing the escape key will not close the drawer.
Enables scroll chaining behavior.
initialFocusResolver
(focusables: (HTMLElement | SVGElement)[]) => (HTMLElement | SVGElement)
Given an array of focusable elements, returns the element that should receive initial focus.
Callback when clicking outside the drawer.
Indicates the user has closed the drawer. Use this to update your state.
Callback indicating the drawer has animated out. Use this to clean up views as needed.
Callback indicating the drawer is beginning to animate out.
Callback indicating the drawer has animated in.
Callback indicating the drawer is beginning to animate in.
size
"small" | "medium" | "large" | "xlarge"
default:"medium"
<Drawer.Content sticky>Drawer content</Drawer.Content>
Drawer.Content Props
In addition to the props listed below, the Drawer.Content component can accept any valid HTML div props.When true, the content will stick below the header during scroll.