Drawer
A panel that slides in from the side of the screen. Use it for content viewed without leaving the page, such as record details, a short form or the filters of a list page.
Basic usage
The panel is opened by its unique name value: acun:drawer.trigger, the acun:drawer:open event or, in JavaScript, AcunUI.drawer.open('catalog-details'). The X in the header, the Esc key, a click on the backdrop and a click on a link inside the panel close it. When it opens, focus moves to the field with autofocus, or else to the first focusable element in the panel; Tab cycles inside the panel, and when it closes, focus returns to the button that opened it. While the panel is open, the page behind it does not scroll.
<acun:drawer.trigger name="catalog-details"><acun:button variant="white">Open the customer summary</acun:button></acun:drawer.trigger>
<acun:drawer name="catalog-details" title="Customer summary">
<dl class="space-y-3 text-sm">
<div class="flex justify-between"><dt class="text-gray-500 dark:text-zinc-400">Membership</dt><dd>Premium</dd></div>
<div class="flex justify-between"><dt class="text-gray-500 dark:text-zinc-400">Orders</dt><dd>5</dd></div>
</dl>
<acun:slot:footer>
<acun:drawer.close><acun:button variant="white">Close</acun:button></acun:drawer.close>
<acun:button>Edit</acun:button>
</acun:slot:footer>
</acun:drawer>
| Prop | Values |
|---|---|
name | Unique name |
title | Header line (with the X button) |
footer | Slot: footer |
Side and width
side chooses which edge the panel opens from; the default is right. The width is set with size (md ≈ 448px, sm ≈ 320px) or with width, which takes a max-w-* class directly. On narrow screens the panel covers 85% of the screen.
<div class="flex flex-wrap gap-2" x-data>
<acun:drawer.trigger name="catalog-left"><acun:button variant="white">From the left, sm</acun:button></acun:drawer.trigger>
<acun:drawer.trigger name="catalog-wide"><acun:button variant="white">From the right, max-w-2xl</acun:button></acun:drawer.trigger>
</div>
<acun:drawer name="catalog-left" side="left" size="sm" title="Shortcuts">
<p class="text-sm">A narrow panel that opens from the left.</p>
</acun:drawer>
<acun:drawer name="catalog-wide" width="max-w-2xl" title="Order history">
<p class="text-sm">A wider panel for wide content such as tables.</p>
</acun:drawer>
| Prop | Values |
|---|---|
side | right | left |
size | sm | md |
width | max-w-* class (replaces size) |
Floating panel
floating shows the panel slightly inset from the screen edges, like a card with rounded corners. It suits short, self-contained content.
<acun:drawer.trigger name="catalog-floating"><acun:button variant="white">Open the floating panel</acun:button></acun:drawer.trigger>
<acun:drawer name="catalog-floating" floating title="Notifications">
<ul class="space-y-3 text-sm">
<li>12 orders were shipped from the Kadıköy warehouse.</li>
<li>Ayşe Yılmaz added a new customer.</li>
</ul>
</acun:drawer>
| Prop | Values |
|---|---|
floating | true | false |
Custom header
The header slot replaces the standard header line; you can build a rich top area with an avatar or tabs. In this case the X button is not added: put a close button in yourself with close() and give the panel an aria-label.
<acun:drawer.trigger name="catalog-person"><acun:button variant="white">Show the support agent</acun:button></acun:drawer.trigger>
<acun:drawer name="catalog-person" aria-label="Support agent">
<acun:slot:header>
<div class="flex items-center gap-3 pb-4">
<acun:avatar name="Ayşe Yılmaz" />
<div class="min-w-0 flex-1">
<p class="text-sm font-semibold text-gray-900 dark:text-zinc-100">Ayşe Yılmaz</p>
<p class="text-xs text-gray-500 dark:text-zinc-400">Support agent · Business accounts</p>
</div>
<acun:drawer.close><acun:button variant="ghost" size="icon" aria-label="Close"><acun:icon name="x-mark" /></acun:button></acun:drawer.close>
</div>
</acun:slot:header>
<p class="text-sm">Answered 112 support tickets this month.</p>
</acun:drawer>
| Prop | Values |
|---|---|
header | Slot: replaces the header line |
aria-label | Accessible name when there is no title |
Without a backdrop
:backdrop="false" removes the dimming; clicking outside the panel still closes it (that is set by closable-on-backdrop). :lock-scroll="false" lets the page behind scroll while the panel is open. Pass the values with a colon.
<acun:drawer.trigger name="catalog-plain"><acun:button variant="white">Open without a backdrop</acun:button></acun:drawer.trigger>
<acun:drawer name="catalog-plain" title="Help" :backdrop="false" :lock-scroll="false">
<p class="text-sm">Help text at the side while the page stays visible.</p>
</acun:drawer>
| Prop | Values |
|---|---|
backdrop | true | false |
lock-scroll | true | false |
Form panel
With :closable-on-backdrop="false", clicking the backdrop does not close the panel; this keeps a half-filled form from closing by accident. The panel still closes with X, Esc and the close button. When it opens, focus moves to the field with autofocus.
<acun:drawer.trigger name="catalog-form"><acun:button variant="white">Add a note</acun:button></acun:drawer.trigger>
<acun:drawer name="catalog-form" :closable-on-backdrop="false">
<acun:drawer.header title="Order note" description="#10482 · Ayşe Yılmaz" />
<acun:drawer.body class="space-y-4">
<acun:input name="subject" label="Subject" />
<acun:textarea name="note" label="Note" rows="4" autofocus />
</acun:drawer.body>
<acun:drawer.footer>
<acun:drawer.close><acun:button variant="white">Cancel</acun:button></acun:drawer.close>
<acun:drawer.close><acun:button>Save</acun:button></acun:drawer.close>
</acun:drawer.footer>
</acun:drawer>
| Prop | Values |
|---|---|
closable-on-backdrop | true | false |
autofocus | Field to focus on open |
Binding with Livewire
wire:model binds the panel to a Livewire property: the panel opens when the property is true and closes when it is false. When the user closes the panel (X, Esc, backdrop), the property becomes false as well; wire:model sends this with the next request, wire:model.live right away. This way you open the panel from the server without sending an event. It binds to Alpine data with x-model in the same way; give the variable a name different from the panel's own state names (rendered, entered, opened, model).
<acun:button wire:click="showDetails(4821)">Order details</acun:button>
<acun:drawer name="order-details" wire:model="detailsOpen">
<acun:drawer.header :title="'Order #'.$orderNo" description="Kadıköy warehouse · 3 items" />
<acun:drawer.body>
<p class="text-sm">Recipient: Ayşe Yılmaz · Status: Processing</p>
</acun:drawer.body>
<acun:drawer.footer>
<acun:drawer.close><acun:button variant="white">Close</acun:button></acun:drawer.close>
<acun:button wire:click="markShipped">Ship order</acun:button>
</acun:drawer.footer>
</acun:drawer>
| Prop | Values |
|---|---|
wire:model · wire:model.live | Livewire property (bool) |
x-model | Alpine data (bool) |
Livewire component
The component of the previous example. Setting the property to true is enough to open the panel.
use Livewire\Component;
class OrderDetails extends Component
{
public bool $detailsOpen = false;
public ?int $orderNo = null;
public function showDetails(int $no): void
{
$this->orderNo = $no;
$this->detailsOpen = true;
}
public function markShipped(): void
{
// … update the record
$this->detailsOpen = false;
}
}
Events
The panel listens for the acun:drawer:open, acun:drawer:close and acun:drawer:toggle events with { name }; when acun:drawer:close is sent without a name, all open panels close. In JavaScript the same commands are given with AcunUI.drawer.open('…'), AcunUI.drawer.close('…') and AcunUI.drawer.toggle('…'). The panel emits acun:drawer:opened when it opens and acun:drawer:closed the moment it starts closing, both with { name }. The original names (open-drawer, close-drawer, toggle-drawer and drawer-state-changed, which carries { name, open }) keep working as well. An open menu or dropdown inside the panel uses the Esc key first; the panel closes with the next Esc.
<div class="flex items-center gap-3" x-data="{ isOpen: false }"
x-on:acun:drawer:opened.window="if ($event.detail.name === 'catalog-notes') isOpen = true"
x-on:acun:drawer:closed.window="if ($event.detail.name === 'catalog-notes') isOpen = false">
<acun:drawer.trigger name="catalog-notes" toggle><acun:button variant="white">Notes</acun:button></acun:drawer.trigger>
<span class="text-sm" x-text="isOpen ? 'Panel open' : 'Panel closed'"></span>
</div>
<acun:drawer name="catalog-notes" title="Notes">
<p class="text-sm">Notes taken during the customer call.</p>
</acun:drawer>
| Prop | Values |
|---|---|
acun:drawer:open · acun:drawer:close · acun:drawer:toggle | Events listened for ({ name }) |
AcunUI.drawer.open · close · toggle | The same commands from JavaScript (name) |
acun:drawer:opened · acun:drawer:closed | Emitted events ({ name }) |
Filters panel (docked)
docked is for the Filters panel of list pages: on desktop (1024px and up) it is pinned to the left edge and, in the ready-made layouts, pushes the content to the right while focus stays on the page; on narrower screens it becomes a drawer with a backdrop that takes focus and keeps it inside. In this mode the header line is not rendered (title only becomes the accessible name), and side, size, width, floating, backdrop and header are not used. compact tightens the spacing in the footer.
<acun:filter-button drawer="catalog-filters" :count="1" />
<acun:drawer name="catalog-filters" docked title="Filters" compact>
<div class="space-y-4">
<acun:input name="f_code" placeholder="Custom Code" />
<acun:select name="f_status" :options="['' => 'All Statuses', '1' => 'Active', '0' => 'Inactive']" />
</div>
<acun:slot:footer>
<acun:button variant="outline" size="sm" class="flex-1">Clear</acun:button>
<acun:drawer.close><acun:button variant="outline-danger" size="sm" class="flex-1">Close</acun:button></acun:drawer.close>
</acun:slot:footer>
</acun:drawer>
| Prop | Values |
|---|---|
docked | true | false |
compact | true | false (footer spacing) |
Composable parts
acun:drawer.trigger opens the drawer (with toggle it opens and closes it; aria-expanded is updated automatically). The panel is built from the acun:drawer.header, acun:drawer.body and acun:drawer.footer parts; a button inside acun:drawer.close closes the panel.
<acun:drawer.trigger name="order-4821">
<acun:button variant="white">Order details</acun:button>
</acun:drawer.trigger>
<acun:drawer name="order-4821">
<acun:drawer.header title="Order #4821" description="Kadıköy warehouse · 3 items · Agent: Ayşe Yılmaz" />
<acun:drawer.body class="space-y-4">
<acun:input name="recipient" label="Recipient" value="Mehmet Kaya" />
<acun:select name="status" label="Order status" :options="['processing' => 'Processing', 'shipped' => 'Shipped', 'delivered' => 'Delivered']" />
<acun:textarea name="note" label="Agent note" rows="3" />
</acun:drawer.body>
<acun:drawer.footer>
<acun:drawer.close>
<acun:button variant="white">Cancel</acun:button>
</acun:drawer.close>
<acun:drawer.close>
<acun:button x-on:click="$dispatch('toast', { type: 'success', message: 'Order updated.' })">Save</acun:button>
</acun:drawer.close>
</acun:drawer.footer>
</acun:drawer>
| Prop | Values |
|---|---|
acun:drawer.trigger | name, toggle |
acun:drawer.header | title, description |
acun:drawer.body · footer | Body and footer |
acun:drawer.close | The element inside closes the panel |
Props and slots
The values the component accepts.
| Prop | Default | Description |
|---|---|---|
name | — | Required. The name used in the acun:drawer:open · acun:drawer:close · acun:drawer:toggle events and the AcunUI.drawer.open('…') call; acun:drawer:opened · acun:drawer:closed carry this name |
title | null | Header line (with X) and accessible name |
side | right | right · left |
size | md | md (max-w-md) · sm (max-w-xs) |
width | null | max-w-* class; when given, size is not used |
floating | false | Panel inset from the edges, with rounded corners |
backdrop | true | Dims the background |
lock-scroll | true | Keeps the page from scrolling while open |
closable-on-backdrop | true | Closes on a backdrop click |
wire:model · x-model | — | Binds the open/closed state to a Livewire property or Alpine data |
compact | false | Tight spacing in the footer |
docked | false | Filters panel behavior of list pages |
aria-label | null | Accessible name when there is no title; when empty, "Menu" (in the app language) |
body-class | flex-1 p-5 overflow-y-auto | Classes of the body wrapper |
drawer.trigger: name · toggle | — | Opens the panel; with toggle, closes it if open. aria-expanded is added to the button |
drawer.header: title · description | null | Header line and X |
drawer.body · drawer.footer | — | Scrolling body and footer (follow the compact and docked settings) |
drawer.close | — | Clicking the element inside closes the panel |