コラプシブル
stimeo--collapsible
1 か所をその場で開閉します。中身の高さを CSS へ渡すので、高さのアニメーションが書けます。
ボタンひとつで、その場の 1 か所を開閉します。開いているかどうかは読み上げにも伝わります。ふつう高さのアニメーションは中身の高さが決まらないと書けませんが、実際の高さを測って CSS へ渡すので、高さのトランジションをそのまま書けます。閉じるときはアニメーションが終わってから隠すため、動きが途中で切れません。
実行中
14 時までのご注文は当日出荷します。通常配送はおよそ 3〜5 営業日、特急オプションはご注文手続きの画面で選べます。
キーボード操作
| キー | 動作 |
|---|---|
| Enter / Space | 領域の開閉を切り替える(ネイティブボタン)。 |
<%# Markup for the collapsible (APG Disclosure) demo.
The trigger opens/closes a single region inline. The library syncs aria-expanded /
hidden / data-state and the content's measured-height variable; the look (the
height transition) lives in demo.css.
The trigger carries both of its wordings as a label pair, so the words live in the
markup where they can be translated. The library shows the half that belongs to the
current state and hides the other, so demo.css has nothing to do here. The chevron
is decoration and is kept out of the accessible name. %>
<div class="collapsible" data-controller="stimeo--collapsible">
<button
class="collapsible__trigger"
type="button"
aria-expanded="false"
aria-controls="collapsible-demo-content"
data-stimeo--collapsible-target="trigger"
data-action="click->stimeo--collapsible#toggle">
<span class="collapsible__chevron" aria-hidden="true"></span>
<span data-stimeo--collapsible-target="collapsedLabel">
<%= t("components.collapsible.demo.show") %>
</span>
<span data-stimeo--collapsible-target="expandedLabel" hidden>
<%= t("components.collapsible.demo.hide") %>
</span>
</button>
<div
class="collapsible__content"
id="collapsible-demo-content"
data-state="closed"
hidden
data-stimeo--collapsible-target="content">
<p class="collapsible__body"><%= t("components.collapsible.demo.body") %></p>
</div>
</div>
<%# Readout: what the component just reported. Purely visual — the component
already announces what a screen reader needs, so this line is aria-hidden to
keep it from being read twice. The value is a code fragment (API identifiers
and values), so only the label is translated. %>
<p class="demo-readout" aria-hidden="true">
<span><%= t("catalog.readout.events") %></span>
<span class="demo-readout__value" data-readout>—</span>
</p>
/*
* Presentation-only styles for the collapsible demo.
* The open/close animation uses the data-state the library sets on the content
* target and --stimeo--collapsible-content-height (the content's measured height).
*/
.collapsible {
max-width: 32rem;
border: 1px solid var(--border-default);
border-radius: 0.5rem;
overflow: hidden;
}
.collapsible__trigger {
display: flex;
align-items: center;
gap: 0.5rem;
width: 100%;
padding: 0.75rem 1rem;
background: var(--surface-subtle);
border: 0;
font: inherit;
font-weight: 600;
color: var(--fg);
cursor: pointer;
text-align: left;
}
.collapsible__chevron {
width: 0.5rem;
height: 0.5rem;
border-right: 2px solid currentColor;
border-bottom: 2px solid currentColor;
transform: rotate(-45deg);
transition: transform 0.2s ease;
}
.collapsible__trigger[aria-expanded="true"] .collapsible__chevron {
transform: rotate(45deg);
}
/* Transition the height while opening/closing or open (i.e. once hidden is removed). */
.collapsible__content {
height: 0;
overflow: hidden;
transition: height 0.2s ease;
}
.collapsible__content[data-state="open"] {
height: var(--stimeo--collapsible-content-height);
}
.collapsible__body {
margin: 0;
padding: 1rem;
}
/* Disable the transition under reduced motion (fall back to instant open/close). */
@media (prefers-reduced-motion: reduce) {
.collapsible__content,
.collapsible__chevron {
transition: none;
}
}
// Every move of the open state is reported, with a `reason` saying what drove it
// — a trigger, Escape, a click outside, or a call from your own code. A real app
// subscribes to mirror the state elsewhere or to send an analytics event; this
// demo writes the event name and its reason into the readout so both are visible
// while you interact. `reason: "api"` is the one to watch for: a close you asked
// for comes back to your own listener.
// The page chrome uses some of these controllers too (the nav, the tab strip),
// so everything below is scoped to the demo itself.
const demoStage = document.querySelector(".demo-stage") ?? document;
const stateReadout = demoStage.querySelector("[data-readout]");
const stateRoot = demoStage.querySelector("[data-controller~='stimeo--collapsible']");
for (const name of ["open", "close"]) {
stateRoot?.addEventListener(`stimeo--collapsible:${name}`, (event) => {
const reason = event.detail?.reason;
if (stateReadout) {
stateReadout.textContent = reason
? `stimeo--collapsible:${name} · reason: ${reason}`
: `stimeo--collapsible:${name}`;
}
});
}
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--collapsible"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
trigger
必須
|
単一の開閉領域を切り替えるボタン。aria-expanded を持つ。 |
data-stimeo--collapsible-target="trigger" |
content
必須
|
インラインで開閉する領域。data-state・hidden・高さ変数がコントローラーで制御される。 |
data-stimeo--collapsible-target="content" |
expandedLabel
|
開いている間に出るトリガーの側。hidden は部品が持つ。同じトリガーの中の collapsedLabel と対になる。 |
data-stimeo--collapsible-target="expandedLabel" |
collapsedLabel
|
閉じている間に出るトリガーの側。hidden は部品が持つ。同じトリガーの中の expandedLabel と対になる。 |
data-stimeo--collapsible-target="collapsedLabel" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
open
|
connect 時にアニメーションせず DOM へ反映する初期開状態(既定 false)。 | data-stimeo--collapsible-open-value |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
toggle
|
領域をアニメーション付きで開閉する。遷移が走るよう hidden・data-state・高さの順序を管理する。 |
stimeo--collapsible#toggle |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
close
|
閉じ始めた時点で発火。detail の reason に、何がそうさせたかを載せる。閉じ遷移の完了は待たない。 | stimeo--collapsible:close |
open
|
開いた直後に発火。detail の reason に、何がそうさせたかを載せる。 | stimeo--collapsible:open |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
aria-expanded |
トリガー | 開で "true"、閉で "false"。 |
hidden |
中身 | 閉のとき付与(閉じるトランジション完了後に付与)。 |
data-state |
中身 | "open" / "closed"。高さアニメーションの起点。 |
--stimeo--collapsible-content-height |
中身 | 中身の実寸高さ(px)。高さトランジションに用いる。 |