データ表示
Step Indicator
ウィザードやフォームのステップ進行を水平・垂直で視覚化するインジケーター。
インストール
npx shadcn add https://d-ui.daigo-suhara.com/registry/step-indicator.jsonサンプル
アカウント
2
プロフィール
3
確認
4
完了
使い方
import { StepIndicator } from "@/components/ui/step-indicator"
export default function Example() {
return (
<StepIndicator
currentStep={1}
steps={[
{ label: "アカウント作成" },
{ label: "プロフィール設定" },
{ label: "確認" },
]}
/>
)
}プロパティ
steps必須{ label: string; description?: string }[]ステップの配列
currentStep必須number現在のステップインデックス(0始まり)
orientation任意"horizontal" | "vertical"表示方向
デフォルト:
"horizontal"className必須string追加のCSSクラス
| 名前 | 型 | デフォルト | 説明 | |
|---|---|---|---|---|
| 必須 | steps | { label: string; description?: string }[] | — | ステップの配列 |
| 必須 | currentStep | number | — | 現在のステップインデックス(0始まり) |
| 任意 | orientation | "horizontal" | "vertical" | "horizontal" | 表示方向 |
| 必須 | className | string | — | 追加のCSSクラス |
ソースコード
import * as React from "react";
import { Check } from "lucide-react";
import { cn } from "@/lib/utils";
export interface Step {
label: string;
description?: string;
}
interface StepIndicatorProps {
steps: Step[];
currentStep: number;
orientation?: "horizontal" | "vertical";
className?: string;
}
export function StepIndicator({
steps,
currentStep,
orientation = "horizontal",
className,
}: StepIndicatorProps) {
const isVertical = orientation === "vertical";
return (
<div
className={cn(
isVertical ? "flex flex-col gap-0" : "flex items-start",
className
)}
>
{steps.map((step, i) => {
const status =
i < currentStep ? "completed" : i === currentStep ? "active" : "upcoming";
const isLast = i === steps.length - 1;
return (
<div
key={i}
className={cn(
isVertical ? "flex gap-4 pb-6 last:pb-0" : "flex flex-col items-center flex-1 last:flex-none"
)}
>
{/* Circle + connector (horizontal) */}
{!isVertical && (
<div className="flex w-full items-center">
<div
className={cn(
"flex h-8 w-8 shrink-0 items-center justify-center rounded-full border-2 text-sm font-semibold transition-colors duration-300",
status === "completed" && "border-primary bg-primary text-primary-foreground",
status === "active" && "border-primary bg-background text-primary",
status === "upcoming" && "border-muted-foreground/30 bg-background text-muted-foreground/50"
)}
>
{status === "completed" ? (
<Check className="h-4 w-4" />
) : (
<span>{i + 1}</span>
)}
</div>
{!isLast && (
<div
className={cn(
"h-0.5 flex-1 transition-colors duration-300",
i < currentStep ? "bg-primary" : "bg-muted"
)}
/>
)}
</div>
)}
{/* Circle + connector (vertical) */}
{isVertical && (
<div className="flex flex-col items-center">
<div
className={cn(
"flex h-8 w-8 shrink-0 items-center justify-center rounded-full border-2 text-sm font-semibold transition-colors duration-300",
status === "completed" && "border-primary bg-primary text-primary-foreground",
status === "active" && "border-primary bg-background text-primary",
status === "upcoming" && "border-muted-foreground/30 bg-background text-muted-foreground/50"
)}
>
{status === "completed" ? (
<Check className="h-4 w-4" />
) : (
<span>{i + 1}</span>
)}
</div>
{!isLast && (
<div
className={cn(
"w-0.5 flex-1 min-h-6 mt-1 transition-colors duration-300",
i < currentStep ? "bg-primary" : "bg-muted"
)}
/>
)}
</div>
)}
{/* Label */}
<div
className={cn(
isVertical ? "flex-1 min-w-0" : "mt-2 text-center"
)}
>
<p
className={cn(
"text-xs font-medium",
status === "active" && "text-primary",
status === "completed" && "text-foreground",
status === "upcoming" && "text-muted-foreground/50"
)}
>
{step.label}
</p>
{step.description && (
<p className="text-xs text-muted-foreground mt-0.5 leading-relaxed">
{step.description}
</p>
)}
</div>
</div>
);
})}
</div>
);
}