Timeline | CoUI
LogoCoUI

Timeline

시간 순서대로 이벤트를 나열하는 타임라인 컴포넌트

Timeline#

이벤트나 활동 내역을 시간 순서대로 시각적으로 나열하는 컴포넌트입니다.

Live Preview#

사용 시기 (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 필드#

필드타입설명
dotColorCoreColor?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).
connectorColorCoreColor?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)#

항목FlutterWeb
렌더링 Column(spacing:) + Row + 연결선 CustomPaint div + CSS flex + gap
시간 컬럼 너비 CoreTimelineStyle.timeColumnWidth (logical px) CoreTimelineStyle.timeColumnWidth (rem 변환)
  • Steps: 단계별 진행 표시
  • Stepper: 인터랙티브 다단계 폼