Drawer
Slide-in panel anchored to one edge of the viewport. Modal-style focus trap and Escape close.
Overview
The Drawer component is a core building block of the Byteion design system, providing a robust, accessibility-compliant interface for its specific UI capabilities.
Why this exists
Drawers provide side-panel access to supplementary content without navigating away. They are ideal for detail views, filters, or settings panels that need to reference the main content.
Mental model
A drawer slides in from an edge (right, left, bottom) and shares the screen with the main content. Unlike a modal, it feels less disruptive and is better for reference workflows.
When to use
When not to use
Basic example
The following example demonstrates standard usage, fully compliant with Byteion's token-first approach.
import { useState } from 'react';
import { Button, Drawer } from '@byteion/react/client';
type Side = 'right' | 'left' | 'bottom';
export default function DrawerDemo() {
const [side, setSide] = useState<Side | null>(null);
const close = () => setSide(null);
return (
<div className="flex flex-wrap gap-3 p-6">
<Button onClick={() => setSide('right')}>Right</Button>
<Button variant="outline" onClick={() => setSide('left')}>Left</Button>
<Button variant="ghost" onClick={() => setSide('bottom')}>Bottom</Button>
<Drawer open={side === 'right'} onClose={close} side="right"
title="User settings"
description="Manage your account preferences and security.">
<p className="text-sm text-muted-foreground">
Anchor a drawer to any side. Useful for deep forms, filters, or
contextual editors that the user needs while keeping the main page visible.
</p>
</Drawer>
<Drawer open={side === 'left'} onClose={close} side="left" title="Navigation" />
<Drawer open={side === 'bottom'} onClose={close} side="bottom" title="Quick action" />
</div>
);
}
<script setup lang="ts">
import { ref } from 'vue';
import { Button, Drawer } from '@byteion/vue';
type Side = 'right' | 'left' | 'bottom';
const side = ref<Side | null>(null);
const close = () => (side.value = null);
</script>
<template>
<div class="flex flex-wrap gap-3 p-6">
<Button @click="side = 'right'">Right</Button>
<Button variant="outline" @click="side = 'left'">Left</Button>
<Button variant="ghost" @click="side = 'bottom'">Bottom</Button>
<Drawer :open="side === 'right'" @close="close" side="right"
title="User settings"
description="Manage your account preferences and security.">
<p class="text-sm text-muted-foreground">
Anchor a drawer to any side. Useful for deep forms, filters, or
contextual editors that the user needs while keeping the main page visible.
</p>
</Drawer>
<Drawer :open="side === 'left'" @close="close" side="left" title="Navigation" />
<Drawer :open="side === 'bottom'" @close="close" side="bottom" title="Quick action" />
</div>
</template>
Variants
Each variant communicates a different meaning. Choose based on priority, not aesthetics.
Single default variant.
Sizes
Size should match the importance and context of the component.
- md — md size.
States
This component reacts seamlessly to the following interaction and data states:
- hidden — Not rendered in the DOM.
- visible — Rendered and interactive.
- default — Resting appearance before interaction.
- hover — Pointer is over the element — visual feedback via color or shadow.
- focus — Keyboard or tab focus — shows ring indicator for accessibility.
- active — Currently selected or in use.
- disabled — Non-interactive — grayed out, cursor not-allowed.
- open — Component is expanded or visible.
- closed — Component is collapsed or hidden.
Anatomy
Usage guidelines
- Use the right edge for detail panels.
- Use the left edge for navigation or filters.
- Allow the overlay to be optional for non-blocking drawers.
Do and Don't
Do
- Use right side for detail views.
- Allow scroll within the drawer.
- Provide a clear close action.
Don't
- Do not open drawers on page load.
- Do not nest drawers.
- Do not use for destructive confirmations.
Accessibility
Drawer has role="dialog" when modal. Focus is trapped. Escape closes. Slide direction affects reading order.
Role:dialog
ARIA Attributes
aria-modal
Keyboard Navigation
- Escape: Closes the overlay or cancels the action
- Tab: Moves focus to the next interactive element
Customization
Width and slide direction are configurable. Z-index uses --z-overlay.
Compose with React cva()
Use the exportedcva() +cn() utilities to compose your own variant on top of the Byteion tokens.
import { cva, cn, type VariantProps } from '@byteion/react';
const myDrawer = cva('base classes…', {
variants: {
accent: { soft:'border-primary-200', loud:'border-primary-500' },
},
defaultVariants: { accent:'soft' },
});
type Props = React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof myDrawer>;
Compose with Vue cva()
Use the exportedcva() +cn() utilities inside standard Vue setup blocks.
<script setup lang="ts">
import { computed, useAttrs } from 'vue';
import { cva, type VariantProps } from '@byteion/vue';
interface Props { accent?:'soft' |'loud' }
const props = withDefaults(defineProps<Props>(), { accent:'soft' });
const attrs = useAttrs();
const myDrawer = cva('base classes…', {
variants: { accent: { soft:'border-primary-200', loud:'border-primary-500' } },
});
const cls = computed(() => myDrawer({ accent: props.accent, class: attrs.class as string | undefined }));
</script>
Runtime events
Byteion keeps framework APIs idiomatic: HTML uses DOM custom events, React uses callback props, and Vue uses emitted events / v-model updates.
HTML / JS events
bi:drawer:hidebi:drawer:show
React callbacks
onClose() => void
Vue emits
update:modelValue[v: boolean]update:open[v: boolean]close[]
Remote / AJAX
This component does not implement Byteion JS remote loading. Use normal HTML form submission, framework state, or your application data layer around it.
API Reference
Requires data-bi-component="drawer" with placement attribute.
HTML Data Attributes
data-bi-drawerdata-bi-drawer-placementdata-bi-drawer-toggledata-bi-drawer-hidedata-bi-backdrop
React Props · DrawerProps
Extracted directly fromreact/src/components/Drawer.tsx.
export interface DrawerProps {
open: boolean;
onClose: () => void;
/** Edge the drawer slides in from. */
side?: 'left' | 'right' | 'top' | 'bottom';
size?: 'sm' | 'md' | 'lg' | 'xl';
staticBackdrop?: boolean;
keyboard?: boolean;
/** Backdrop visual treatment. */
backdrop?: 'clear' | 'soft';
title?: ReactNode;
description?: ReactNode;
footer?: ReactNode;
children?: ReactNode;
className?: string;
}
Vue Props · DrawerProps
Extracted directly fromvue/src/components/Drawer.vue.
export interface DrawerProps {
modelValue?: boolean;
open?: boolean;
/** Edge the drawer slides in from. */
side?: 'left' | 'right' | 'top' | 'bottom';
size?: 'sm' | 'md' | 'lg' | 'xl';
staticBackdrop?: boolean;
keyboard?: boolean;
backdrop?: 'clear' | 'soft';
title?: string;
description?: string;
}
Developer tips
side="bottom") for short mobile tasks and full-page routing for long tasks.
• Destructive confirmation: for delete actions, have the drawer open a Modal confirmation rather than committing directly.Common mistakes
Production usage tips
- Test with keyboard only — trigger via Enter/Space, tab through all controls, close via Escape.
- Test focus trap and focus restoration — focus should cycle within the drawer and return to the trigger on close.
- Test
staticBackdropandkeyboard={false}— backdrop click and Escape should be blocked respectively. - Test long content scrolling — body scrolls independently, header and footer stay fixed.
- Test inside dashboards, tables, and layouts with
overflow-hiddenparents — drawer must render via portal to body. - Test mobile bottom placement (
side="bottom") — ensure close button is visible and drag handle has sufficient contrast. - Test dark mode and branded themes — all tokens (
bg-card,text-card-foreground,shadow-modal) adapt automatically. - Keep React and Vue APIs aligned with the core schema —
side/sizeprops map to CSS classes and data attributes.
Premium examples
Advanced patterns available in Byteion Pro:
Related components
Unlock Premium Patterns
Get access to copy-paste ready Pro blocks, SaaS templates, and advanced data-tables built with this component.
Explore Pro Components