Stepper

Stepperは入力フォームや申請フローなど、ステップごとの進捗状況を視覚的に示しながら操作をガイドするためのコンポーネントです。

プロパティ

Stepperは、以下のプロパティをサポートしています。

名前 デフォルト値 説明
steps StepperStatus 表示するSteps
isEquallySpaced boolean true 等間隔か。初期値は等間隔
direction StepperDirection horizontal 表示向き。初期値はhorizontal(横表示)
finishedIcon Component Check finishedステータス時に表示するアイコン。初期はCheck
errorIcon Component X errorステータス時に表示するアイコン。初期はX

インストールの手順

以下のコンポーネントのコードを、使いたいプロジェクトにコピー&ペーストします。
パスは実際のプロジェクトの構成にあわせて更新します。

atoms/Stepper.svelte
        <!--
@component
## 概要
- Stepperは入力フォームや申請フローなど、ステップごとの進捗状況を視覚的に示しながら操作をガイドするためのコンポーネントです。

## 機能
- ステップごとの状態(未着手・進行中・完了・エラー)を表示
- カスタムアイコンやレイアウトの切替が可能

## Props
- steps: ステップ情報(2つ以上の配列)を指定します
- isEquallySpaced: ステップの幅を等間隔にするかを指定します(デフォルト: true)
- direction: ステップの表示方向を指定します('horizontal' または 'vertical'、デフォルト: 'horizontal')
- finishedIcon: 完了状態のカスタムアイコンを指定できます(デフォルト: ✓)
- errorIcon: エラー状態のカスタムアイコンを指定できます(デフォルト: ✕)

## Usage
```svelte
  <Stepper steps={steppers} isEquallySpaced={false} direction="vertical" />
```
-->

<script module lang="ts">
  import type { Component, Snippet } from 'svelte';
  import type { ClassValue } from 'svelte/elements';
  import { cva, type VariantProps } from 'class-variance-authority';

  export const stepperStepVariants = cva('flex flex-col items-center justify-center shrink-0 size-8 rounded-full', {
    variants: {
      status: {
        upcoming: ['bg-base-container-muted text-base-foreground-muted'],
        current: ['bg-primary text-base-foreground-on-fill-bright'],
        finished: ['bg-base-container-default border border-primary text-primary'],
        error: ['bg-base-container-default border border-destructive text-destructive'],
      },
    },
    defaultVariants: {
      status: 'upcoming',
    },
  });

  export type StepperStepVariants = VariantProps<typeof stepperStepVariants>;

  /** ステップの状態(未完了・現在・完了・エラー) */
  export type StepperStatus = NonNullable<StepperStepVariants['status']>;

  /** ステップの定義 */
  export interface StepperStep {
    /** ステップ全体に適用するクラス */
    class?: ClassValue;
    /** ステップの状態 */
    status: StepperStatus;
    /** ステップの開始部分(横向き:上、縦向き:左) */
    startContent?: Snippet<[]>;
    /** ステップの終了部分(横向き:下、縦向き:右) */
    endContent?: Snippet<[]>;
  }

  /** ステッパーの方向(横 or 縦) */
  export type StepperDirection = 'horizontal' | 'vertical';

  /** 2ステップ以上の配列であることを保証 */
  export type StepperSteps = [StepperStep, StepperStep, ...StepperStep[]];

  /** ステッパーのコンポーネント Props */
  export interface StepperProps {
    /** 表示するステップ一覧(2つ以上) */
    steps: StepperSteps;
    /** ステップを等間隔に配置するか(デフォルト: true) */
    isEquallySpaced?: boolean;
    /** 表示方向(デフォルト: 'horizontal') */
    direction?: StepperDirection;
    /** 完了アイコンのカスタマイズ(デフォルト: '✓') */
    finishedIcon?: Component;
    /** エラー時アイコンのカスタマイズ(デフォルト: '✕') */
    errorIcon?: Component;
  }
</script>

<script lang="ts">
  import { Check, X } from '@lucide/svelte';

  let { steps, isEquallySpaced = true, direction = 'horizontal', finishedIcon = Check, errorIcon = X }: StepperProps = $props();

  const isHorizontal = $derived(direction === 'horizontal');

  const hasEnoughSteps = $derived(steps.length >= 2);

  $effect(() => {
    if (!hasEnoughSteps) {
      console.warn('[Stepper] stepsは2つ以上必要です');
    }
  });

  function resolveStatusIcon(status: StepperStatus) {
    if (status === 'finished') return finishedIcon;
    if (status === 'error') return errorIcon;
    return null;
  }

  function connectorColor(status: StepperStatus) {
    return status === 'upcoming' ? 'bg-base-container-muted' : 'bg-primary';
  }
