Timeline#
이벤트나 활동 내역을 시간 순서대로 시각적으로 나열하는 컴포넌트입니다.
Live Preview#
class TimelineDefaultExample extends StatelessComponent {
const TimelineDefaultExample({super.key});
@override
Component build(BuildContext context) {
return Timeline(
items: [
CoreTimelineItem(title: 'Order placed', timestamp: '10:00 AM'),
CoreTimelineItem(title: 'Processing', timestamp: '11:30 AM'),
CoreTimelineItem(title: 'Shipped', timestamp: '2:00 PM'),
],
);
}
}
class TimelineDefaultExample extends StatelessWidget {
const TimelineDefaultExample({super.key});
@override
Widget build(BuildContext context) {
return Timeline(
items: [
CoreTimelineItem(title: 'Order placed', timestamp: '10:00 AM'),
CoreTimelineItem(title: 'Processing', timestamp: '11:30 AM'),
CoreTimelineItem(title: 'Shipped', timestamp: '2:00 PM'),
],
);
}
}
class TimelineChainExample extends StatelessComponent {
const TimelineChainExample({super.key});
@override
Component build(BuildContext context) {
return Timeline(
items: [
CoreTimelineItem(title: 'Order placed', timestamp: '10:00 AM'),
CoreTimelineItem(title: 'Processing', timestamp: '11:30 AM'),
CoreTimelineItem(title: 'Shipped', timestamp: '2:00 PM'),
],
).withStyle(
const CoreTimelineStyle(
dotColor: CoreColor.token(CoreColors.success),
dotSize: CoreSpace.space16,
dotCornerRadius: CoreRadius.radius4,
connectorColor: CoreColor.token(CoreColors.outlineVariant),
connectorThickness: CoreStrokeWidth.stroke4,
titlePadding: CoreEdgeInsets.directional(start: CoreSpace.space16),
),
);
}
}
class TimelineChainExample extends StatelessWidget {
const TimelineChainExample({super.key});
@override
Widget build(BuildContext context) {
return Timeline(
items: [
CoreTimelineItem(title: 'Order placed', timestamp: '10:00 AM'),
CoreTimelineItem(title: 'Processing', timestamp: '11:30 AM'),
CoreTimelineItem(title: 'Shipped', timestamp: '2:00 PM'),
],
).withStyle(
const CoreTimelineStyle(
dotColor: CoreColor.token(CoreColors.success),
dotSize: CoreSpace.space16,
dotCornerRadius: CoreRadius.radius4,
connectorColor: CoreColor.token(CoreColors.outlineVariant),
connectorThickness: CoreStrokeWidth.stroke4,
titlePadding: CoreEdgeInsets.directional(start: CoreSpace.space16),
),
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 활동 이력, 변경 로그를 시간 순서로 나열할 때
- 각 이벤트에 제목, 날짜, 상세 설명이 필요할 때
대신 다른 컴포넌트를 사용하세요:
Steps: 단계 간 진행형 프로세스Stepper: 다단계 폼
기본 사용법 (Basic Usage)#
Timeline(
items: [
CoreTimelineItem(
title: 'Order placed',
timestamp: '10:00 AM',
description: 'Your order has been received.',
),
CoreTimelineItem(
title: 'Processing',
timestamp: '11:30 AM',
),
CoreTimelineItem(
title: 'Shipped',
timestamp: '2:00 PM',
),
],
)
Timeline(
items: [
CoreTimelineItem(
title: 'Order placed',
timestamp: '10:00 AM',
description: 'Your order has been received.',
),
CoreTimelineItem(
title: 'Processing',
timestamp: '11:30 AM',
),
CoreTimelineItem(
title: 'Shipped',
timestamp: '2:00 PM',
),
],
)
빠른 오버라이드 (Chain)#
이미 만든 Timeline 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.
class TimelineChainExample extends StatelessWidget {
const TimelineChainExample({super.key});
@override
Widget build(BuildContext context) {
return Timeline(
items: [
CoreTimelineItem(title: 'Order placed', timestamp: '10:00 AM'),
CoreTimelineItem(title: 'Processing', timestamp: '11:30 AM'),
CoreTimelineItem(title: 'Shipped', timestamp: '2:00 PM'),
],
).withStyle(
const CoreTimelineStyle(
dotColor: CoreColor.token(CoreColors.success),
dotSize: CoreSpace.space16,
dotCornerRadius: CoreRadius.radius4,
connectorColor: CoreColor.token(CoreColors.outlineVariant),
connectorThickness: CoreStrokeWidth.stroke4,
titlePadding: CoreEdgeInsets.directional(start: CoreSpace.space16),
),
);
}
}
class TimelineChainExample extends StatelessComponent {
const TimelineChainExample({super.key});
@override
Component build(BuildContext context) {
return Timeline(
items: [
CoreTimelineItem(title: 'Order placed', timestamp: '10:00 AM'),
CoreTimelineItem(title: 'Processing', timestamp: '11:30 AM'),
CoreTimelineItem(title: 'Shipped', timestamp: '2:00 PM'),
],
).withStyle(
const CoreTimelineStyle(
dotColor: CoreColor.token(CoreColors.success),
dotSize: CoreSpace.space16,
dotCornerRadius: CoreRadius.radius4,
connectorColor: CoreColor.token(CoreColors.outlineVariant),
connectorThickness: CoreStrokeWidth.stroke4,
titlePadding: CoreEdgeInsets.directional(start: CoreSpace.space16),
),
);
}
}
Props / Parameters#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
items |
List<CoreTimelineItem> |
필수 | 타임라인 아이템 목록 |
timelineStyle |
CoreTimelineStyle? |
null |
인스턴스 스타일 (Style 시스템 참조) |
스타일 시스템 (Style System)#
Timeline 의 모든 chrome / dimensional / nested-slot 오버라이드는 CoreTimelineStyle 단일 슬롯으로 흐릅니다. 시간 컬럼 너비도 chrome 이라
timeColumnWidth 로 들어갑니다.
시맨틱 vs 스타일#
- 데이터 입력: 위젯 파라미터로 직접 (
items) - chrome / dimensional / 슬롯 스타일:
CoreTimelineStyle한 곳으로 (아래 필드 표)
Resolve chain#
design system default for timeline
→ CoreTimelineTheme.style // 프로젝트 공통
→ parent component slot override
→ widget.timelineStyle // 인스턴스별
각 nested 슬롯 스타일 (timestampTextStyle / titleTextStyle / descriptionTextStyle) 은
CoreTextStyle 자체 resolve chain 으로 다시 한 번 머지됩니다.
CoreTimelineStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
dotColor | CoreColor? | Dot indicator fill colour. |
dotSize |
double? |
Dot indicator diameter (logical px). |
dotCornerRadius |
double? |
Dot indicator corner radius (logical px). 9999 = circle. |
dotBorderColor |
CoreColor? |
Dot indicator border stroke colour. Paired with [dotBorderWidth] — the ring is drawn only when BOTH are set; see the note above.
Deliberately has no default* — absence is the design.
The ring is opt-in and gated on the PAIR: Flutter
border: dotBorderColor != null && dotBorderWidth != null ?.all(…) : null
(a null BoxDecoration.border paints nothing), Web writes the
border
entry only inside that same
&&
. Defaulting both draws a ring around every dot in every timeline. Defaulting only this one is worse than leaving it alone — the
&&
still fails, so the value can never take effect on its own and the class would advertise a default unreachable by construction.
|
dotBorderWidth |
double? |
Dot indicator border stroke width (logical px). Paired with [dotBorderColor]; see the note above.
Deliberately has no default* — absence is the design.
Other half of the same
&&
gate; identical reasoning.
|
dotTopMargin |
double? |
Dot indicator top margin so it aligns with the first text baseline (logical px). |
connectorColor | CoreColor? | Connector line colour. |
connectorThickness |
double? |
Connector line thickness (logical px). |
connectorOvershoot |
double? |
Connector line overshoot — the connector extends past the row gap by this amount so it visually continues into the next item's dot (logical px). |
rowSpacing |
double? |
Vertical spacing between adjacent timeline rows / blocks (logical px). Stays a flat double — the rows wrapper consumes this scalar via native
Column.spacing
(Flutter) / inline
gap
rem (Web) per N-sibling uniform distribution pattern.
|
columnGapStyle |
CoreGapStyle? |
Nested [CoreGapStyle] for the horizontal gap between the timestamp / dot / content columns. Forwarded straight to
Gap(gapStyle: …)
(Flutter) / consumed as inline rem (Web) per nested-slot pattern (1-off Gap between named siblings inside the Row).
|
timeColumnWidth |
double? |
Width of the timestamp column (logical px). |
titlePadding |
CoreEdgeInsets? |
Padding of the item title. Falls back to [defaultTitlePadding]. |
contentTopGapStyle |
CoreGapStyle? |
Nested [CoreGapStyle] for the vertical gap between the title and the description. Forwarded straight to
Gap(gapStyle: …)
(Flutter) / consumed as inline rem (Web) per nested-slot pattern (1-off Gap between named siblings inside the content column).
|
timestampTextStyle |
CoreTextStyle? |
Timestamp text style override. null defers to [defaultTimestampTextStyle]. |
titleTextStyle |
CoreTextStyle? |
Item title text style override. null defers to [defaultTitleTextStyle]. |
descriptionTextStyle |
CoreTextStyle? |
Item description text style override. null defers to [defaultDescriptionTextStyle]. |
동작 스펙 (Behavior)#
- 수직으로 이벤트를 나열
- 점(dot) + 세로 연결선으로 시간 흐름 표현
- 마지막 아이템에는 연결선 없음
사용 가이드라인 (Usage Guidelines)#
✅ Do#
상세 설명이 없는 이벤트는 description 생략
CoreTimelineItem(title: 'Processing', timestamp: '11:30 AM') // description 생략 가능
이유: description은 optional 필드이므로 값이 없으면 그 아이템은 제목과 타임스탬프만 렌더링되어, 불필요한 빈 텍스트 블록이 생기지 않습니다.
❌ Don't#
항목 수가 중요한 화면에 Timeline 단독 사용 금지
// ❌ "총 5단계 중 3번째"를 전달해야 하는데 Timeline 만 배치
Timeline(items: orderStatusSteps)
이유: Timeline 은 role/label 을 내보내지 않아 스크린 리더가 항목 개수나 순서 관계를 안내하지 못합니다. 항목 수가 중요한 자리라면 호출자가 바깥에 목록 구조나 요약 라벨을 추가해야 합니다.
접근성 (Accessibility)#
Timeline 은 역할도 라벨도 내보내지 않습니다 — Flutter 쪽에 Semantics 호출이 없고, Web 은 전부 역할 없는
<div> 로 렌더합니다. 타임스탬프 · 제목 · 설명은 순서대로 배치된 일반 텍스트로 읽히고, 점(dot)과 연결선은 순수 장식이라 발표되지 않습니다.
알려진 제약#
- 목록 시맨틱이 없습니다. 스크린 리더가 "N개 항목 중 M번째" 를 말해주지 못하므로, 항목 수가 중요한 자리라면 호출자가 바깥에서 목록 구조나 라벨을 제공해야 합니다.
- 시간 순서가 시각 배치로만 표현됩니다. 타임스탬프 텍스트 자체는 읽히지만 "이전/다음" 관계는 발표되지 않습니다.
컴포넌트와 무관하게 적용되는 축(동작 줄이기 · 고대비 · 색 강제 모드 · 최소 터치 타겟)은 전역 접근성 축에 정리되어 있습니다.
크로스 플랫폼 차이점 (Platform Differences)#
| 항목 | Flutter | Web |
|---|---|---|
| 렌더링 | Column(spacing:) + Row + 연결선 CustomPaint |
div + CSS flex + gap |
| 시간 컬럼 너비 | CoreTimelineStyle.timeColumnWidth (logical px) |
CoreTimelineStyle.timeColumnWidth (rem 변환) |