Clamps long content to a line count and reveals the rest behind a "Show more" toggle. Use for memos, notes and descriptions that would otherwise push the rest of the page down.
Usage
<sui-spoiler lines="2">
Fall supplies order for the New Albany dock. Confirm pallet counts with receiving before 3 PM. The vendor asked us to split the freight across two invoices, so match both against PO-2211 when the feed lands. Terms are Net 30 from the later of the two invoice dates.
</sui-spoiler>
<script type="module">
import { defineCustomElements } from "@shineui/core/loader";
import "@shineui/core/shineui.css";
defineCustomElements();
const el = document.querySelector("sui-spoiler");
el.addEventListener("suiClose", (e) => console.log(e.detail));
</script>
import { SuiSpoiler } from "@shineui/react";
import "@shineui/core/shineui.css";
export function Example() {
return (
<SuiSpoiler lines="2" onSuiClose={(e) => console.log(e.detail)}>
Fall supplies order for the New Albany dock. Confirm pallet counts with receiving before 3 PM. The vendor asked us to split the freight across two invoices, so match both against PO-2211 when the feed lands. Terms are Net 30 from the later of the two invoice dates.
</SuiSpoiler>
);
}
<script setup>
import { SuiSpoiler } from "@shineui/vue";
import "@shineui/core/shineui.css";
const onChange = (e) => console.log(e.detail);
</script>
<template>
<SuiSpoiler lines="2" @sui-close="onChange">Fall supplies order for the New Albany dock. Confirm pallet counts with receiving before 3 PM. The vendor asked us to split the freight across two invoices, so match both against PO-2211 when the feed lands. Terms are Net 30 from the later of the two invoice dates.</SuiSpoiler>
</template>
import { Component } from "@angular/core";
import { SuiSpoiler } from "@shineui/angular";
@Component({
standalone: true,
imports: [SuiSpoiler],
template: `<sui-spoiler lines="2" [(open)]="open" (suiClose)="onChange($event)">Fall supplies order for the New Albany dock. Confirm pallet counts with receiving before 3 PM. The vendor asked us to split the freight across two invoices, so match both against PO-2211 when the feed lands. Terms are Net 30 from the later of the two invoice dates.</sui-spoiler>`,
})
export class Example {
open = false;
onChange(e: CustomEvent) { console.log(e.detail); }
}
Basic
The toggle is a native button carrying aria-expanded and aria-controls for the clamped body. The clamp is visual only, so the full text stays in the accessibility tree in both states and aria-expanded describes the visible height.
Fall supplies order for the New Albany dock. Confirm pallet counts with receiving before 3 PM. The vendor asked us to split the freight across two invoices, so match both against PO-2211 when the feed lands. Terms are Net 30 from the later of the two invoice dates.
<div style="max-width:420px;font:460 13px/1.6 var(--font-sans);color:var(--ink)"><sui-spoiler lines="2">Fall supplies order for the New Albany dock. Confirm pallet counts with receiving before 3 PM. The vendor asked us to split the freight across two invoices, so match both against PO-2211 when the feed lands. Terms are Net 30 from the later of the two invoice dates.</sui-spoiler></div>
Line count and labels
lines sets the collapsed height. moreLabel and lessLabel replace the default wording, which is where a different register or language goes.
Fall supplies order for the New Albany dock. Confirm pallet counts with receiving before 3 PM. The vendor asked us to split the freight across two invoices, so match both against PO-2211 when the feed lands. Terms are Net 30 from the later of the two invoice dates.Fall supplies order for the New Albany dock. Confirm pallet counts with receiving before 3 PM. The vendor asked us to split the freight across two invoices, so match both against PO-2211 when the feed lands. Terms are Net 30 from the later of the two invoice dates.
<div style="display:grid;gap:20px;max-width:420px;font:460 13px/1.6 var(--font-sans);color:var(--ink)">
<sui-spoiler lines="1">Fall supplies order for the New Albany dock. Confirm pallet counts with receiving before 3 PM. The vendor asked us to split the freight across two invoices, so match both against PO-2211 when the feed lands. Terms are Net 30 from the later of the two invoice dates.</sui-spoiler>
<sui-spoiler lines="4" more-label="Read the full memo" less-label="Collapse the memo">Fall supplies order for the New Albany dock. Confirm pallet counts with receiving before 3 PM. The vendor asked us to split the freight across two invoices, so match both against PO-2211 when the feed lands. Terms are Net 30 from the later of the two invoice dates.</sui-spoiler>
</div>
Expanded on mount
open is mutable and reflected, so a parent can render the memo already expanded and bind the state. Writing open from outside emits nothing, because the parent already knows.
Fall supplies order for the New Albany dock. Confirm pallet counts with receiving before 3 PM. The vendor asked us to split the freight across two invoices, so match both against PO-2211 when the feed lands. Terms are Net 30 from the later of the two invoice dates.
<div style="max-width:420px;font:460 13px/1.6 var(--font-sans);color:var(--ink)"><sui-spoiler lines="2" open>Fall supplies order for the New Albany dock. Confirm pallet counts with receiving before 3 PM. The vendor asked us to split the freight across two invoices, so match both against PO-2211 when the feed lands. Terms are Net 30 from the later of the two invoice dates.</sui-spoiler></div>
Events
The toggle emits suiOpen after it expands and a cancelable suiClose before it collapses, with detail "trigger", the only way this element closes itself. Bind open to these two and a parent never goes stale.
Fall supplies order for the New Albany dock. Confirm pallet counts with receiving before 3 PM. The vendor asked us to split the freight across two invoices, so match both against PO-2211 when the feed lands. Terms are Net 30 from the later of the two invoice dates.Idle.
<div style="display:grid;gap:10px;max-width:420px;font:460 13px/1.6 var(--font-sans);color:var(--ink)">
<sui-spoiler id="sp-events" lines="2">Fall supplies order for the New Albany dock. Confirm pallet counts with receiving before 3 PM. The vendor asked us to split the freight across two invoices, so match both against PO-2211 when the feed lands. Terms are Net 30 from the later of the two invoice dates.</sui-spoiler>
<span id="sp-log" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Idle.</span>
</div>
preventDefault on suiClose keeps the memo expanded. Use it while something below the fold still needs reading, such as an unacknowledged notice.
Fall supplies order for the New Albany dock. Confirm pallet counts with receiving before 3 PM. The vendor asked us to split the freight across two invoices, so match both against PO-2211 when the feed lands. Terms are Net 30 from the later of the two invoice dates.Expand it, then try to collapse it.
<div style="display:grid;gap:10px;max-width:420px;font:460 13px/1.6 var(--font-sans);color:var(--ink)">
<sui-spoiler id="sp-veto" lines="2">Fall supplies order for the New Albany dock. Confirm pallet counts with receiving before 3 PM. The vendor asked us to split the freight across two invoices, so match both against PO-2211 when the feed lands. Terms are Net 30 from the later of the two invoice dates.</sui-spoiler>
<span id="sp-veto-log" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Expand it, then try to collapse it.</span>
</div>
// setup(root): properties and events
setup(root) {
const log = root.querySelector('#sp-veto-log');
const el = root.querySelector('#sp-veto');
let acknowledged = false;
el.addEventListener('suiClose', (e) => {
if (acknowledged) return;
e.preventDefault();
acknowledged = true;
log.textContent = 'Held open once. Collapse again and it closes.';
});
}
Keyboard
Key
Action
Tab
Lands on the Show more control below the clamped text; it reads aria-expanded and controls the body.
EnterSpace
Toggles the body. Opening emits suiOpen; closing emits a cancelable suiClose with reason trigger and stays open when a listener cancels it.
API
<sui-spoiler>
Properties
Property
Attribute
Type
Default
Description
lessLabel
less-label
stringstring | undefined
Toggle label while expanded.
lines
lines
number
3
Visible line count while collapsed. The clamp is visual only: the full text stays in the accessibility tree in both states.
moreLabel
more-label
stringstring | undefined
Toggle label while collapsed.
open
open
boolean
false
Whether the content is expanded (the reference defaultOpen). Mutable and reflected: the toggle flips it and a parent can bind it. A write from outside emits nothing.
Fires before the toggle collapses the content; detail is 'trigger', the only way this element closes itself. Cancelable: preventDefault() keeps it expanded. It does not bubble.
suiOpen
void
onSuiOpen
@sui-open
Fires after the toggle expands the content, so a parent binding open stays current. It does not bubble, as no open-state event does.
Methods
Method
Signature
Description
setFocus()
setFocus() => Promise<void>
Moves keyboard focus to the Show more toggle. Does nothing while the content fits and there is no toggle.
Slots
Slot
Description
(default)
The long content to clamp.
Types
SuiCloseReasontypeWhy a dismissible surface is closing. Detail of the cancelable suiClose. A consumer expresses "a stray backdrop click must not close this" by calling preventDefault() on the reason it does not accept, which is why none of these needs a prop of its own.
No members.
Theming
Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.