Sheet
A panel that slides in from the edge of the screen. Useful for navigation, filters, forms, or any supplementary content.
Example
A right-side sheet used for navigation.
<div buiSheet side="end">
<button matButton buiSheetTrigger>Open Navigation</button>
<ng-template buiSheetPortal>
<div buiSheetBackdrop></div>
<div buiSheetContent>
<div buiSheetHeader class="items-center justify-between">
<h2 buiSheetTitle>Navigation</h2>
<button matIconButton buiSheetClose>
<mat-icon svgIcon="x" />
</button>
</div>
<div buiSheetBody class="flex flex-col gap-y-2">
<button matButton buiSheetClose class="tertiary justify-start">
<mat-icon svgIcon="house" />
<span>Home</span>
</button>
<button matButton buiSheetClose class="tertiary justify-start">
<mat-icon svgIcon="user" />
<span>Profile</span>
</button>
<button matButton buiSheetClose class="tertiary justify-start">
<mat-icon svgIcon="settings" />
<span>Settings</span>
</button>
</div>
</div>
</ng-template>
</div>Directives
The directives are standalone. Import only the slots the sheet actually uses. Trigger, close, portal, backdrop, content, title and description require the root.
import {
BuiSheet,
BuiSheetBackdrop,
BuiSheetBody,
BuiSheetClose,
BuiSheetContent,
BuiSheetDescription,
BuiSheetFooter,
BuiSheetHeader,
BuiSheetPortal,
BuiSheetTitle,
BuiSheetTrigger,
} from '@/bui/ui/sheet';| Directive | Description |
|---|---|
buiSheet | Root container. Manages open state, position, and the CDK dialog. |
buiSheetTrigger | Opens the sheet when clicked. |
buiSheetPortal | Wraps the content that is rendered inside a CDK dialog overlay. |
buiSheetBackdrop | Semi-transparent backdrop behind the panel. Optional. |
buiSheetContent | The sliding panel itself. |
buiSheetHeader | Sticky header area. |
buiSheetTitle | Title text inside the header. |
buiSheetDescription | Description text inside the header. Optional. |
buiSheetBody | Scrollable main content area. |
buiSheetFooter | Sticky footer area. |
buiSheetClose | Closes the sheet when clicked. |
This is how the slots nest.
<div buiSheet side="end">
<button matButton buiSheetTrigger>Open</button>
<ng-template buiSheetPortal>
<div buiSheetBackdrop></div>
<div buiSheetContent>
<div buiSheetHeader>
<h2 buiSheetTitle>Title</h2>
</div>
<div buiSheetBody>
Content goes here
</div>
<div buiSheetFooter>
<button matButton buiSheetClose>Close</button>
</div>
</div>
</ng-template>
</div>Sheet
The root directive. Apply it to any element to create a sheet instance. It manages the open state, handles keyboard and backdrop dismissal, and renders the content inside a CDK dialog.
<!-- Default (end) -->
<div buiSheet>...</div>
<!-- Left side -->
<div buiSheet side="start">...</div>
<!-- Prevent backdrop/Escape dismissal -->
<div buiSheet disableClose>...</div>| Prop | Type | Default | Description |
|---|---|---|---|
open | model<boolean> | false | Two-way binding for the open state. |
side | 'start' | 'end' | 'top' | 'bottom' | 'end' | Edge of the screen the sheet slides in from. |
disableClose | boolean | false | Prevents closing via backdrop click or Escape key. |
autoFocus | 'dialog' | 'first-tabbable' | 'first-heading' | string | undefined | 'first-tabbable' | Configures where focus goes when the dialog opens. |
restoreFocus | boolean | string | HTMLElement | true | Configures where focus returns when the dialog closes. |
(opened) | void | — | Emits when the sheet opens. |
(closed) | void | — | Emits after the sheet closes and its exit animation completes. |
Trigger
Apply buiSheetTrigger to a button or any clickable element. It automatically sets aria-expanded, aria-controls, and keyboard support (Enter / Space).
<button matButton buiSheetTrigger>Open Sheet</button>Portal
The buiSheetPortal directive goes on an ng-template. Its content is projected into a CDK dialog overlay when the sheet opens.
<ng-template buiSheetPortal>
<div buiSheetBackdrop></div>
<div buiSheetContent>
<!-- header, body, footer -->
</div>
</ng-template>Backdrop
Add buiSheetBackdrop inside the portal to render a semi-transparent overlay behind the panel. It is optional, omitting it gives you a sheet without a dimmed background.
Content
The buiSheetContent directive marks the sliding panel. It handles positioning, entry/exit animations, and nested sheet scaling. Place header, body, and footer directives inside it.
Header, Title & Description
The header is a sticky area at the top of the sheet. buiSheetTitle automatically connects to aria-labelledby on the dialog, and buiSheetDescription (optional) connects to aria-describedby.
<div buiSheetHeader class="items-center justify-between">
<h2 buiSheetTitle>Title</h2>
<button matIconButton buiSheetClose>
<mat-icon svgIcon="x" />
</button>
</div>Body
The scrollable main content area. It fills the available space between the header and footer.
Footer
A sticky area at the bottom. Typically holds action buttons like Apply or Cancel.
<div buiSheet side="start">
<button matButton buiSheetTrigger>Open Sheet</button>
<ng-template buiSheetPortal>
<div buiSheetBackdrop></div>
<div buiSheetContent>
<div buiSheetHeader class="items-center justify-between">
<h2 buiSheetTitle>Sheet Title</h2>
<button matIconButton buiSheetClose>
<mat-icon svgIcon="x" />
</button>
</div>
<div buiSheetBody>
Scrollable content here...
</div>
<div buiSheetFooter>
<button matButton buiSheetClose>Close</button>
</div>
</div>
</ng-template>
</div>Close
Apply buiSheetClose to any element inside the portal. It closes the sheet when clicked. Attach your own (click) handler separately for additional logic.
<button matButton buiSheetClose (click)="onSave()">
Save & Close
</button>Side
Use the side input to control which edge the sheet enters from. Sides start and end are direction-aware and flip automatically in RTL layouts.
<!-- Right (default) -->
<div buiSheet side="end">...</div>
<!-- Left -->
<div buiSheet side="start">...</div>
<!-- Top -->
<div buiSheet side="top">...</div>
<!-- Bottom -->
<div buiSheet side="bottom">...</div>Programmatic control
You can control the sheet without a trigger. Use the open model for two-way binding, or grab a template reference via #ref="buiSheet" and call open(), close(), or toggle().
<!-- Two-way binding -->
<div buiSheet [(open)]="isSheetOpen">
<ng-template buiSheetPortal>
<div buiSheetBackdrop></div>
<div buiSheetContent>...</div>
</ng-template>
</div>
<button matButton (click)="isSheetOpen = true">Open</button>
<!-- Template reference -->
<div buiSheet #mySheet="buiSheet">
<ng-template buiSheetPortal>
<div buiSheetBackdrop></div>
<div buiSheetContent>...</div>
</ng-template>
</div>
<button matButton (click)="mySheet.open()">Open</button>
<button matButton (click)="mySheet.close()">Close</button>
<button matButton (click)="mySheet.toggle()">Toggle</button>Disable close
Add disableClose to prevent closing via backdrop click or the Escape key. The user must use an explicit close button.
<div buiSheet side="end" disableClose>
<button matButton buiSheetTrigger>Open Sheet</button>
<ng-template buiSheetPortal>
<div buiSheetBackdrop></div>
<div buiSheetContent>
<div buiSheetHeader class="items-center justify-between">
<h2 buiSheetTitle>Disable Close</h2>
<button matIconButton buiSheetClose>
<mat-icon svgIcon="x" />
</button>
</div>
<div buiSheetBody>
<p>Only the close button can dismiss this sheet.</p>
</div>
</div>
</ng-template>
</div>Focus and accessibility
The panel opens through a CDK dialog. It renders inside a role="dialog" container, focus moves into the panel on open and a focus trap keeps it there while the sheet is open. The autoFocus input picks the target, the first tabbable element by default, and restoreFocus controls where focus lands on close, by default back on the trigger. buiSheetTitle and buiSheetDescription feed aria-labelledby and aria-describedby.
See it in blocks
- Simple opens one sheet per side.
- With close button puts a close button in the header.
- With header and footer scrolls a long body between fixed actions.
- Explicit close dismisses only from its own button.
- Nested opens two more sheets on top of itself.