Skip to content

Collapsible

Collapsible
Thymeleaf source
<sl:collapsible>
<sl:collapsible-trigger>@peduarte starred 3 repositories <sl:icon name="chevrons-up-down"/></sl:collapsible-trigger>
<sl:collapsible-content>
<p>@radix-ui/primitives</p>
<p>@radix-ui/colors</p>
<p>@stitches/react</p>
</sl:collapsible-content>
</sl:collapsible>
Rendered HTML
<details class="collapsible">
<summary class="collapsible-trigger">
@peduarte starred 3 repositories
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="m7 15 5 5 5-5"></path>
<path d="m7 9 5-5 5 5"></path>
</svg>
</summary>
<div class="collapsible-content">
<p>@radix-ui/primitives</p>
<p>@radix-ui/colors</p>
<p>@stitches/react</p>
</div>
</details>

A collapsible is <sl:collapsible> with an <sl:collapsible-trigger> and an <sl:collapsible-content>:

<sl:collapsible>
<sl:collapsible-trigger>@peduarte starred 3 repositories <sl:icon name="chevrons-up-down"/></sl:collapsible-trigger>
<sl:collapsible-content>
<p>@radix-ui/primitives</p>
<p>@radix-ui/colors</p>
</sl:collapsible-content>
</sl:collapsible>

It is a <details> element with the trigger as its <summary>, so it needs no JavaScript: the browser opens and closes it, from the keyboard too, and tells a screen reader whether it is expanded. Two consequences for porting a shadcn/ui example:

  • The trigger comes first, and the content after it, both inside <sl:collapsible>. shadcn/ui’s examples can put the trigger anywhere in the collapsible, even after the content; a summary must be the first child.
  • Everything else goes in the trigger or the content. Text next to the trigger (shadcn’s “@peduarte starred 3 repositories” heading) can be part of the trigger.

open renders it open. As with the accordion, find-in-page and a link to an element inside open a closed collapsible on a match (Chrome and Edge 97, Firefox 148, Safari 26.2). It does not animate, as shadcn/ui’s does not.

The trigger is plain text by default. variant or size draws it as a button, with the button’s variants and sizes, where shadcn/ui writes <CollapsibleTrigger asChild><Button …>. An icon-only trigger (size="icon") needs an aria-label.

Drawn as a buttonvariant and size draw the trigger as a button, as shadcn/ui's asChild on a Button does.
Thymeleaf source
<sl:collapsible open>
<sl:collapsible-trigger variant="outline" size="sm"><sl:icon name="sliders-horizontal"/> Filters</sl:collapsible-trigger>
<sl:collapsible-content>
<sl:field orientation="horizontal" for="collapsible-in-stock">
<sl:checkbox checked/>
<sl:field-label>In stock only</sl:field-label>
</sl:field>
</sl:collapsible-content>
</sl:collapsible>
Rendered HTML
<details class="collapsible" open>
<summary class="button collapsible-trigger" data-variant="outline" data-size="sm">
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M10 5H3"></path>
<path d="M12 19H3"></path>
<path d="M14 3v4"></path>
<path d="M16 17v4"></path>
<path d="M21 12h-9"></path>
<path d="M21 19h-5"></path>
<path d="M21 5h-7"></path>
<path d="M8 10v4"></path>
<path d="M8 12H3"></path>
</svg>
Filters
</summary>
<div class="collapsible-content">
<div class="field" data-orientation="horizontal">
<span class="checkbox-wrapper">
<input type="checkbox" class="checkbox" checked id="collapsible-in-stock">
<svg xmlns="http://www.w3.org/2000/svg" class="icon checkbox-indicator" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M20 6 9 17l-5-5"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" class="icon checkbox-indeterminate-indicator" viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">
<path d="M5 12h14"></path>
</svg>
</span>
<label class="label field-label" for="collapsible-in-stock">In stock only</label>
</div>
</div>
</details>

disabled keeps it closed (or open, with open): the trigger fades, leaves the tab order and ignores the pointer, and a closed disabled collapsible renders no content, so find-in-page cannot open it.

Disabled
Thymeleaf source
<sl:collapsible disabled>
<sl:collapsible-trigger variant="ghost" size="sm">Order history</sl:collapsible-trigger>
<sl:collapsible-content>Your past orders.</sl:collapsible-content>
</sl:collapsible>
Rendered HTML
<details class="collapsible" data-disabled="true">
<summary class="button collapsible-trigger" data-variant="ghost" data-size="sm" aria-disabled="true" tabindex="-1">Order history</summary>
</details>
  • asChild on the trigger: variant and size draw it as a button; the element stays a summary.
  • open/onOpenChange state: open renders it open; the browser’s toggle event reports changes.
  • A trigger outside the collapsible, or after the content: a details element has its summary first.
  • Styling the trigger’s icon by state (shadcn’s group-data-[state=open]:rotate-90): the bundle has no utility classes. In your own CSS, .collapsible[open] > .collapsible-trigger svg { rotate: 90deg; } does it.
AttributeValuesDefaultDescription
openboolean—Render it open.
disabledboolean—It cannot be opened or closed: its trigger is out of the tab order and ignores the pointer, and closed it renders no content, so find-in-page cannot open it.

Every prop can also be set from an expression: th:open="${...}". Any other attribute passes through to the rendered element, and class is appended.

AttributeValuesDefaultDescription
variantdefault outline ghost destructive secondary link—Draw the trigger as a button of this variant, as on sl:button.
sizexs sm default lg icon icon-xs icon-sm icon-lg—Draw the trigger as a button of this size, as on sl:button. The icon sizes need an aria-label.

Every prop can also be set from an expression: th:variant="${...}". Any other attribute passes through to the rendered element, and class is appended. An aria-label or aria-labelledby is required when size is icon, icon-xs, icon-sm or icon-lg; rendering fails without one.

sl:collapsible-content declares no props; every attribute passes through.