Accordion
可摺疊列表。v0.4.0 改為巢狀語法 (Blade 槽位可自然傳遞,無 XSS 顧慮)。<x-accordion-item title="...">content</x-accordion-item> 列在 <x-accordion> 內。支援 single-open / multiple-open,尺寸 3 階段。content 為 Blade 槽位,可使用任意 Blade 標記、元件、連結等。Alpine x-collapse 提供平滑動畫。
single-open (default) — 同一時間只展開一個
<x-accordion>
<x-accordion-item title="Section 1">First section content.</x-accordion-item>
<x-accordion-item title="Section 2">Second section content.</x-accordion-item>
<x-accordion-item title="Section 3">Third section content.</x-accordion-item>
</x-accordion>
<div x-data="{ open: null }" class="w-full divide-y divide-base-300 radius-box tune-border border-base-300 overflow-hidden">
<div>
<button type="button" class="w-full flex items-center justify-between min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-0 font-medium text-base-content hover:bg-base-200/50 transition-colors cursor-pointer" x-on:click="open = (open === 'item_5a9f7d69') ? null : 'item_5a9f7d69'" x-bind:aria-expanded="open === 'item_5a9f7d69'"><span>Section 1</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_5a9f7d69' && 'rotate-180'" fill="none" viewbox="0 0 24 24" stroke="currentcolor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path></svg></button>
<div x-show="open === 'item_5a9f7d69'" x-collapse="" x-cloak="">
<div class="min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-sm text-base-content/70">
First section content.
</div>
</div>
</div>
<div>
<button type="button" class="w-full flex items-center justify-between min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-0 font-medium text-base-content hover:bg-base-200/50 transition-colors cursor-pointer" x-on:click="open = (open === 'item_60a6ee82') ? null : 'item_60a6ee82'" x-bind:aria-expanded="open === 'item_60a6ee82'"><span>Section 2</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_60a6ee82' && 'rotate-180'" fill="none" viewbox="0 0 24 24" stroke="currentcolor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path></svg></button>
<div x-show="open === 'item_60a6ee82'" x-collapse="" x-cloak="">
<div class="min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-sm text-base-content/70">
Second section content.
</div>
</div>
</div>
<div>
<button type="button" class="w-full flex items-center justify-between min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-0 font-medium text-base-content hover:bg-base-200/50 transition-colors cursor-pointer" x-on:click="open = (open === 'item_7b1ecd60') ? null : 'item_7b1ecd60'" x-bind:aria-expanded="open === 'item_7b1ecd60'"><span>Section 3</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_7b1ecd60' && 'rotate-180'" fill="none" viewbox="0 0 24 24" stroke="currentcolor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path></svg></button>
<div x-show="open === 'item_7b1ecd60'" x-collapse="" x-cloak="">
<div class="min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-sm text-base-content/70">
Third section content.
</div>
</div>
</div>
</div>
multiple-open — 可同時展開多個
<x-accordion :multiple="true">
<x-accordion-item title="Section 1">First section content.</x-accordion-item>
<x-accordion-item title="Section 2">Second section content.</x-accordion-item>
<x-accordion-item title="Section 3">Third section content.</x-accordion-item>
</x-accordion>
<div x-data="{ open: [] }" class="w-full divide-y divide-base-300 radius-box tune-border border-base-300 overflow-hidden">
<div>
<button type="button" class="w-full flex items-center justify-between min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-0 font-medium text-base-content hover:bg-base-200/50 transition-colors cursor-pointer" x-on:click="open.includes('item_bef9a482') ? (open = open.filter(i => i !== 'item_bef9a482')) : open.push('item_bef9a482')" x-bind:aria-expanded="open.includes('item_bef9a482')"><span>Section 1</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open.includes('item_bef9a482') && 'rotate-180'" fill="none" viewbox="0 0 24 24" stroke="currentcolor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path></svg></button>
<div x-show="open.includes('item_bef9a482')" x-collapse="" x-cloak="">
<div class="min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-sm text-base-content/70">
First section content.
</div>
</div>
</div>
<div>
<button type="button" class="w-full flex items-center justify-between min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-0 font-medium text-base-content hover:bg-base-200/50 transition-colors cursor-pointer" x-on:click="open.includes('item_79082719') ? (open = open.filter(i => i !== 'item_79082719')) : open.push('item_79082719')" x-bind:aria-expanded="open.includes('item_79082719')"><span>Section 2</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open.includes('item_79082719') && 'rotate-180'" fill="none" viewbox="0 0 24 24" stroke="currentcolor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path></svg></button>
<div x-show="open.includes('item_79082719')" x-collapse="" x-cloak="">
<div class="min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-sm text-base-content/70">
Second section content.
</div>
</div>
</div>
<div>
<button type="button" class="w-full flex items-center justify-between min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-0 font-medium text-base-content hover:bg-base-200/50 transition-colors cursor-pointer" x-on:click="open.includes('item_df734f7d') ? (open = open.filter(i => i !== 'item_df734f7d')) : open.push('item_df734f7d')" x-bind:aria-expanded="open.includes('item_df734f7d')"><span>Section 3</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open.includes('item_df734f7d') && 'rotate-180'" fill="none" viewbox="0 0 24 24" stroke="currentcolor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path></svg></button>
<div x-show="open.includes('item_df734f7d')" x-collapse="" x-cloak="">
<div class="min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-sm text-base-content/70">
Third section content.
</div>
</div>
</div>
</div>
尺寸 (sm / md / lg)
<x-accordion size="sm">
<x-accordion-item title="size=sm">Compact rows.</x-accordion-item>
<x-accordion-item title="Section 2">Second section content.</x-accordion-item>
</x-accordion>
<x-accordion size="md">
<x-accordion-item title="size=md (default)">Standard rows.</x-accordion-item>
<x-accordion-item title="Section 2">Second section content.</x-accordion-item>
</x-accordion>
<x-accordion size="lg">
<x-accordion-item title="size=lg">Roomy rows.</x-accordion-item>
<x-accordion-item title="Section 2">Second section content.</x-accordion-item>
</x-accordion>
<div x-data="{ open: null }" class="w-full divide-y divide-base-300 radius-box tune-border border-base-300 overflow-hidden">
<div>
<button type="button" class="w-full flex items-center justify-between min-h-[var(--h-field-sm)] px-[var(--px-field-sm)] text-[length:var(--text-field-sm)] py-0 font-medium text-base-content hover:bg-base-200/50 transition-colors cursor-pointer" x-on:click="open = (open === 'item_eab7a8e9') ? null : 'item_eab7a8e9'" x-bind:aria-expanded="open === 'item_eab7a8e9'"><span>size=sm</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_eab7a8e9' && 'rotate-180'" fill="none" viewbox="0 0 24 24" stroke="currentcolor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path></svg></button>
<div x-show="open === 'item_eab7a8e9'" x-collapse="" x-cloak="">
<div class="min-h-[var(--h-field-sm)] px-[var(--px-field-sm)] text-[length:var(--text-field-sm)] py-sm text-base-content/70">
Compact rows.
</div>
</div>
</div>
<div>
<button type="button" class="w-full flex items-center justify-between min-h-[var(--h-field-sm)] px-[var(--px-field-sm)] text-[length:var(--text-field-sm)] py-0 font-medium text-base-content hover:bg-base-200/50 transition-colors cursor-pointer" x-on:click="open = (open === 'item_c6b25c15') ? null : 'item_c6b25c15'" x-bind:aria-expanded="open === 'item_c6b25c15'"><span>Section 2</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_c6b25c15' && 'rotate-180'" fill="none" viewbox="0 0 24 24" stroke="currentcolor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path></svg></button>
<div x-show="open === 'item_c6b25c15'" x-collapse="" x-cloak="">
<div class="min-h-[var(--h-field-sm)] px-[var(--px-field-sm)] text-[length:var(--text-field-sm)] py-sm text-base-content/70">
Second section content.
</div>
</div>
</div>
</div>
<div x-data="{ open: null }" class="w-full divide-y divide-base-300 radius-box tune-border border-base-300 overflow-hidden">
<div>
<button type="button" class="w-full flex items-center justify-between min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-0 font-medium text-base-content hover:bg-base-200/50 transition-colors cursor-pointer" x-on:click="open = (open === 'item_0ce8f1a8') ? null : 'item_0ce8f1a8'" x-bind:aria-expanded="open === 'item_0ce8f1a8'"><span>size=md (default)</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_0ce8f1a8' && 'rotate-180'" fill="none" viewbox="0 0 24 24" stroke="currentcolor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path></svg></button>
<div x-show="open === 'item_0ce8f1a8'" x-collapse="" x-cloak="">
<div class="min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-sm text-base-content/70">
Standard rows.
</div>
</div>
</div>
<div>
<button type="button" class="w-full flex items-center justify-between min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-0 font-medium text-base-content hover:bg-base-200/50 transition-colors cursor-pointer" x-on:click="open = (open === 'item_7d029ccf') ? null : 'item_7d029ccf'" x-bind:aria-expanded="open === 'item_7d029ccf'"><span>Section 2</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_7d029ccf' && 'rotate-180'" fill="none" viewbox="0 0 24 24" stroke="currentcolor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path></svg></button>
<div x-show="open === 'item_7d029ccf'" x-collapse="" x-cloak="">
<div class="min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-sm text-base-content/70">
Second section content.
</div>
</div>
</div>
</div>
<div x-data="{ open: null }" class="w-full divide-y divide-base-300 radius-box tune-border border-base-300 overflow-hidden">
<div>
<button type="button" class="w-full flex items-center justify-between min-h-[var(--h-field-lg)] px-[var(--px-field-lg)] text-[length:var(--text-field-lg)] py-0 font-medium text-base-content hover:bg-base-200/50 transition-colors cursor-pointer" x-on:click="open = (open === 'item_89da5135') ? null : 'item_89da5135'" x-bind:aria-expanded="open === 'item_89da5135'"><span>size=lg</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_89da5135' && 'rotate-180'" fill="none" viewbox="0 0 24 24" stroke="currentcolor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path></svg></button>
<div x-show="open === 'item_89da5135'" x-collapse="" x-cloak="">
<div class="min-h-[var(--h-field-lg)] px-[var(--px-field-lg)] text-[length:var(--text-field-lg)] py-sm text-base-content/70">
Roomy rows.
</div>
</div>
</div>
<div>
<button type="button" class="w-full flex items-center justify-between min-h-[var(--h-field-lg)] px-[var(--px-field-lg)] text-[length:var(--text-field-lg)] py-0 font-medium text-base-content hover:bg-base-200/50 transition-colors cursor-pointer" x-on:click="open = (open === 'item_857a4aca') ? null : 'item_857a4aca'" x-bind:aria-expanded="open === 'item_857a4aca'"><span>Section 2</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_857a4aca' && 'rotate-180'" fill="none" viewbox="0 0 24 24" stroke="currentcolor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path></svg></button>
<div x-show="open === 'item_857a4aca'" x-collapse="" x-cloak="">
<div class="min-h-[var(--h-field-lg)] px-[var(--px-field-lg)] text-[length:var(--text-field-lg)] py-sm text-base-content/70">
Second section content.
</div>
</div>
</div>
</div>
豐富內容 — 因為是 Blade slot,任意 markup / 元件 / 連結 / 清單、多段落都能自然書寫
Content containing a link to an external resource. See the official docs.
- Bullet 1
- Bullet 2
- Bullet 3
The body is a Blade slot, so any HTML or Blade component can be embedded.
A second paragraph. Line breaks and styling are free-form.
<x-accordion>
<x-accordion-item title="With a link">
<p>Content containing a link to an external resource. See the <a href="#" class="text-primary underline underline-offset-2 hover:opacity-80">official docs</a>.</p>
</x-accordion-item>
<x-accordion-item title="With a list">
<ul class="list-disc list-inside space-y-1">
<li>Bullet 1</li>
<li>Bullet 2</li>
<li>Bullet 3</li>
</ul>
</x-accordion-item>
<x-accordion-item title="Multiple paragraphs">
<p class="mb-2">The body is a Blade slot, so any HTML or Blade component can be embedded.</p>
<p>A second paragraph. Line breaks and styling are free-form.</p>
</x-accordion-item>
</x-accordion>
<div x-data="{ open: null }" class="w-full divide-y divide-base-300 radius-box tune-border border-base-300 overflow-hidden">
<div>
<button type="button" class="w-full flex items-center justify-between min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-0 font-medium text-base-content hover:bg-base-200/50 transition-colors cursor-pointer" x-on:click="open = (open === 'item_7afd31c3') ? null : 'item_7afd31c3'" x-bind:aria-expanded="open === 'item_7afd31c3'"><span>With a link</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_7afd31c3' && 'rotate-180'" fill="none" viewbox="0 0 24 24" stroke="currentcolor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path></svg></button>
<div x-show="open === 'item_7afd31c3'" x-collapse="" x-cloak="">
<div class="min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-sm text-base-content/70">
<p>
Content containing a link to an external resource. See the <a href="#" class="text-primary underline underline-offset-2 hover:opacity-80">official docs</a>.
</p>
</div>
</div>
</div>
<div>
<button type="button" class="w-full flex items-center justify-between min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-0 font-medium text-base-content hover:bg-base-200/50 transition-colors cursor-pointer" x-on:click="open = (open === 'item_d94a729e') ? null : 'item_d94a729e'" x-bind:aria-expanded="open === 'item_d94a729e'"><span>With a list</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_d94a729e' && 'rotate-180'" fill="none" viewbox="0 0 24 24" stroke="currentcolor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path></svg></button>
<div x-show="open === 'item_d94a729e'" x-collapse="" x-cloak="">
<div class="min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-sm text-base-content/70">
<ul class="list-disc list-inside space-y-1">
<li>Bullet 1
</li>
<li>Bullet 2
</li>
<li>Bullet 3
</li>
</ul>
</div>
</div>
</div>
<div>
<button type="button" class="w-full flex items-center justify-between min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-0 font-medium text-base-content hover:bg-base-200/50 transition-colors cursor-pointer" x-on:click="open = (open === 'item_658f88be') ? null : 'item_658f88be'" x-bind:aria-expanded="open === 'item_658f88be'"><span>Multiple paragraphs</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_658f88be' && 'rotate-180'" fill="none" viewbox="0 0 24 24" stroke="currentcolor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"></path></svg></button>
<div x-show="open === 'item_658f88be'" x-collapse="" x-cloak="">
<div class="min-h-[var(--h-field-md)] px-[var(--px-field-md)] text-[length:var(--text-field-md)] py-sm text-base-content/70">
<p class="mb-2">
The body is a Blade slot, so any HTML or Blade component can be embedded.
</p>
<p>
A second paragraph. Line breaks and styling are free-form.
</p>
</div>
</div>
</div>
</div>
FAQ 展示 — 實際使用情境 (single-open)
composer require sparrowhawk-labs/pinion-ui,再執行 php artisan ui:install 設定所需資源。
@plugin "daisyui/theme" 指令定義主題。pinion-ui 的 class 全部透過 theme variable,只要切換主題配色就會自動跟著變。
FAQ (multiple-open) — 想同時查看多個比較項目時
composer require → php artisan ui:install。
dark / dim / night。