Steps
진행 단계를 시각적으로 표시하는 스텝 인디케이터
목차
다단계 진행 상황을 시각적으로 표시하는 읽기 전용 컴포넌트입니다. 번호 원과 연결선으로
단계를 이어 보여 주는 numbered(기본)와, 단계마다 상태 글리프 · 이름 · 오른쪽 글을 한 줄씩
쌓는 list 두 가지 모양이 있습니다.
기본 사용법 (Basic Usage)#
Steps(
currentStep: 2,
items: [
CoreStepData(
title: 'Create Account',
description: 'Sign up with your email',
),
CoreStepData(
title: 'Verify Email',
description: 'Check your inbox',
),
CoreStepData(
title: 'Setup Profile',
description: 'Add your information',
),
CoreStepData(
title: 'Complete',
description: 'Ready to go',
),
],
)
빠른 오버라이드 (Chain)#
이미 만든 Steps 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.
class StepsChainExample extends StatelessWidget {
const StepsChainExample({super.key});
@override
Widget build(BuildContext context) {
return const Steps(
currentStep: 2,
items: [
CoreStepData(
title: 'Create Account',
description: 'Sign up with your email',
),
CoreStepData(
title: 'Verify Email',
description: 'Check your inbox',
),
CoreStepData(
title: 'Setup Profile',
description: 'Add your information',
),
CoreStepData(
title: 'Complete',
description: 'Ready to go',
),
],
).withStyle(
const CoreStepsStyle(
indicatorSize: CoreSize.size40,
activeIndicatorColor: CoreColor.token(CoreColors.primary),
connectorThickness: CoreStrokeWidth.stroke4,
indicatorContentGapStyle: CoreGapStyle(size: CoreSpace.space12),
),
);
}
}
class StepsChainExample extends StatelessComponent {
const StepsChainExample({super.key});
@override
Component build(BuildContext context) {
return const Steps(
currentStep: 2,
items: [
CoreStepData(
title: 'Create Account',
description: 'Sign up with your email',
),
CoreStepData(
title: 'Verify Email',
description: 'Check your inbox',
),
CoreStepData(
title: 'Setup Profile',
description: 'Add your information',
),
CoreStepData(
title: 'Complete',
description: 'Ready to go',
),
],
).withStyle(
const CoreStepsStyle(
indicatorSize: CoreSize.size40,
activeIndicatorColor: CoreColor.token(CoreColors.primary),
connectorThickness: CoreStrokeWidth.stroke4,
indicatorContentGapStyle: CoreGapStyle(size: CoreSpace.space12),
),
);
}
}
Live Preview#
코드 보기
class StepsDefaultExample extends StatelessComponent {
const StepsDefaultExample({super.key});
@override
Component build(BuildContext context) {
return const Steps(
currentStep: 2,
items: [
CoreStepData(
title: 'Create Account',
description: 'Sign up with your email',
),
CoreStepData(
title: 'Verify Email',
description: 'Check your inbox',
),
CoreStepData(
title: 'Setup Profile',
description: 'Add your information',
),
CoreStepData(
title: 'Complete',
description: 'Ready to go',
),
],
);
}
}
class StepsDefaultExample extends StatelessWidget {
const StepsDefaultExample({super.key});
@override
Widget build(BuildContext context) {
return const Steps(
currentStep: 2,
items: [
CoreStepData(
title: 'Create Account',
description: 'Sign up with your email',
),
CoreStepData(
title: 'Verify Email',
description: 'Check your inbox',
),
CoreStepData(
title: 'Setup Profile',
description: 'Add your information',
),
CoreStepData(
title: 'Complete',
description: 'Ready to go',
),
],
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 주문 진행, 배송 추적, 절차 안내 등 진행 단계를 보여줄 때
- 각 단계의 완료/진행/대기/실패 상태를 시각적으로 구분해야 할 때
- 실행 기록·배포 로그처럼 단계마다 상태와 짧은 결과(걸린 시간, 사유)를 한 줄씩 보고할 때 (
variant: list)
대신 다른 컴포넌트를 사용하세요:
Stepper: 사용자가 단계를 클릭하여 이동할 수 있는 인터랙티브 프로세스Progress: 단일 진행률을 바 형태로 표시StepItem: 목록의 행 하나를 직접 놓아야 할 때 (Steps(variant: list)가 쌓는 바로 그 행)
Props / Parameters#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
items |
List<CoreStepData> |
필수 | 단계 데이터 목록 |
currentStep |
int |
0 |
현재 활성 단계 인덱스 (0-based) |
vertical |
bool |
true |
세로 배치 여부. numbered 에서만 읽습니다 — list 는 늘 세로이고 이 값을 무시합니다 |
variant |
CoreStepsVariant |
CoreStepsVariant.defaultVariant(numbered) |
배치 모양 — 번호 원과 연결선(numbered) 또는 상태 행 목록(list) |
stepsStyle |
CoreStepsStyle? |
null |
인스턴스별 chrome 단일 진입점 |
CoreStepData#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
title | String | 필수 | 단계 제목 |
description |
String? |
null |
선택적 부가 설명. numbered 만 제목 아래에 그립니다 — list 행은 한 줄이라 그리지 않습니다 |
status |
CoreStepStatus? |
null |
이 단계가 지금 어디에 있는지(
done
·
current
·
todo
·
failed
).
null
이면
currentStep
과 순번으로 정해집니다
|
trailing |
String? |
null |
행 끝에 붙는 짧은 글(걸린 시간 · 개수 · 사유). list 만 그립니다 |
CoreStepsStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
indicatorSize |
double? |
Indicator circle diameter (logical px). |
indicatorBorderRadius |
CoreBorderRadius? |
Indicator (circle) corner radius override. Falls back to [defaultIndicatorBorderRadius] (a full circle) when null. |
stepPadding |
CoreEdgeInsets? |
Padding below each step content in the vertical layout. Falls back to [defaultStepPadding] when null. |
stepPaddingHorizontal |
CoreEdgeInsets? |
Outer padding around each step content in the horizontal layout — carries the top + right insets (the widget zeroes the right inset on the last item so the connector still ends cleanly). |
connectorThickness |
double? |
Connector line stroke thickness (logical px). |
iconSizeRatio |
double? |
Check icon size ratio relative to the indicator diameter. |
connectorColor |
CoreColor? |
Colour of a connector line that has not been walked yet — the one leading out of a current, upcoming or failed step. Falls back to [defaultConnectorColor]. |
activeConnectorColor |
CoreColor? |
Colour of the connector line leading out of a completed step. Falls back to [defaultActiveConnectorColor]. |
indicatorColor |
CoreColor? |
Inactive indicator fill colour. Falls back to [defaultIndicatorColor]. |
indicatorBorderColor |
CoreColor? |
Ring colour of upcoming indicators. Falls back to [defaultIndicatorBorderColor]. |
indicatorBorderWidth |
double? |
Ring thickness of upcoming indicators. Falls back to [defaultIndicatorBorderWidth]. |
activeIndicatorColor |
CoreColor? |
Active indicator fill colour. Falls back to [defaultActiveIndicatorColor]. |
failedIndicatorColor |
CoreColor? |
Failed step indicator fill colour. Falls back to [defaultFailedIndicatorColor]. |
activeLabelColor |
CoreColor? |
Label colour inside completed / current step indicators (on the active fill). Falls back to [defaultActiveLabelColor]. |
inactiveLabelColor |
CoreColor? |
Label colour inside upcoming step indicators (on the inactive fill). Falls back to [defaultInactiveLabelColor]. |
failedLabelColor |
CoreColor? |
Colour of the cross inside a failed step's indicator (on the failed fill). Falls back to [defaultFailedLabelColor]. |
titleColor |
CoreColor? |
Content title colour of every step that has been reached, and of a failed one. Falls back to [defaultTitleColor]. |
inactiveTitleColor |
CoreColor? |
Content title colour of an upcoming step. Falls back to [defaultInactiveTitleColor]. |
descriptionColor |
CoreColor? |
Content description colour. Falls back to [defaultDescriptionColor]. |
labelStyle |
CoreTextStyle? |
Indicator label typography. Merged on top of [defaultLabelStyle]. |
titleStyle |
CoreTextStyle? |
Content title typography for every step. Merged on top of [defaultTitleStyle], and on the current step on top of the [currentTitleStyle] overlay too, so a weight stated here reaches every step, the current one included. |
currentTitleStyle |
CoreTextStyle? |
Overlay for the current step's title typography. Merged on top of [defaultCurrentTitleStyle]; the result sits between [defaultTitleStyle] and the per-instance [titleStyle]. |
descriptionStyle |
CoreTextStyle? |
Content description typography. Merged on top of [defaultDescriptionStyle]. |
indicatorContentGapStyle |
CoreGapStyle? |
Nested [CoreGapStyle] slot for the gap between the indicator column and the step content. Merged on top of [defaultIndicatorContentGapStyle]. |
indicatorConnectorGapStyle |
CoreGapStyle? |
Nested [CoreGapStyle] slot for the gap above and below the connector line inside the indicator column. Merged on top of [defaultIndicatorConnectorGapStyle]. |
indicatorConnectorGapHorizontalStyle |
CoreGapStyle? |
Nested [CoreGapStyle] slot for the gap on either side of the connector line inside the indicator row of the horizontal layout. Merged on top of [defaultIndicatorConnectorGapHorizontalStyle]; the vertical layout reads [indicatorConnectorGapStyle]. |
stepItemStyle |
CoreStepItemStyle? |
Nested chrome of every row the list layout ([CoreStepsVariant.list]) stacks — the
StepItem
it composes. Merged on top of [defaultStepItemStyle] and forwarded raw to each row; the
StepItem
resolver fills the rest from [CoreStepItemStyle]. The status is not here — each row takes its item's.
|
변형 (Variants)#
세로 (기본)#
Steps(
currentStep: 2,
items: [
CoreStepData(title: 'Create Account', description: 'Sign up with your email'),
CoreStepData(title: 'Verify Email', description: 'Check your inbox'),
CoreStepData(title: 'Setup Profile', description: 'Add your information'),
CoreStepData(title: 'Complete', description: 'Ready to go'),
],
)
가로 (vertical: false)#
Steps(
currentStep: 1,
vertical: false,
items: [
CoreStepData(title: 'Cart'),
CoreStepData(title: 'Shipping'),
CoreStepData(title: 'Payment'),
CoreStepData(title: 'Review'),
],
)
가로 레이아웃은 인디케이터가 동일한 행에 배치되고, 인디케이터 사이에 가로 커넥터 라인이 그려집니다. 각 단계의 콘텐츠는 인디케이터 아래에 배치됩니다.
목록 (variant: list)#
Steps(
variant: CoreStepsVariant.list,
currentStep: 1,
items: [
CoreStepData(title: 'Install dependencies', trailing: '12s'),
CoreStepData(title: 'Run tests', trailing: '41s'),
CoreStepData(title: 'Lint', status: CoreStepStatus.failed, trailing: 'exit 1'),
CoreStepData(title: 'Deploy', trailing: 'Queued'),
],
)
번호 원과 연결선 대신 단계마다 StepItem 행을 하나씩 세로로 쌓습니다 — 상태 글리프
(완료 circle-check · 진행 중 circle-dot · 대기 circle
· 실패 circle-x), 한 줄 이름, 행 끝의 오른쪽 글(trailing).
-
늘 세로입니다.
vertical은numbered만 읽고list는 무시합니다 —vertical: false를 줘도 같은 열이 나옵니다. - 받은 폭을 채웁니다. 오른쪽 글이 행 끝에 붙으려면 목록의 폭이 필요하므로, 폭은 호스트(카드 · 페이지 열)가 정합니다.
description은 그리지 않습니다. 행은 한 줄이 계약입니다.- 행의 치수와 색은
StepItem의 것입니다.stepsStyle.stepItemStyle로 덮으면 모든 행에 닿습니다.
단계별 상태 (status)#
currentStep 은 위치로 상태를 정합니다 — 앞은 완료, 그 자리는 진행 중, 뒤는 대기. 그런데 "여기서 멈췄다"(실패)는
어떤 위치로도 말할 수 없으므로, 단계는 자기 status 를 직접 가질 수 있습니다.
Steps(
currentStep: 1,
items: [
CoreStepData(title: 'Create Account'), // 위치로 → 완료
CoreStepData(title: 'Verify Email'), // 위치로 → 진행 중
CoreStepData(title: 'Setup Profile', status: CoreStepStatus.failed), // 직접 → 실패
],
)
status 를 가진 단계와 없는 단계는 한 목록에 섞여도 됩니다 — 각 단계가 자기 몫으로 정해집니다
(status ?? 위치로 정한 값). 두 플랫폼 모두 이 한 규칙(CoreStepStatus.effective)을 거치므로
절반만 적은 목록이 플랫폼마다 다르게 그려지지 않습니다.
동작 스펙 (Behavior)#
상태 표시#
단계의 상태는 status ?? 위치로 정한 값 입니다. 위치로 정한 값은 currentStep 이 정합니다.
currentStep미만: 완료 상태 (primary 색상 원 + 체크 아이콘)currentStep인덱스: 현재 활성 (primary 색상 원 + 번호)-
currentStep초과: 대기 상태 (surface 채움 원 +outlineVariant1px 테두리 + 번호 — 번호와 제목은onSurfaceVariant로 한 단계 낮춥니다) -
status: failed: 실패 상태 (error색상 원 +onError가위표 아이콘) — 번호 대신 가위표입니다. 제목은 대기처럼 낮추지 않습니다
번호와 제목은 모든 단계가 SemiBold 입니다 — 현재 단계를 가르는 것은 굵기가 아니라 채운 원과 제목 색입니다.
list 는 같은 상태를 행의 글리프와 색으로 말합니다(StepItem 의 상태 표).
커넥터#
- 단계 간 선으로 연결 (세로 레이아웃: 세로선, 가로 레이아웃: 가로선). 선은 두 원 사이를 채웁니다
-
완료된 단계에서 나가는 구간은
primary, 나머지 구간은outlineVariant— 선도 원처럼 진행을 따라 채워집니다 (activeConnectorColor·connectorColor) -
원과 선 사이 간격은 세로 4, 가로 8 입니다 — 가로 행은 선이 옆으로 뻗으므로 더 넓게 둡니다 (
indicatorConnectorGapStyle·indicatorConnectorGapHorizontalStyle) list에는 연결선이 없습니다
인터랙션#
- 읽기 전용 — 탭/클릭 동작 없음
- 인터랙티브한 단계 내비게이션은 Stepper 사용
사용 가이드라인 (Usage Guidelines)#
현재·완료·대기 단계를 읽기 전용으로 보여주려면 Steps를 사용합니다. 단계 클릭 이벤트가 필요한 위저드에는 Stepper, 정해진 단계 없이 시간순 사건을 나열하려면 Timeline을 선택합니다. currentStep은 0-based 인덱스이며 데이터 진행은 호출자가 전달합니다.
✅ Do#
진행 상태만 필요하면 Steps, 클릭 이동이 필요하면 Stepper
Steps(
currentStep: 2,
items: [
CoreStepData(title: 'Create Account', description: 'Sign up with your email'),
CoreStepData(title: 'Verify Email'),
CoreStepData(title: 'Setup Profile'),
],
)
Steps는 currentStep 값 하나로 완료/현재/대기 상태를 색상과 아이콘으로 자동 표시합니다 — 각 단계의 색을 직접 계산할 필요가 없습니다.
❌ Don't#
클릭 가능한 것처럼 감싸서 인터랙션을 흉내내지 않기
// ❌ Steps는 콜백이 없어 탭해도 아무 일도 일어나지 않음
GestureDetector(
onTap: () => setState(() => step++),
child: Steps(currentStep: step, items: items),
)
Steps는 읽기 전용 컴포넌트입니다 — items(CoreStepData)는 title/description만 받고 클릭 콜백 자체가 없습니다. 단계를 클릭해 이동해야 한다면 Stepper를 사용하세요.
접근성 (Accessibility)#
역할#
-
numbered— 양 플랫폼 모두 자체 role 이나 semantics 를 내보내지 않습니다. 루트에list류 role 도, 단계별aria-current도 없습니다. 읽기 전용 진행 표시이므로 읽히는 것은 각 단계의 텍스트뿐입니다. -
list— 루트가 목록(list)이고 행이 항목(listitem)이라 리더가 "5개 중 3번째" 처럼 읽습니다. 진행 중인 단계에는 Web 이aria-current="step"을, Flutter 가selected를 답니다 — 같은 뜻의 신호이지만 리더가 읽는 문구는 플랫폼마다 다릅니다(아래알려진 제약참조).
키보드#
처리하는 키가 없습니다. 양 플랫폼 모두 Clickable 이나 키 핸들러를 합성하지 않고, 클릭·탭 콜백 자체가 계약에 없습니다(steps_contract.dart:22). 클릭 이동이 필요하면
Stepper 를 씁니다.
스크린 리더#
번호·제목·설명이 위젯 / DOM 순서 그대로 텍스트로 읽힙니다.
완료된 단계는 번호가 체크 아이콘으로, 실패한 단계는 가위표 아이콘으로 대체됩니다. Icon 은 어느 쪽에서도 이름을 내보내지 않으므로(Flutter 는
SvgPicture 만, Web 은 <iconify-icon> 에 aria-label 없음) 리더에는
그 단계의 번호도, 완료·실패했다는 사실도 남지 않고 제목·설명만 읽힙니다. list 의 상태 글리프도 마찬가지입니다.
numbered 의 현재 단계 역시 제목 굵기(현재 단계 700 · 나머지 600, CoreStepsStyle.defaultCurrentTitleStyle
· defaultTitleStyle)와 인디케이터 색으로만 구분되어, 리더가 읽는 문자열은 대기 단계와 같습니다. list 에서는 현재 단계만
aria-current="step" 이 알려 줍니다.
진행이 바뀐 사실을 알리는 live region 은 양쪽 다 없습니다.
포커스 관리#
진입 · 이탈 · 트랩 모두 없습니다. 포커스 가능한 노드가 하나도 없어(Flutter 에 Focus / FocusNode
없음, Web 에 tabindex 없음) 어느 플랫폼에서도 traversal 정지점이 되지 않습니다. currentStep 이 바뀌어도 포커스는 움직이지 않습니다.
알려진 제약#
-
list의 현재 단계 신호가 플랫폼마다 다릅니다 — Flutterselectedvs Webaria-current="step". 같은 행을 가리키지만 두 플랫폼에서 리더가 읽는 문구가 같지 않습니다. -
완료 / 실패 / 대기 상태가 리더에 전달되지 않습니다 — 위에서 본 대로 글리프에는 이름이 없고 굵기·색은 문자열로 드러나지 않습니다. 상태를 읽어 주는 문구는 두 플랫폼의 로컬라이제이션에 문구를 더해야 하는 별도 작업입니다. 대안은
CoreStepData.description(numbered) 이나trailing(list) 에 상태 문구를 담는 것입니다 — 양 플랫폼 모두 그 문자열을 평범한 텍스트로 읽어 줍니다. -
커넥터는 한 색입니다 — 완료 구간과 남은 구간이 같은 색·같은 두께라(
connectorColor, 기본outlineVariant) 진행 정도를 연결선 색으로 읽을 수 없습니다. 그것은 원(채움 · 체크 · 번호)이 말하므로, 색 강제 모드에서 연결선 색이 접혀도 잃는 진행 정보는 없습니다. -
numbered의 단계별 노드에 속성을 실을 진입점이 없습니다 — WebSteps(attributes: …)는 루트<div>에만 닿고 인디케이터·제목·커넥터에는 caller 표면이 없으며, FlutterSteps에는 attributes 표면 자체가 없습니다.numbered에role="list"나aria-current를 소비자가 밖에서 보강할 수 없다는 뜻입니다(list는 내장). -
라이트 모드에서 카드 위
list행의 오른쪽 글은 4.26:1 입니다 — 세 번째 텍스트 단계(onSurfaceSubtle)가surfaceContainer위에서 4.5:1 에 못 미치는 기록된 설계 예외입니다. 페이지 면 위와 다크 모드, 그리고 실패한 단계의 오른쪽 글(오류 계열 글자 색)은 해당하지 않습니다.
전역 축(동작 줄이기 · 고대비 · 색 강제 모드 · 시맨틱 역할 · 최소 터치 타겟)은 접근성 페이지가 한 곳에서 다룹니다.
크로스 플랫폼 차이점 (Platform Differences)#
| 항목 | Flutter | Web |
|---|---|---|
| 클래스명 | Steps | Steps |
| 렌더링 | Column + Row + Container |
<div> + 인라인 스타일 |
list 렌더링 |
Column(stretch) 에 StepItem 을 쌓음 |
<div>(flex-col, 전폭) 에 StepItem 을 쌓음 |
list 역할 |
CoUISemantics list / listItem, 진행 중 행은 selected |
role="list" / role="listitem", 진행 중 행은 aria-current="step" |
| 아이콘 | Icon(LucideIcons.check) · Icon(LucideIcons.x) |
Icon(LucideIcons.check) · Icon(LucideIcons.x) |
| 테마 | CoreStepsTheme (style + variantStyles) |
CoreStepsTheme (style + variantStyles) |