Sidebar

x-teleport で body 直下に描画される drawer 系コンポーネント。trigger slot を click で開き、Escape / × / 背景 click で閉じる。side (left/right)・size (sm/md/lg)・backdrop の 3 軸で variant を作る。focus trap (x-trap.inert.noscroll) と role=dialog/aria-modal=true で a11y も担保。backdrop=false の時は overlay 全体を pointer-events-none にして、panel のみ 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_6a7e9b5b15dc2.window="open = true" x-on:close-sidebar-sidebar_6a7e9b5b15dc2.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_6a7e9b5b1e156.window="open = true" x-on:close-sidebar-sidebar_6a7e9b5b1e156.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_6a7e9b5b2615f.window="open = true" x-on:close-sidebar-sidebar_6a7e9b5b2615f.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 で半透明黒の dim 無し + 背景操作可能
  • 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 アニメーション