Sidebar

x-teleport 繪製到 body 直下的抽屜式元件。trigger 槽位點擊開啟,Escape / × / 背景點擊關閉。side (left/right)、size (sm/md/lg)、backdrop 3 軸構成變體。焦點捕獲 (x-trap.inert.noscroll) 和 role=dialog/aria-modal=true 確保無障礙。backdrop=false 時整個 overlay 設 pointer-events-none,面板單獨改回 pointer-events-auto,允許背後頁面操作 (永久 inspector、工具調色板用途)。

1. 最簡: 附帶 trigger slot (default = side=left, size=md, 有 backdrop)

<x-sidebar>
    <x-slot:trigger>
        <x-button>Open</x-button>
    </x-slot:trigger>
    <p>Drawer content</p>
</x-sidebar>
<div x-data="{ open: false }" x-on:open-sidebar-sidebar_6a7e9b87e8fc9.window="open = true" x-on:close-sidebar-sidebar_6a7e9b87e8fc9.window="open = false">
<div @click="open = true" class="cursor-pointer">
<button class="inline-flex items-center justify-center whitespace-nowrap font-medium tracking-wide transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 rounded-[var(--radius-field)] border-[length:var(--border)] h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] gap-2 bg-primary text-primary-content border-primary hover:bg-primary/90 focus-visible:ring-primary cursor-pointer">Open</button>
</div>
<template x-teleport="body">
<div x-show="open" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-150" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="fixed inset-0 z-50" x-cloak="">
<div class="absolute inset-0 bg-black/50" @click="open = false"></div>
<div x-show="open" x-transition:enter="transition ease-out duration-300" x-transition:enter-start="translate-x-[-100%]" x-transition:enter-end="translate-x-0" x-transition:leave="transition ease-in duration-200" x-transition:leave-start="translate-x-0" x-transition:leave-end="translate-x-[-100%]" x-trap.inert.noscroll="open" @keydown.escape.window="open = false" class="absolute top-0 left-0 h-full w-80 bg-base-100 text-base-content border-r border-base-300 shadow-[0_0_40px_-8px_rgb(0_0_0_/_0.20),var(--shadow-box)] p-lg overflow-y-auto" role="dialog" aria-modal="true">
<button type="button" @click="open = false" class="absolute top-lg right-lg z-10 text-base-content/50 hover:text-base-content transition-colors rounded-[var(--radius-field)] p-1 hover:bg-base-200" aria-label="Close"><svg class="w-5 h-5" fill="none" viewbox="0 0 24 24" stroke="currentcolor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path></svg></button>
<p>
                    Drawer content
                </p>
</div>
</div>
</template>
</div>

2. 右側配置 + lg 尺寸

<x-sidebar side="right" size="lg">
    <x-slot:trigger><x-button>Open details</x-button></x-slot:trigger>
    <p>Details panel</p>
</x-sidebar>
<div x-data="{ open: false }" x-on:open-sidebar-sidebar_6a7e9b87f0e7e.window="open = true" x-on:close-sidebar-sidebar_6a7e9b87f0e7e.window="open = false">
<div @click="open = true" class="cursor-pointer">
<button class="inline-flex items-center justify-center whitespace-nowrap font-medium tracking-wide transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 rounded-[var(--radius-field)] border-[length:var(--border)] h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] gap-2 bg-primary text-primary-content border-primary hover:bg-primary/90 focus-visible:ring-primary cursor-pointer">Open details</button>
</div>
<template x-teleport="body">
<div x-show="open" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-150" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="fixed inset-0 z-50" x-cloak="">
<div class="absolute inset-0 bg-black/50" @click="open = false"></div>
<div x-show="open" x-transition:enter="transition ease-out duration-300" x-transition:enter-start="translate-x-[100%]" x-transition:enter-end="translate-x-0" x-transition:leave="transition ease-in duration-200" x-transition:leave-start="translate-x-0" x-transition:leave-end="translate-x-[100%]" x-trap.inert.noscroll="open" @keydown.escape.window="open = false" class="absolute top-0 right-0 h-full w-96 bg-base-100 text-base-content border-l border-base-300 shadow-[0_0_40px_-8px_rgb(0_0_0_/_0.20),var(--shadow-box)] p-lg overflow-y-auto" role="dialog" aria-modal="true">
<button type="button" @click="open = false" class="absolute top-lg right-lg z-10 text-base-content/50 hover:text-base-content transition-colors rounded-[var(--radius-field)] p-1 hover:bg-base-200" aria-label="Close"><svg class="w-5 h-5" fill="none" viewbox="0 0 24 24" stroke="currentcolor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path></svg></button>
<p>
                    Details panel
                </p>
</div>
</div>
</template>
</div>

3. 無 backdrop — 用於常駐 inspector / 工具面板情境。overlay 為 pointer-events-none,僅 panel 為 pointer-events-auto,因此可在保持 drawer 開啟的同時操作主頁面

<x-sidebar :backdrop="false">
    <x-slot:trigger><x-button>Inspector</x-button></x-slot:trigger>
    <p>Inspector content — the page behind stays interactive.</p>
