Overview
AxDrawer is a styled wrapper around Ant Design’s Drawer component, providing preset sizes, loading states, and consistent styling with the design system. Use drawers for forms, detail views, profiles, and multi-step workflows.
Import
import AxDrawer from "@/components/AxDrawer"
Basic Usage
import { useState } from "react"
import AxDrawer from "@/components/AxDrawer"
import AxButton from "@/components/AxButton"
function Example() {
const [open, setOpen] = useState(false)
return (
<>
<AxButton onClick={() => setOpen(true)}>Open Drawer</AxButton>
<AxDrawer
title="Drawer Title"
description="Additional context about this drawer"
open={open}
onClose={() => setOpen(false)}
footer={
<Flex justify="flex-end" gap={8}>
<AxButton variant="secondary" onClick={() => setOpen(false)}>
Cancel
</AxButton>
<AxButton onClick={() => setOpen(false)}>Save</AxButton>
</Flex>
}
>
Drawer content goes here
</AxDrawer>
</>
)
}
Props
AxDrawer-specific Props
Short description rendered below the title in muted text. Provides additional context for the drawer content. Same pattern as AxModal and AxCard.
size
'xs' | 'sm' | 'md' | 'lg'
default:"'md'"
Preset widths:
xs: 400px — quick actions, simple confirmations
sm: 500px — detail views, bid summaries
md: 600px — forms, multi-step flows
lg: 640px — profiles, rich side panels
You can still override with the width prop for custom sizes.
Show a loading spinner overlay over the drawer body. Use while fetching data or during async submission.
Common Drawer Props
Whether the drawer is visible.
The drawer title. Combined with description to form the header block.
placement
'left' | 'right' | 'top' | 'bottom'
default:"'right'"
The placement of the drawer on the screen.
Custom width of the drawer (when placement is left or right). Overrides the size preset.
Custom height of the drawer (when placement is top or bottom).
Footer content, typically used for action buttons.
Extra actions displayed in the top-right corner of the drawer header.
onClose
(e: React.MouseEvent | React.KeyboardEvent) => void
Callback when the drawer is closed (via close button, mask click, or Escape key).
Additional class name for the drawer’s root element.
Whether to show the close icon in the top right corner.
Whether to show the mask (backdrop).
Whether clicking the mask closes the drawer.
Whether the Escape key closes the drawer.
Whether to unmount child components when the drawer is closed.
Whether to destroy content when hidden (similar to destroyOnClose but also applies when transitioning).
Callback when the open state changes, fired after animation completes.
The z-index of the drawer.
push
boolean | { distance: number }
default:"true"
Whether to push other drawers when this one opens. Can specify a custom push distance.
Whether to render the drawer content before it’s opened for the first time.
Examples
Sizes
AxDrawer provides four preset sizes optimized for different use cases:
// Extra Small (400px) - quick actions
<AxDrawer
size="xs"
title="Quick confirmation"
open={open}
onClose={() => setOpen(false)}
/>
// Small (500px) - detail views and summaries
<AxDrawer
size="sm"
title="Bid Details"
description="Submitted by PharmaCorp Ltd · 3 days ago"
open={open}
onClose={() => setOpen(false)}
>
{/* Bid summary content */}
</AxDrawer>
// Medium (600px) - forms and multi-step flows (default)
<AxDrawer
size="md"
title="Review quantities"
description="Confirm the quantities you can supply before submitting."
open={open}
onClose={() => setOpen(false)}
>
{/* Form content */}
</AxDrawer>
// Large (640px) - profiles and rich content
<AxDrawer
size="lg"
title="Supplier Profile"
open={open}
onClose={() => setOpen(false)}
>
{/* Profile content */}
</AxDrawer>
Detail View
Use for read-only data display:
<AxDrawer
title="Bid #BID-2024-0892"
description="Submitted by PharmaCorp Ltd · 3 days ago"
size="sm"
open={open}
onClose={() => setOpen(false)}
footer={
<Flex justify="flex-end" gap={8}>
<AxButton variant="secondary" onClick={() => setOpen(false)}>
Reject
</AxButton>
<AxButton onClick={handleAccept}>Accept Bid</AxButton>
</Flex>
}
>
<Flex vertical gap={24}>
<Flex justify="space-between" align="center">
<AxText variant="body-sm" color="secondary">Status</AxText>
<AxTag tone="info" dot>In Review</AxTag>
</Flex>
<Divider />
{/* Line items */}
<Flex vertical gap={12}>
<AxText variant="body-sm" weight="medium">Line Items</AxText>
{items.map(item => (
<div key={item.id} style={{ background: "var(--neutral-50)", borderRadius: 8, padding: "12px 16px" }}>
<Flex justify="space-between">
<AxText variant="body-sm" weight="medium">{item.name}</AxText>
<AxText variant="body-sm" weight="semibold">{item.total}</AxText>
</Flex>
</div>
))}
</Flex>
</Flex>
</AxDrawer>
Form Drawer
Use for input collection:
<AxDrawer
title="Review quantities"
description="Confirm the quantities you can supply before submitting."
size="md"
open={open}
onClose={() => setOpen(false)}
footer={
<Flex justify="space-between">
<AxButton variant="ghost" onClick={() => setOpen(false)}>
Back
</AxButton>
<AxButton onClick={handleSubmit}>Accept & Continue</AxButton>
</Flex>
}
>
<Flex vertical gap={16}>
{medications.map(med => (
<div key={med.id}>
<Flex justify="space-between" style={{ marginBottom: 4 }}>
<span>{med.name}</span>
<AxText variant="body-xs" color="secondary">
Requested: {med.quantity}
</AxText>
</Flex>
<AxInput placeholder="Enter quantity you can supply" suffix="units" />
</div>
))}
<div>
<AxText variant="body-sm" weight="medium" style={{ marginBottom: 8 }}>
Notes (optional)
</AxText>
<Input.TextArea
placeholder="Add any notes about availability, lead times, or substitutions..."
rows={3}
/>
</div>
</Flex>
</AxDrawer>
Multi-Step Flow
Use for wizard-style interactions:
function MultiStepDrawer() {
const [open, setOpen] = useState(false)
const [step, setStep] = useState(0)
const steps = [
{ title: "Confirm order details", description: "Step 1 of 3 — Review your order" },
{ title: "Select delivery address", description: "Step 2 of 3 — Choose delivery location" },
{ title: "Review & submit", description: "Step 3 of 3 — Final confirmation" },
]
return (
<AxDrawer
title={steps[step].title}
description={steps[step].description}
size="md"
open={open}
onClose={() => setOpen(false)}
footer={
<Flex justify="space-between">
{step > 0 ? (
<AxButton variant="ghost" onClick={() => setStep(s => s - 1)}>
Back
</AxButton>
) : (
<AxButton variant="ghost" onClick={() => setOpen(false)}>
Cancel
</AxButton>
)}
{step < steps.length - 1 ? (
<AxButton onClick={() => setStep(s => s + 1)}>Next</AxButton>
) : (
<AxButton onClick={handleSubmit}>Submit Order</AxButton>
)}
</Flex>
}
>
{/* Render content based on current step */}
{step === 0 && <OrderSummary />}
{step === 1 && <AddressSelection />}
{step === 2 && <FinalReview />}
</AxDrawer>
)
}
Profile Drawer
Use for rich, content-heavy side panels:
<AxDrawer
title="PharmaCorp Ltd"
description="Verified supplier · Nairobi, Kenya"
size="lg"
open={open}
onClose={() => setOpen(false)}
destroyOnHidden
footer={
<Flex justify="flex-end">
<AxButton onClick={() => setOpen(false)}>Close</AxButton>
</Flex>
}
>
<Flex vertical gap={24}>
{/* Identity */}
<Flex gap={16} align="center">
<Avatar size={64} icon={<UserOutlined />} />
<Flex vertical gap={4}>
<AxText variant="body-lg" weight="semibold">PharmaCorp Ltd</AxText>
<AxTag tone="success" dot>Verified</AxTag>
</Flex>
</Flex>
<Divider />
{/* Contact */}
<Flex vertical gap={12}>
<AxText variant="body-sm" weight="medium">Contact Information</AxText>
<Flex gap={8} align="center">
<MailOutlined />
<AxText variant="body-sm">[email protected]</AxText>
</Flex>
{/* More contact info */}
</Flex>
{/* Performance stats, recent activity, etc. */}
</Flex>
</AxDrawer>
Loading State
Show loading overlay during data fetch:
function LoadingDrawer() {
const [open, setOpen] = useState(false)
const [loading, setLoading] = useState(false)
const handleOpen = () => {
setOpen(true)
setLoading(true)
fetchData().then(() => setLoading(false))
}
return (
<AxDrawer
title="Purchase Order #PO-2024-0044"
description="Loading order details..."
size="sm"
open={open}
loading={loading}
onClose={() => setOpen(false)}
footer={
<Flex justify="flex-end">
<AxButton onClick={() => setOpen(false)}>Close</AxButton>
</Flex>
}
>
{/* Content will be covered by spinner while loading */}
<OrderDetails />
</AxDrawer>
)
}
Left Placement
Place drawer on the left side:
<AxDrawer
title="Navigation"
placement="left"
size="sm"
open={open}
onClose={() => setOpen(false)}
>
<Navigation />
</AxDrawer>
Add extra actions in the header:
<AxDrawer
title="Settings"
open={open}
onClose={() => setOpen(false)}
extra={
<AxButton variant="ghost" size="sm" onClick={handleReset}>
Reset All
</AxButton>
}
>
<SettingsForm />
</AxDrawer>
Accessibility
- The drawer automatically sets
aria-describedby when a description is provided
- Keyboard navigation is enabled by default (Escape to close)
- Focus is trapped within the drawer when open
- The drawer uses semantic HTML with proper ARIA attributes
- Loading state includes
aria-busy attribute
Best Practices
-
Choose the right size:
xs (400px) for quick actions and confirmations
sm (500px) for detail views and summaries
md (600px) for forms and multi-step flows
lg (640px) for profiles and content-rich panels
-
Use loading state when fetching data to provide visual feedback
-
Provide descriptions to give users context about the drawer content
-
Use footer buttons consistently:
- Left: Secondary/Back/Cancel actions
- Right: Primary/Save/Continue actions
-
Consider destroyOnClose for drawers with forms to reset state when closed
-
Prefer right placement (default) for most use cases
-
Drawers vs Modals:
- Use drawers for forms, details, and workflows
- Use modals for confirmations and brief interactions
Related Components
- AxModal — Dialog for confirmations and focused interactions
- AxButton — Used for drawer actions
- AxCard — For content layout within drawers