</script>

<div class={isHorizontal ? ['flex items-start', isEquallySpaced ? 'w-full' : 'overflow-x-auto'] : ['flex flex-col items-stretch ', isEquallySpaced ? 'h-full' : 'overflow-y-auto']}>
  {#if isHorizontal}
    {#each steps as step, i}
      <div class={[step.class, 'flex flex-col items-stretch min-w-8', isEquallySpaced ? 'flex-1' : !step.class ? 'max-w-8' : '']}>
        {#if step.startContent}
          <div class="place-self-center w-fit mb-2">
            {@render step.startContent()}
          </div>
        {/if}
        <div class="grid grid-cols-[1fr_auto_1fr] items-center w-full">
          <div class={['h-px w-full', i === 0 ? 'opacity-0' : connectorColor(step.status)]}></div>
          {@render stepCircle({ index: i, status: step.status })}
          <div class={['h-px w-full', i === steps.length - 1 ? 'opacity-0' : connectorColor(steps[i + 1].status)]}></div>
        </div>
        {#if step.endContent}
          <div class="place-self-center w-fit mt-2">
            {@render step.endContent()}
          </div>
        {/if}
      </div>
    {/each}
  {:else}
    {#each steps as step, i}
      <div class={['grid grid-cols-[auto_auto_1fr] items-center min-h-8', isEquallySpaced ? 'flex-1' : !step.class ? 'max-h-8' : '', step.class]}>
        {#if step.startContent}
          <div class={['place-self-center mr-2 overflow-auto', !isEquallySpaced && !step.class ? 'max-h-8' : '']}>
            {@render step.startContent()}
          </div>
        {/if}
        <div class="flex flex-col items-center self-stretch">
          <div class={['w-px', i === 0 ? 'opacity-0' : connectorColor(step.status), isEquallySpaced ? 'flex-1' : 'h-full']}></div>
          {@render stepCircle({ index: i, status: step.status })}
          <div class={['w-px', i === steps.length - 1 ? 'opacity-0' : connectorColor(steps[i + 1].status), isEquallySpaced ? 'flex-1' : 'h-full']}></div>
        </div>
        {#if step.endContent}
          <div class={['place-self-center ml-2 overflow-auto', !isEquallySpaced && !step.class ? 'max-h-8' : '']}>
            {@render step.endContent()}
          </div>
        {/if}
      </div>
    {/each}
  {/if}
</div>

{#snippet stepCircle({ index, status })}
  <div class={stepperStepVariants({ status })}>
    {#if resolveStatusIcon(status)}
      {@const StatusIcon = resolveStatusIcon(status)}
      <StatusIcon size="1rem" />
    {:else}
      <span class="text-xs">{index + 1}</span>
    {/if}
  </div>
{/snippet}

      

使い方


サンプル

Default

3つのステップをデフォルト設定である横向き・等間隔で表示したパターンです。

HorizontalStartContent

3つのステップをデフォルト設定である横向き・等間隔で表示し上にコンテンツを表示したパターンです。

HorizontalEndContent

3つのステップをデフォルト設定である横向き・等間隔で表示し下にコンテンツを表示したパターンです。

HorizontalBothContents

3つのステップをデフォルト設定である横向き・等間隔で表示し上下にコンテンツを表示したパターンです。

Vertical

3つのステップを縦向き・等間隔で表示したパターンです。

VerticalStartContent

3つのステップを縦向き・等間隔で表示し左にコンテンツを表示したパターンです。

VerticalEndContent

3つのステップを縦向き・等間隔で表示し右にコンテンツを表示したパターンです。

VerticalBothContents

3つのステップを縦向き・等間隔で表示し左右にコンテンツを表示したパターンです。

HorizontalSize

3つのステップで、横向きでサイズを指定したパターンです。

VerticalSize

3つのステップで、縦向きでサイズを指定したパターンです。

Error

3つのステップのうち、エラー状態を含むパターンです。

Upcoming

3つのステップで、2番目のステップをスキップしているパターンです。