</x-sidebar>
<div x-data="{ open: false }" x-on:open-sidebar-sidebar_6a7e9b880473b.window="open = true" x-on:close-sidebar-sidebar_6a7e9b880473b.window="open = false">
<div @click="open = true" class="cursor-pointer">
<button class="inline-flex items-center justify-center whitespace-nowrap font-medium tracking-wide transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 rounded-[var(--radius-field)] border-[length:var(--border)] h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] gap-2 bg-primary text-primary-content border-primary hover:bg-primary/90 focus-visible:ring-primary cursor-pointer">Inspector</button>
</div>
<template x-teleport="body">
<div x-show="open" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-150" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="fixed inset-0 z-50 pointer-events-none" x-cloak="">
<div x-show="open" x-transition:enter="transition ease-out duration-300" x-transition:enter-start="translate-x-[-100%]" x-transition:enter-end="translate-x-0" x-transition:leave="transition ease-in duration-200" x-transition:leave-start="translate-x-0" x-transition:leave-end="translate-x-[-100%]" x-trap.inert.noscroll="open" @keydown.escape.window="open = false" class="absolute top-0 left-0 h-full w-80 bg-base-100 text-base-content border-r border-base-300 shadow-[0_0_40px_-8px_rgb(0_0_0_/_0.20),var(--shadow-box)] p-lg overflow-y-auto pointer-events-auto" role="dialog" aria-modal="true">
<button type="button" @click="open = false" class="absolute top-lg right-lg z-10 text-base-content/50 hover:text-base-content transition-colors rounded-[var(--radius-field)] p-1 hover:bg-base-200" aria-label="Close"><svg class="w-5 h-5" fill="none" viewbox="0 0 24 24" stroke="currentcolor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path></svg></button>
<p>
                    Inspector content — the page behind stays interactive.
                </p>
</div>
</div>
</template>
</div>

4. 指定 id + 透過 dispatch 從別處開啟

<x-sidebar id="filters" side="right">
    <p>Filter UI</p>
</x-sidebar>

<x-button x-on:click="$dispatch('open-sidebar-filters')">Filters</x-button>
<div x-data="{ open: false }" x-on:open-sidebar-filters.window="open = true" x-on:close-sidebar-filters.window="open = false">
<template x-teleport="body">
<div x-show="open" x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:leave="transition ease-in duration-150" x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0" class="fixed inset-0 z-50" x-cloak="">
<div class="absolute inset-0 bg-black/50" @click="open = false"></div>
<div x-show="open" x-transition:enter="transition ease-out duration-300" x-transition:enter-start="translate-x-[100%]" x-transition:enter-end="translate-x-0" x-transition:leave="transition ease-in duration-200" x-transition:leave-start="translate-x-0" x-transition:leave-end="translate-x-[100%]" x-trap.inert.noscroll="open" @keydown.escape.window="open = false" class="absolute top-0 right-0 h-full w-80 bg-base-100 text-base-content border-l border-base-300 shadow-[0_0_40px_-8px_rgb(0_0_0_/_0.20),var(--shadow-box)] p-lg overflow-y-auto" role="dialog" aria-modal="true">
<button type="button" @click="open = false" class="absolute top-lg right-lg z-10 text-base-content/50 hover:text-base-content transition-colors rounded-[var(--radius-field)] p-1 hover:bg-base-200" aria-label="Close"><svg class="w-5 h-5" fill="none" viewbox="0 0 24 24" stroke="currentcolor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path></svg></button>
<p>
                    Filter UI
                </p>
</div>
</div>
</template>
</div><button class="inline-flex items-center justify-center whitespace-nowrap font-medium tracking-wide transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 rounded-[var(--radius-field)] border-[length:var(--border)] h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] gap-2 bg-primary text-primary-content border-primary hover:bg-primary/90 focus-visible:ring-primary cursor-pointer" x-on:click="$dispatch('open-sidebar-filters')">Filters</button>

side=left / size=md / backdrop=true / closeOnBackdrop=true / escape=true — 在各 iframe 內按下 trigger 查看效果

左側 (default) — side=left, size=md, backdrop=true

右側 — side=right, size=md, backdrop=true

no-backdrop — :backdrop="false" / 背景可操作

with-content — 實用的導覽範例 (logo + menu + user)

props 一覽

  • id — string|null (default: sidebar_{uniqid()})。透過 dispatch 從外部開關時指定
  • side'left' | 'right' (default: 'left')
  • size'sm' (w-64) | 'md' (w-80) | 'lg' (w-96) (default: 'md')
  • backdrop — bool (default: true)。false 時無半透明黑色蒙層,背景可操作
  • closeOnBackdrop — bool (default: true)。僅在 backdrop=true 時生效
  • escape — bool (default: true)。按 Escape 鍵關閉

props: id, side='left', size='md', backdrop=true, closeOnBackdrop=true, escape=true / size: sm (w-64) / md (w-80, default) / lg (w-96) / a11y: role=dialog, aria-modal=true, x-trap.inert.noscroll / 渲染: 透過 x-teleport 傳送至 body 直屬 / slide-in 動畫