Accordion

開閉式リスト。v0.4.0 で nested 構文に変更 (Blade slot を XSS なく自然に渡せる)。<x-accordion-item title="...">content</x-accordion-item> を <x-accordion> の中に並べる。single-open / multiple-open、size 3段階。content は Blade slot のため任意の Blade markup・コンポーネント・リンク等が使える。Alpine x-collapse で滑らかにアニメーション。

single-open (default) — 1度に1つしか開かない

First section content.
Second section content.
Third section content.
<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_bfb86b94') ? null : 'item_bfb86b94'" x-bind:aria-expanded="open === 'item_bfb86b94'"><span>Section 1</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_bfb86b94' &amp;&amp; '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_bfb86b94'" 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_1930c926') ? null : 'item_1930c926'" x-bind:aria-expanded="open === 'item_1930c926'"><span>Section 2</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_1930c926' &amp;&amp; '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_1930c926'" 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_b078d10b') ? null : 'item_b078d10b'" x-bind:aria-expanded="open === 'item_b078d10b'"><span>Section 3</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_b078d10b' &amp;&amp; '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_b078d10b'" 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 — 複数同時に開ける

First section content.
Second section content.
Third section content.
<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_b3ecb74f') ? (open = open.filter(i =&gt; i !== 'item_b3ecb74f')) : open.push('item_b3ecb74f')" x-bind:aria-expanded="open.includes('item_b3ecb74f')"><span>Section 1</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open.includes('item_b3ecb74f') &amp;&amp; '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_b3ecb74f')" 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_43e8d6e0') ? (open = open.filter(i =&gt; i !== 'item_43e8d6e0')) : open.push('item_43e8d6e0')" x-bind:aria-expanded="open.includes('item_43e8d6e0')"><span>Section 2</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open.includes('item_43e8d6e0') &amp;&amp; '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_43e8d6e0')" 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_c7072675') ? (open = open.filter(i =&gt; i !== 'item_c7072675')) : open.push('item_c7072675')" x-bind:aria-expanded="open.includes('item_c7072675')"><span>Section 3</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open.includes('item_c7072675') &amp;&amp; '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_c7072675')" 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)

Compact rows.
Second section content.
Standard rows.
Second section content.
Roomy rows.
Second section content.
<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_6e85c075') ? null : 'item_6e85c075'" x-bind:aria-expanded="open === 'item_6e85c075'"><span>size=sm</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_6e85c075' &amp;&amp; '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_6e85c075'" 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_7f654292') ? null : 'item_7f654292'" x-bind:aria-expanded="open === 'item_7f654292'"><span>Section 2</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_7f654292' &amp;&amp; '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_7f654292'" 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_6ae77fb7') ? null : 'item_6ae77fb7'" x-bind:aria-expanded="open === 'item_6ae77fb7'"><span>size=md (default)</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_6ae77fb7' &amp;&amp; '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_6ae77fb7'" 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_6ca06dd6') ? null : 'item_6ca06dd6'" x-bind:aria-expanded="open === 'item_6ca06dd6'"><span>Section 2</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_6ca06dd6' &amp;&amp; '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_6ca06dd6'" 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_4b83da70') ? null : 'item_4b83da70'" x-bind:aria-expanded="open === 'item_4b83da70'"><span>size=lg</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_4b83da70' &amp;&amp; '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_4b83da70'" 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_137cd0e6') ? null : 'item_137cd0e6'" x-bind:aria-expanded="open === 'item_137cd0e6'"><span>Section 2</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_137cd0e6' &amp;&amp; '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_137cd0e6'" 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_0d280d6d') ? null : 'item_0d280d6d'" x-bind:aria-expanded="open === 'item_0d280d6d'"><span>With a link</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_0d280d6d' &amp;&amp; '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_0d280d6d'" 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_04fc18c6') ? null : 'item_04fc18c6'" x-bind:aria-expanded="open === 'item_04fc18c6'"><span>With a list</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_04fc18c6' &amp;&amp; '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_04fc18c6'" 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_18a4a566') ? null : 'item_18a4a566'" x-bind:aria-expanded="open === 'item_18a4a566'"><span>Multiple paragraphs</span>
<svg class="w-4 h-4 shrink-0 transition-transform duration-200" x-bind:class="open === 'item_18a4a566' &amp;&amp; '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_18a4a566'" 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)

Tailwind v4 + daisyUI v5 + Alpine.js をベースにした、Yakaze Tech Studio 内製の Blade コンポーネントライブラリです。プロジェクト横断で UI 一貫性を担保することを目的としています。
composer require sparrowhawk-labs/pinion-ui のあと、php artisan ui:install で必要な resource を wire-up してください。
はい。pinion-ui は daisyUI の class を内部で利用しているため、同じ theme variable を共有します。<x-...> と通常の daisyUI markup を併用しても問題ありません。
daisyUI v5 の @plugin "daisyui/theme" ディレクティブでテーマを定義してください。pinion-ui の class はすべて theme variable 経由なので、テーマを変えるだけで配色が追従します。
GitHub Issues で報告してください。再現手順とスクリーンショットがあると助かります。sparrowhawk-labs/pinion-ui#issues

FAQ (multiple-open) — 比較対象を同時に見たい時

Tailwind v4 + daisyUI v5 + Alpine.js をベースにした、Blade コンポーネントライブラリ。
composer requirephp artisan ui:install
v0.4.0 から light only (pinion)。dark が必要なら daisyUI 標準 dark / dim / night を <html data-theme> で指定。