Offcanvas
A sliding panel that appears from any edge of the screen.
Introduction
Offcanvas components slide in from the edges of the viewport, useful for navigation menus, filters, or supplementary content. Built on Radix UI Dialog for accessibility.
Installation
import {
Offcanvas, OffcanvasTrigger, OffcanvasContent,
OffcanvasHeader, OffcanvasTitle, OffcanvasBody,
} from '@/components/ui/offcanvas';Usage
<Offcanvas>
<OffcanvasTrigger asChild>
<Button>Open</Button>
</OffcanvasTrigger>
<OffcanvasContent>
<OffcanvasHeader><OffcanvasTitle>Title</OffcanvasTitle></OffcanvasHeader>
<OffcanvasBody>Content here</OffcanvasBody>
</OffcanvasContent>
</Offcanvas>Examples
Default
import { Button } from '@/components/ui/button';
import { Offcanvas, OffcanvasTrigger, OffcanvasContent, OffcanvasHeader, OffcanvasTitle, OffcanvasBody } from '@/components/ui/offcanvas';
export default function Component() {
return (
<Offcanvas>
<OffcanvasTrigger asChild><Button variant="outlined">Open Offcanvas</Button></OffcanvasTrigger>
<OffcanvasContent>
<OffcanvasHeader><OffcanvasTitle>Offcanvas</OffcanvasTitle></OffcanvasHeader>
<OffcanvasBody><p className="text-sm">Offcanvas content here.</p></OffcanvasBody>
</OffcanvasContent>
</Offcanvas>
);
}Sides
Use the side prop to control which edge the panel slides from.
import { Button } from '@/components/ui/button';
import { Offcanvas, OffcanvasTrigger, OffcanvasContent, OffcanvasHeader, OffcanvasTitle, OffcanvasBody } from '@/components/ui/offcanvas';
export default function Component() {
return (
<div className="flex gap-2">
{(['left', 'right', 'top', 'bottom'] as const).map((side) => (
<Offcanvas key={side}>
<OffcanvasTrigger asChild><Button variant="outlined" size="sm">{side}</Button></OffcanvasTrigger>
<OffcanvasContent side={side}>
<OffcanvasHeader><OffcanvasTitle>Offcanvas {side}</OffcanvasTitle></OffcanvasHeader>
<OffcanvasBody><p className="text-sm">Slides from {side}.</p></OffcanvasBody>
</OffcanvasContent>
</Offcanvas>
))}
</div>
);
}Body Scroll
Use the bodyScroll prop to allow the page body to remain scrollable while the offcanvas is open.
'use client';
/* eslint-disable eslint-frontend-rules/enforce-typography-components */
import { Button } from '@/components/ui/button';
import {
Offcanvas,
OffcanvasBody,
OffcanvasContent,
OffcanvasHeader,
OffcanvasTitle,
OffcanvasTrigger,
} from '@/components/ui/offcanvas';
export function OffcanvasDefault() {
return (
<Offcanvas>
<OffcanvasTrigger asChild>
<Button variant='outlined'>Open Offcanvas</Button>
</OffcanvasTrigger>
<OffcanvasContent>
<OffcanvasHeader>
<OffcanvasTitle>Offcanvas</OffcanvasTitle>
</OffcanvasHeader>
<OffcanvasBody>
<p className='text-sm'>
Some text as placeholder. In real life you can have the elements you have chosen.
</p>
</OffcanvasBody>
</OffcanvasContent>
</Offcanvas>
);
}
export function OffcanvasSides() {
return (
<div className='flex flex-wrap gap-2'>
{(['left', 'right', 'top', 'bottom'] as const).map(side => (
<Offcanvas key={side}>
<OffcanvasTrigger asChild>
<Button variant='outlined' size='sm'>
{side}
</Button>
</OffcanvasTrigger>
<OffcanvasContent side={side}>
<OffcanvasHeader>
<OffcanvasTitle>Offcanvas {side}</OffcanvasTitle>
</OffcanvasHeader>
<OffcanvasBody>
<p className='text-sm'>Content slides in from the {side}.</p>
</OffcanvasBody>
</OffcanvasContent>
</Offcanvas>
))}
</div>
);
}
export function OffcanvasBodyScroll() {
return (
<Offcanvas>
<OffcanvasTrigger asChild>
<Button variant='outlined'>Enable Body Scroll</Button>
</OffcanvasTrigger>
<OffcanvasContent bodyScroll>
<OffcanvasHeader>
<OffcanvasTitle>Body Scrolling</OffcanvasTitle>
</OffcanvasHeader>
<OffcanvasBody>
<p className='text-sm'>
Try scrolling the rest of the page while this offcanvas is open. The backdrop is hidden
and body scroll is enabled.
</p>
</OffcanvasBody>
</OffcanvasContent>
</Offcanvas>
);
}
Note: The offcanvas panel width is 400px for left/right sides, and height is 30vh for top/bottom sides.
API Reference
OffcanvasContent
| Prop | Type | Default | Description |
|---|---|---|---|
side | left | right | top | bottom | right | Slide direction |
bodyScroll | boolean | false | Allow body scrolling while open |