Progress
진행률 표시 컴포넌트
목차
작업 진행률을 시각적으로 표시하는 컴포넌트입니다. 직선형(Progress)과 원형(RadialProgress)을 지원합니다.
기본 사용법 (Basic Usage)#
// 기본 진행률 바 (0.0 ~ 1.0 범위)
Progress(value: 0.65)
// 커스텀 min/max 범위 지정
Progress(value: 75, min: 0, max: 100)
// 채움이 무엇을 뜻하는지는 tone 으로 — 트랙은 모든 tone 에서 같은 중립색
Progress(value: 0.92, tone: CoreProgressTone.warning)
// 막대 위 라벨 · 값, 막대 아래 캡션 (모두 선택)
Progress(
value: 0.62,
label: Text('Team plan'),
valueLabel: Text('62%'),
caption: Text('AI usage · 18 days left'),
)
// 단계형 — 단계 수만큼 나뉜 막대. 지나온 단계는 채움 색, 남은 단계는 트랙 색
Progress(
value: 1,
max: 2,
segments: 2,
label: Text('STEP 1 / 2'),
)
// 색상/두께 커스터마이징은 progressStyle 슬롯으로
Progress(
value: 0.5,
progressStyle: CoreProgressStyle(
color: CoreColor.token(CoreColors.tertiary),
minHeight: 12,
),
)
// 원형 진행률
RadialProgress(
value: 75,
showValueText: true,
)
// 원형 단계(size)와 의미(tone)
RadialProgress(
value: 50,
size: CoreProgressSize.md,
tone: CoreProgressTone.success,
)
// 기본 진행률 바 (0.0 ~ 1.0 범위)
Progress(value: 0.65)
// 커스텀 min/max 범위 지정
Progress(value: 75, min: 0, max: 100)
// 채움이 무엇을 뜻하는지는 tone 으로 — 트랙은 모든 tone 에서 같은 중립색
Progress(value: 0.92, tone: CoreProgressTone.warning)
// 막대 위 라벨 · 값, 막대 아래 캡션 (모두 선택)
Progress(
value: 0.62,
label: Text('Team plan'),
valueLabel: Text('62%'),
caption: Text('AI usage · 18 days left'),
)
// 단계형 — 단계 수만큼 나뉜 막대. 지나온 단계는 채움 색, 남은 단계는 트랙 색
Progress(
value: 1,
max: 2,
segments: 2,
label: Text('STEP 1 / 2'),
)
// 색상/두께 커스터마이징은 progressStyle 슬롯으로
Progress(
value: 0.5,
progressStyle: CoreProgressStyle(
color: CoreColor.token(CoreColors.tertiary),
minHeight: 12,
),
)
// 원형 진행률
RadialProgress(
value: 75,
showValueText: true,
)
// 원형 단계(size)와 의미(tone)
RadialProgress(
value: 50,
size: CoreProgressSize.md,
tone: CoreProgressTone.success,
)
빠른 오버라이드 (Chain)#
이미 만든 Progress 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.
.radius9999처럼 Core 토큰 상수 이름과 똑같은 이름의 getter도 있습니다 — withStyle을 한 번 더 줄인 sugar로, 이름이 곧 값이라(radius9999 ==
CoreRadius.radius9999) 어느 컴포넌트에서 써도 뜻이 갈리지 않습니다.
색 getter(.primary · .success · .error …)는 채움(color)을 칠하고, getter 가 정한 색은
tone 보다 이깁니다. 채움이 무엇을 뜻하는지(한도에 가까움 · 실패) 말하려면 tone 을 쓰고, getter 는 tone 에 없는 색에 씁니다. 트랙(backgroundColor)은 비워 두므로 어떤 채움 아래서도 같은 중립 트랙(surfaceContainerHigh)입니다 —
Progress(value: 0.5).success 는 success 채움 + 중립 트랙입니다. 트랙 색을 따로 정하려면 withStyle(CoreProgressStyle(backgroundColor: …))
를 쓰고, 채움 색을 옅게 따라가는 트랙이 필요하면 backgroundAlpha 를 줍니다. .transparent 만은 트랙을 비웁니다 — 다른 컴포넌트에서처럼 '바탕 없음' 이라는 뜻이고, 채움을 투명하게 하면 막대가 보이지 않기 때문입니다.
class ProgressChainExample extends StatefulWidget {
const ProgressChainExample({super.key});
@override
State<ProgressChainExample> createState() => _ProgressChainExampleState();
}
class _ProgressChainExampleState extends State<ProgressChainExample> {
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
SizedBox(
width: double.infinity,
child: const Progress(value: 0.65).radius16.primary,
),
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(minHeight)까지 한 번에.
SizedBox(
width: double.infinity,
child: const Progress(value: 0.65).withStyle(
const CoreProgressStyle(
color: CoreColor.token(CoreColors.tertiary),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
minHeight: CoreSpace.space16,
),
),
),
],
);
}
}
class ProgressChainExample extends StatefulComponent {
const ProgressChainExample({super.key});
@override
State<ProgressChainExample> createState() => _ProgressChainExampleState();
}
class _ProgressChainExampleState extends State<ProgressChainExample> {
@override
Component build(BuildContext context) {
return div(
[
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
Progress(value: 0.65).radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(minHeight)까지 한 번에.
Progress(value: 0.65).withStyle(
const CoreProgressStyle(
color: CoreColor.token(CoreColors.tertiary),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
minHeight: CoreSpace.space16,
),
),
],
classes: 'flex flex-col items-start',
);
}
}
Live Preview#
코드 보기
class ProgressDefaultExample extends StatefulComponent {
const ProgressDefaultExample({super.key});
@override
State<ProgressDefaultExample> createState() => _ProgressDefaultExampleState();
}
class _ProgressDefaultExampleState extends State<ProgressDefaultExample> {
@override
Component build(BuildContext context) {
return Progress(value: 0.65);
}
}
class ProgressDefaultExample extends StatefulWidget {
const ProgressDefaultExample({super.key});
@override
State<ProgressDefaultExample> createState() => _ProgressDefaultExampleState();
}
class _ProgressDefaultExampleState extends State<ProgressDefaultExample> {
@override
Widget build(BuildContext context) {
return const SizedBox(
width: double.infinity,
child: Progress(value: 0.65),
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 파일 업로드, 다운로드 등 작업 진행률을 표시할 때
- 목표 달성률, 스토리지 사용량 등 비율을 시각화할 때
- 사용량이 한도에 가까운지 · 작업이 실패했는지처럼 채움 색으로 상태를 함께 말해야 할 때 (
tone)
대신 다른 컴포넌트를 사용하세요:
Loading: 단순 로딩 스피너가 필요할 때Skeleton: 콘텐츠 로딩 플레이스홀더가 필요할 때Slider: 사용자가 값을 조절해야 할 때
Props / Parameters#
모든 chrome / dimensional override는 단일 Style 슬롯
(progressStyle / radialProgressStyle) 한 곳으로 모입니다. 위젯 평면 파라미터는
behaviour (value / min / max / disableAnimation / segments / showValueText), 시맨틱 축 (size / tone),
합성 슬롯 (label / valueLabel / caption / child) 만 남습니다.
직선형의 label · valueLabel · caption 과 원형의 child 에 쓰는
W는 Flutter의 Widget, Web의 Component입니다. 직선형 값은 min–max 범위, 원형 값은 0–100 퍼센트 단위입니다.
Progress (직선형)#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
value |
double? |
null |
진행률 (0.0~1.0) — null → 무한 슬라이드 |
min | double | 0.0 | 최소값 |
max | double | 1.0 | 최대값 |
disableAnimation |
bool |
false |
결정형(determinate) 트랜지션 비활성화 |
segments |
int? |
null |
막대를 이 수의 칸으로 나눕니다 — 지나온 단계는 채움 색, 남은 칸은 트랙 색, 칸 사이는
segmentSpacing
.
null
→ 연속 막대. 불확정(
value: null
)이면 무시합니다. 1 이상
|
size |
CoreProgressSize |
defaultSize (sm) |
직선형 진행바의 크기 토큰 |
tone |
CoreProgressTone |
defaultTone (primary) |
채움 색이 뜻하는 것 —
primary
·
success
·
warning
·
destructive
. 트랙은 모든 tone 에서 같은 중립색
|
label |
W? |
null |
막대 위 왼쪽 칸 — 막대의 이름 |
valueLabel |
W? |
null |
막대 위 오른쪽 칸 — 값 (예: 62%) |
caption |
W? |
null |
막대 아래 칸 — 보조 설명 |
progressStyle |
CoreProgressStyle? |
null |
chrome 슬롯 (아래 표) |
CoreProgressStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
color | CoreColor? | Progress fill colour override. |
backgroundColor |
CoreColor? |
Track background colour override.
null
(default) → [defaultBackgroundColor], unless [backgroundAlpha] is stated.
|
backgroundAlpha |
double? |
Track background alpha — a track derived from the fill (
color
faded by this alpha) instead of the neutral [defaultBackgroundColor]. Applied only when [backgroundColor] is not set.
null
(default) → the neutral track.
|
borderRadius |
CoreBorderRadius? |
Border radius override. Applies to both track and indicator so rounded shapes remain visually consistent. |
minHeight |
double? |
Minimum height (thickness) override (logical px).
null
defers to [defaultsBySize] for the active
CoreProgressSize
.
|
duration |
Duration? |
Determinate width transition duration override. |
indeterminateDuration |
Duration? |
Indeterminate slide cycle duration override. |
indeterminateFillRatio |
double? |
Indeterminate fill width as a share of the track,
0..1
.
null
defers to [defaultIndeterminateFillRatio]. Both platforms derive the slide's travel from it.
|
labelStyle |
CoreTextStyle? |
label
cell text override, merged field by field onto [defaultLabelStyle]. Applied as the ambient text style of whatever the caller puts in the
label
slot.
|
valueLabelStyle |
CoreTextStyle? |
valueLabel cell text override, merged field by field onto [defaultValueLabelStyle]. |
captionStyle |
CoreTextStyle? |
caption cell text override, merged field by field onto [defaultCaptionStyle]. |
labelGapStyle |
CoreGapStyle? |
Spacer between the label row and the bar — drawn only when the bar has a
label
or
valueLabel
. Merged onto [defaultLabelGapStyle].
|
captionGapStyle |
CoreGapStyle? |
Spacer between the bar and the caption — drawn only when the bar has a
caption
. Merged onto [defaultCaptionGapStyle].
|
segmentSpacing |
double? |
Gap between the cells of a segmented bar (
Progress.segments
), in logical pixels —
Row.spacing
(Flutter) / the cell row's
column-gap
(Web). A continuous bar never reads it.
null
defers to [defaultSegmentSpacing].
|
RadialProgress (원형)#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
value | double | 필수 | 진행률 (0~100) |
showValueText |
bool |
false |
퍼센트 텍스트 표시 (sm 은 링이 작아 그리지 않음) |
size |
CoreProgressSize |
lg |
크기 단계 —
sm
32 ·
md
48 ·
lg
80 ·
xl
128 (링 두께 3 · 4 · 6 · 8)
|
tone |
CoreProgressTone |
primary |
아크 색의 의미 —
primary
·
success
·
warning
·
destructive
|
child | W? | null | 중앙 커스텀 콘텐츠 |
radialProgressStyle |
CoreRadialProgressStyle? |
null |
chrome 슬롯 (아래 표) |
CoreRadialProgressStyle 필드 (chrome)
| 필드 | 타입 | 기본값 | 설명 |
|---|---|---|---|
color |
CoreColor? |
CoreColors.primary |
아크 색상 |
backgroundColor |
CoreColor? |
CoreColors.surfaceContainerHigh (직선형 트랙과 같은 중립색) |
트랙 링 색상 |
backgroundAlpha |
double? |
null → 중립 트랙 |
값을 주면 backgroundColor 가 없을 때 아크 색을 이 알파로 페이드한 트랙 링 |
size |
double? |
CoreSize.size80 (80px, lg) |
원 지름. defaultsBySize 가 단계마다 따로 정합니다 |
strokeWidth |
double? |
CoreStrokeWidth.stroke6 (lg) |
링 두께. 단계마다 defaultsBySize 가 정합니다 |
valueTextStyle |
CoreTextStyle? |
단계가 정한 역할(
md
labelSmall
·
lg
titleMedium
·
xl
headlineLarge
— 좁은 화면에서는 글자 단계가 줄입니다) +
onSurface
|
중앙 퍼센트 텍스트. sm 은 값이 없어 그리지 않습니다 |
동작 스펙 (Behavior)#
직선형#
value변경 시 부드러운 width 애니메이션 (300ms ease-in-out)- 인디케이터에 borderRadius 적용 — 양쪽 둥근 모양
- 트랙 배경 기본값:
surfaceContainerHigh(채움 색을 따라가지 않는 중립 트랙) -
채움 색은
tone이 정합니다 —primary·success·warning·destructive가 각각primary·success·warning·error를 칠합니다. 불확정(value: null) 막대도 같은 tone 색입니다. -
라벨 칸:
label(왼쪽, 남은 폭을 채움)과valueLabel(오른쪽)이 한 행으로 막대 위에,caption이 막대 아래에 놓입니다. 칸과 막대 사이는labelGapStyle·captionGapStyle(기본 8).label없이valueLabel만 주면 값은 오른쪽에 그대로 있습니다. -
칸의 글자:
labellabelMedium· 600 ·onSurface,valueLabellabelMedium·onSurfaceVariant,captionbodyXs·onSurfaceSubtle. 칸에 넣은 위젯이 이 스타일을 이어받고, 다르게 하려면Text체인으로 덮습니다. - 세 칸이 모두
null이면 막대 하나만 그립니다 — 칸이 생기기 전과 같은 트리 · DOM 입니다. -
segments를 주면 막대가 그 수의 칸으로 나뉩니다. 채워지는 칸은 지나온 단계 수 —⌊(value − min) / (max − min) × segments⌋— 이고 나머지 칸은 트랙 색입니다. 단계는 도달해야 지나간 것으로 셉니다(value: 0.99, segments: 2는 한 칸). 곱셈의 부동소수 오차로 정수 바로 아래에 떨어진 값(0.29 × 100=28.999…)은 그 단계에 도달한 것으로 봅니다. 두 플랫폼이 같은 계산(CoreProgressContract.filledSegmentsOf)을 부릅니다. -
칸은 폭을 똑같이 나누고(Flutter
Expanded, Webflex-1) 높이 · 모서리는 트랙과 같습니다. 칸 사이는segmentSpacing(기본space4) 입니다. 기본값이 밀도 축의 바닥(4 이하는 움직이지 않음)이라 밀도를 바꿔도 같고, 더 큰 값을 직접 주면 밀도를 따라 움직입니다. 칸 색이 바뀔 때는duration동안CoreEasing.move로 전환합니다 — 연속 막대의 채움 폭과 같은 시간 · 곡선입니다. - 불확정 막대에는 보여 줄 단계가 없어
segments를 주어도 연속 막대로 미끄러집니다.
원형#
- 12시 방향(-π/2)에서 시작하는 원형 아크
strokeCap: round— 양쪽 끝 둥근 형태showValueText: true시 중앙에 "75%" 형태 표시-
직선형과 같은
size(4단 —xs2·xs는 없음) ·tone축을 가집니다. 단계가 지름 · 링 두께 · 글자 역할을 함께 정하고,tone이 아크 색을 정합니다. 트랙은 어느 tone 에서나 중립 면(surfaceContainerHigh) 입니다. -
label·valueLabel·caption이 없습니다 — 중앙에는child한 칸만 있습니다. 직선형과 API 가 갈리는 자리입니다. -
값 칸(
valueLabel)은 가장 넓은 읽기100%의 너비를 미리 잡고 숫자를 끝에 붙입니다 — 9% → 10% → 100% 로 올라가도 막대가 밀리지 않습니다 (숫자는 같은 폭의 자릿수). - 불확정 막대의 움직이는 채움은 트랙의 35% 입니다 (
indeterminateFillRatio).
사용 가이드라인 (Usage Guidelines)#
완료 비율이 알려진 작업은 Progress/RadialProgress로 표시하고 알려지지 않은 작업은 Progress의 value: null 또는 Loading을 사용합니다. 기다리는 콘텐츠의 모양을 예약하려면 호출자가
Skeleton 트리를 구성합니다. 값을 사용자가 조절해야 하면 Slider를 선택합니다.
두 끝 값을 조절하는 구간 입력은 Range, 서로 다른 시점의 상태 구간은 Tracker이며 단일 완료 비율과 구분합니다.
Do#
원형 진행률은 대시보드 통계에 적합합니다.
RadialProgress(
value: 75,
showValueText: true,
)
공간이 제한된 카드에서 목표 달성률을 효과적으로 표시합니다.
Don't#
진행률이 멈춘 상태에서 아무 피드백 없이 두지 마세요.
진행이 느리면 "대용량 파일 처리 중..." 같은 텍스트를 추가하세요.
접근성 (Accessibility)#
역할#
| Web | Flutter | |
|---|---|---|
Progress |
role="progressbar"
+
aria-valuemin
/
aria-valuemax
, 값이 있을 때만
aria-valuenow
.
label
이 있으면
aria-labelledby
,
caption
이 있으면
aria-describedby
. 단계형이면
aria-valuetext
|
Semantics(value: ...)
— progressbar 역할도 min/max 도 없음. 칸이 있으면
MergeSemantics
로 한 노드가 되어
label
·
caption
글자가 이름으로 읽힘
|
RadialProgress |
role="progressbar" + aria-valuenow (min/max 없음 → ARIA 기본 0–100) |
없음 — 파일 어디에도 Semantics 가 없음 |
-
Web 의
role/aria-value*는 callerattributes보다 나중에 병합되어 덮어쓸 수 없습니다. 그 밖의 속성(aria-label등)은 그대로 통과합니다. -
Progress는label을 주면 그 글자가 막대의 이름이 됩니다(Webaria-labelledby, Flutter 병합된 노드의 라벨).caption은 Web 에서aria-describedby로 이름과 갈라진 설명이 되고, Flutter 에서는 노드가 하나로 합쳐져 이름(라벨)에 이어 읽힙니다 — 리더가 읽는 글자는 같지만 이름과 설명을 따로 구별할 수 있는 것은 Web 뿐입니다.valueLabel은 값을 화면에 다시 쓰는 칸이라 두 플랫폼 모두 읽지 않습니다 — 값은 막대 자신의 값으로 한 번만 읽힙니다. -
label이 없는Progress와RadialProgress는 이름이 없습니다 — 무엇의 진행인지 알려주는 라벨을 붙이지 않습니다.
키보드#
해당 없음. 진행률 표시는 포커스를 받지 않고 tab stop 을 만들지 않습니다. 표시 전용
이라 값을 바꾸는 키도 없습니다 — 사용자가 값을 조절해야 하면 Slider 를 쓰세요.
스크린 리더#
-
Web —
aria-valuenow에는 정규화하지 않은 원래 값이 실리고(Progress(value: 0.65)→aria-valuenow="0.65"),aria-valuemin/aria-valuemax가 함께 있어 리더가 백분율을 계산합니다.value가null인 indeterminate 상태에서는aria-valuenow를 아예 내보내지 않습니다. -
Flutter — 값은
min~max를 0–100 으로 정규화한 백분율 문자열('65%')로 실립니다. indeterminate 일 때는 숫자 대신 로케일의 로딩 문구를 값으로 내보냅니다 — 0% 를 읽어 "멈춘 것처럼" 들리는 것을 피하기 위해서입니다. -
단계형(
segments)은 두 플랫폼 모두 백분율 대신 단계를 읽습니다 — 활성 로케일의 문구로 "Step 1 of 2" / "2단계 중 1단계". Web 은 이 문구를aria-valuetext에 싣고aria-valuenow·aria-valuemin·aria-valuemax는 원래 값 그대로 둡니다. Flutter 는 같은 문구를Semantics의 value 로 냅니다. - 값이 바뀔 때 통보하는 live region 은 어느 쪽에도 없습니다. 진행 상황을 소리로 알려야 하면 호출자가 별도 라이브 리전을 둬야 합니다.
-
RadialProgress의showValueText가 그리는NN%텍스트는 Flutter 에서 이 컴포넌트가 읽히게 하는 유일한 경로입니다.
포커스 관리#
- 진입 / 이탈 / 트랩 — 전부 없음. 오버레이도 컨트롤도 아니라 포커스를 옮기지 않습니다.
알려진 제약#
-
Flutter 에는 progressbar 역할이 없습니다.
CoreSemanticRole.progressBar는 존재 하지만Progress는 rawSemantics(value:)만 쓰고 role ·minValue·maxValue를 넘기지 않습니다. 리더는 "진행률 표시줄"이라고 안내하지 않고 값만 읽습니다. 대안: 호출부가 값·최소·최대를 다시 적어Semantics(role: ..., value:, minValue:, maxValue:)로 감쌉니다. -
RadialProgress는 Flutter 스크린 리더에 존재하지 않습니다 — 시맨틱 배선이 전혀 없습니다. 대안: 호출부에서Semantics(...)로 감쌉니다. Web 은 역할이 있으나aria-valuemin/aria-valuemax가 없어 ARIA 기본값 0–100 에 의존합니다 —value가 0–100 스케일이라 결과는 맞지만 범위를 바꿀 진입점이 없습니다. -
화면에 라벨을 그리지 않고 이름만 붙이는 진입점은 플랫폼마다 다릅니다. 보이는
라벨이면 두 플랫폼 모두
label슬롯이 이름이 됩니다. 보이지 않는 이름만 필요하면 Web 은Progress(attributes: {'aria-label': ...})로 통과시킬 수 있고(강제되는 것은role과aria-value*, 칸이 있을 때의aria-labelledby·aria-describedby), Flutter 는 호출부가Semantics(label: ...)로 감싸야 합니다. -
OS 색 강제 모드에서 진행 위치가 사라질 수 있습니다. 트랙과 채움이 보더 없이
배경색만으로 구분되므로 두 색이 같은 시스템 색으로 치환되면 구분이 없어집니다.
Web 은 그때도
role="progressbar"/aria-valuenow로 값 자체는 계속 전달합니다.
크로스 플랫폼 차이점 (Platform Differences)#
| 항목 | Flutter | Web |
|---|---|---|
| 클래스명 | Progress / RadialProgress |
Progress / RadialProgress |
| 값 범위 | 0.0~1.0 (기본) | 0.0~1.0 (기본) |
| 직선형 렌더링 | Container + AnimatedContainer |
div + inline style |
| 라벨 칸 배치 | 칸이 있으면 Column[Row(label, valueLabel), Gap, 트랙, Gap, caption] |
칸이 있으면 flex flex-col 루트 아래 같은 순서 — 루트가 role="progressbar" 를 유지 |
| 단계형 렌더링 |
트랙 자리에
Row(spacing: segmentSpacing)
+
Expanded
칸, 칸 색 전환은
AnimatedContainer
|
트랙 자리에
flex
행 +
column-gap
+
flex-1
칸, 칸 색 전환은 CSS
background-color
transition — 라벨 칸이 없으면 이 행이
role="progressbar"
루트
|
| 원형 렌더링 | CustomPaint (_RadialProgressPainter) |
inline
<svg>
+
<circle>
2개 (트랙 +
stroke-dasharray
/
stroke-dashoffset
아크)
|
| 애니메이션 | AnimatedContainer 300ms | CSS transition 300ms |