ステップインジケータ
stimeo--step-indicator
導出した状態と進捗割合の CSS 変数を備えた、読み取り専用の進行状況表示。
stimeo--step-indicator コントローラは、読み取り専用の進行状況表示。専用の APG ウィジェットは無いため、現在地は現在の li の aria-current="step" で表し、各ステップ li には現在番号から導出した data-state(complete / current / upcoming)を付与する。あわせて全体の進捗割合を --stimeo-step-indicator-ratio(0–1)の CSS 変数として公開する。ステップは操作対象ではなくフォーカスも受けない。現在ステップの更新は外部の step:set イベント(ここでは戻る/進むボタン)で行い、stimeo--step-indicator:change を発火する。操作できるウィザードは Stepper を参照。ライブラリは振る舞いのみを提供する。
- カート
- 配送
- 支払い
- 確認
<%# Markup for the step-indicator (read-only progress) demo.
The library holds the current step number, sets data-state on each li, sets
aria-current="step" on the current li, and updates the progress CSS variable
(--stimeo-step-indicator-ratio). The steps themselves aren't interactive, so the
current step is updated via the external step:set event (fired by demo.js from the
prev/next buttons). %>
<div class="step-indicator-demo">
<ol
class="step-indicator"
data-controller="stimeo--step-indicator"
aria-label="<%= t("components.step_indicator.demo.label") %>"
data-stimeo--step-indicator-current-value="1"
data-action="step:set->stimeo--step-indicator#setCurrent">
<% %w[cart shipping payment confirm].each_with_index do |step, index| %>
<li
class="step-indicator__step"
data-stimeo--step-indicator-target="step"
<%= "data-state=\"complete\"".html_safe if index < 1 %>
<%= "data-state=\"current\" aria-current=\"step\"".html_safe if index == 1 %>
<%= "data-state=\"upcoming\"".html_safe if index > 1 %>>
<span class="step-indicator__dot"></span>
<span class="step-indicator__label"><%= t(
"components.step_indicator.demo.steps.#{step}"
) %></span>
</li>
<% end %>
</ol>
<div class="step-indicator__controls">
<button type="button" class="demo-trigger" data-step-indicator-prev>
<%= t("components.step_indicator.demo.back") %>
</button>
<button type="button" class="demo-trigger" data-step-indicator-next>
<%= t("components.step_indicator.demo.advance") %>
</button>
</div>
</div>
/*
* Presentation-only styles for the step-indicator demo.
* Complete / current / upcoming are built by reacting to each li's data-state. The
* progress bar uses --stimeo-step-indicator-ratio (0–1, updated by the library) as its width.
*/
.step-indicator {
display: flex;
gap: 0;
margin: 0;
padding: 0;
list-style: none;
position: relative;
}
.step-indicator::before,
.step-indicator::after {
content: "";
position: absolute;
top: 0.5rem;
left: 0.5rem;
right: 0.5rem;
height: 2px;
background: var(--surface-subtle);
}
.step-indicator::after {
right: auto;
width: calc((100% - 1rem) * var(--stimeo-step-indicator-ratio, 0));
background: var(--accent, var(--color-primary));
transition: width 0.2s ease;
}
.step-indicator__step {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.4rem;
font-size: 0.8125rem;
color: var(--color-text-subtle);
}
.step-indicator__dot {
position: relative;
z-index: 1;
width: 1rem;
height: 1rem;
border-radius: 50%;
background: var(--border-strong);
}
.step-indicator__step[data-state="complete"] .step-indicator__dot,
.step-indicator__step[data-state="current"] .step-indicator__dot {
background: var(--accent, var(--color-primary));
}
.step-indicator__step[data-state="current"] {
color: var(--fg, var(--color-text));
font-weight: 600;
}
.step-indicator__controls {
display: flex;
gap: 0.5rem;
margin-top: 1.25rem;
}
// step-indicator external-update demo (consumer-side JS).
//
// The core controller (stimeo--step-indicator) is read-only: it only holds the current
// step and syncs data-state / aria-current / the progress CSS variable (no interaction).
// Changing the current step is done externally via the step:set event by contract, so
// here the prev/next buttons read the current value and fire step:set with a clamped new index.
document.querySelectorAll('[data-controller~="stimeo--step-indicator"]').forEach((list) => {
const root = list.closest('.step-indicator-demo') ?? list.parentElement;
if (!root) return;
const total = list.querySelectorAll('[data-stimeo--step-indicator-target="step"]').length;
const attr = 'data-stimeo--step-indicator-current-value';
const move = (delta) => {
const current = Number(list.getAttribute(attr) ?? 0);
const next = Math.min(total - 1, Math.max(0, current + delta));
list.dispatchEvent(new CustomEvent('step:set', { detail: { current: next } }));
};
root.querySelector('[data-step-indicator-prev]')?.addEventListener('click', () => move(-1));
root.querySelector('[data-step-indicator-next]')?.addEventListener('click', () => move(1));
});
これらのスタイルは共通のデザイントークン(ライト/ダーク両対応)を使います。 共通スタイルも一緒にコピーし、ルート要素の data-theme を切り替えればダークになります。
このコンポーネントを動かすために HTML へ記述する data-* 属性です。ルート要素に下の data-controller を付け、その内側に各 target / value / action を配置します。
ルート要素に付与
data-controller="stimeo--step-indicator"
ターゲット
| 名前 | 説明 | 属性 |
|---|---|---|
step
必須
|
各ステップ要素。data-state(complete/current/upcoming)と aria-current が付与される。 |
data-stimeo--step-indicator-target="step" |
値(Values)
| 名前 | 説明 | 属性 |
|---|---|---|
current
|
現在ステップの0始まりインデックス。ステップ数に丸められる(既定 0)。 | data-stimeo--step-indicator-current-value |
アクション
| 名前 | 説明 | アクション |
|---|---|---|
setCurrent
|
detail.current(0始まり・丸め)で現在ステップを更新し change を発火する。 | stimeo--step-indicator#setCurrent |
イベント
| 名前 | 説明 | イベント |
|---|---|---|
change
|
現在ステップが変わったときに発火。detail.current / detail.total を伴う。 | stimeo--step-indicator:change |
状態フック
ライブラリが操作するのはこれらの ARIA / data 属性、カスタムプロパティだけです。見た目は利用側 CSS がこれらに反応して作ります([aria-selected] / [aria-expanded] / var(--stimeo--…) などのセレクタでフックします)。
| フック | 対象 | 意味 |
|---|---|---|
data-state |
ステップ(li) | 現在番号から導出した "complete" / "current" / "upcoming"。 |
aria-current |
現在ステップ(li) | 現在ステップにのみ "step"。 |
--stimeo-step-indicator-ratio |
コントローラ要素 | 全体の進捗割合(0–1)を CSS へ。 |