Divider | CoUI
본문으로 건너뛰기
LogoCoUI

Divider

콘텐츠 영역을 구분하는 통일 구분선 컴포넌트

목차

콘텐츠 영역을 시각적으로 구분하는 통일 구분선 컴포넌트입니다. Divider는 Flutter/Web 동일한 파라미터 이름을 가지며, 수평/수직 orientation, 두께, 색상, 들여쓰기, 중앙 child 슬롯을 지원합니다.

기본 사용법#

// 기본 수평 구분선
Divider()

// 수직 구분선 (부모가 높이를 제공해야 함)
SizedBox(
  height: CoreSpace.space64,
  child: const Divider(orientation: .vertical),
)

// 들여쓰기
Divider(
  dividerStyle: CoreDividerStyle(
    indent: CoreSpace.space16,
    endIndent: CoreSpace.space16,
  ),
)

// 라벨이 있는 구분선
Divider(child: Text('OR').labelMedium.onSurfaceVariant)
// 기본 수평 구분선
Divider()

// 수직 구분선 (부모가 높이를 제공해야 함)
div(
  [const Divider(orientation: .vertical)],
  styles: Styles(raw: {'height': '${CoreSpace.space64}px'}),
)

// 들여쓰기
Divider(
  dividerStyle: CoreDividerStyle(
    indent: CoreSpace.space16,
    endIndent: CoreSpace.space16,
  ),
)

// 라벨이 있는 구분선
Divider(child: Text('OR').labelMedium.onSurfaceVariant)

빠른 오버라이드 (Chain)#

이미 만든 Divider 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.

class DividerChainExample extends StatelessWidget {
  const DividerChainExample({super.key});

  @override
  Widget build(BuildContext context) {
    return const Divider().withStyle(
      const CoreDividerStyle(
        color: CoreColor.token(CoreColors.primary),
        thickness: CoreStrokeWidth.stroke4,
        indent: CoreSpace.space24,
        endIndent: CoreSpace.space24,
      ),
    );
  }
}
class DividerChainExample extends StatelessComponent {
  const DividerChainExample({super.key});

  @override
  Component build(BuildContext context) {
    return const Divider().withStyle(
      const CoreDividerStyle(
        color: CoreColor.token(CoreColors.primary),
        thickness: CoreStrokeWidth.stroke4,
        indent: CoreSpace.space24,
        endIndent: CoreSpace.space24,
      ),
    );
  }
}

Live Preview#

사용 시기#

  • 리스트 항목 사이나 섹션 사이에 시각적 경계선이 필요할 때
  • 폼 필드나 메뉴 항목 사이를 구분할 때
  • "또는" 구분선처럼 라벨이 있는 구분선이 필요할 때

Props / Parameters#

W 는 Flutter의 Widget, Web의 Jaspr Component 입니다. W? 는 선택 슬롯입니다.

속성타입기본값설명
orientation CoreDividerOrientation horizontal 구분선 방향
child W? null 선 사이에 표시할 슬롯 ("OR" 등)
semantic bool false 장식 선이 아닌 의미 있는 그룹 경계로 알릴지
dividerStyle CoreDividerStyle? null chrome 단일 진입점 (아래 표 참고)

CoreDividerStyle 필드#

필드타입설명
colorCoreColor?Divider line colour override.
thickness double? Line thickness in logical pixels.
indent double? Start-side inset of the line, in logical pixels. Logical, not physical: along a horizontal divider it is the reading direction's start edge — the left in a left-to-right locale, the right in a right-to-left one — and on a labelled divider it insets the segment before the label. Along a vertical divider it is the top.
endIndent double? End-side inset of the line, in logical pixels. The counterpart of [indent]: the reading direction's end edge along a horizontal divider (the right in a left-to-right locale, the left in a right-to-left one), the segment after the label on a labelled divider, the bottom along a vertical divider.
childAlignment CoreDividerChildAlignment? Where the optional centered child sits along the divider main axis. Logical, so start / end follow the reading direction. Only observable when the divider has a child — a childless divider has no label to place.
childPadding CoreEdgeInsets? Padding wrapped around the optional centered child — the resolver / widget applies this along the divider main axis only (orientation-aware). Applied as EdgeInsets.symmetric(...) / CSS padding on the child wrapper — not a sibling gap.

색상 토큰#

기본 색상은 CoreColorScheme.outlineVariant — M3 표준 divider 토큰. 프로젝트 전역 변경은 CoreComponentTheme.divider 의 style 로 설정.

동작 스펙 (Behavior)#

  • child가 없으면 한 줄, 있으면 선 → child → 선 구조입니다. orientation이 세로면 선·여백 축을 바꿉니다. thickness / indent / endIndent 단위는 논리 px입니다.
  • indent 는 선이 시작하는 쪽, endIndent 는 끝나는 쪽의 여백입니다. 가로 구분선에서 두 값은 읽기 방향을 따르는 논리 방향이라, 왼쪽→오른쪽 로케일에서는 왼쪽·오른쪽이고 오른쪽→왼쪽 로케일(ar 등)에서는 오른쪽·왼쪽입니다. 라벨이 있으면 라벨 앞의 선이 indent 를, 뒤의 선이 endIndent 를 받으므로 어느 방향에서든 indent 는 텍스트가 시작하는 가장자리에서 들여쓰는 값입니다. 세로 구분선은 방향과 무관하게 위쪽이 indent, 아래쪽이 endIndent 입니다.
  • 들여쓴 단독 선은 컨테이너의 길이에서 두 여백을 뺀 길이입니다. Flutter 는 선의 폭이 그 길이이고, Web 은 width: calc(100% - 두 여백) 가 같은 길이를 정합니다 — 100% 에 여백을 더하면 선이 시작점에서 밀려 반대쪽 가장자리 밖으로 넘치기 때문입니다. 여백이 없으면 w-full / h-full 이 그대로입니다.
  • 라벨이 있으면 라벨과 그 패딩(childPadding)은 자기 길이를 지키고, 앞뒤 두 조각이 남은 길이를 childAlignment 의 가중치대로 나눠 가집니다(기본 center 는 1 : 1). 각 조각은 자기 여백을 자기 몫 안에 두므로 indent 와 endIndent 가 달라도 라벨은 움직이지 않고, 선만 여백만큼 짧아집니다. 기본 정렬에서 라벨이 상자에 들어가면 라벨의 중심은 가로 · 세로 · 두 읽기 방향 어디서나 상자의 정중앙입니다. 여백이 몫 밖에 있으면 두 여백 차이의 절반만큼 라벨이 한쪽으로 치우치기 때문에, 여백은 몫 안에 둡니다. 몫이 여백보다 작은 조각은 선의 길이가 없습니다.
  • start / end 정렬에서 가중치가 0 인 조각은 자기 여백만큼의 길이이고, 라벨은 그 여백 뒤에서 시작합니다. 나머지 조각이 남는 길이를 모두 가져갑니다.
  • 라벨(패딩 포함)이 상자보다 길면 두 조각은 길이가 없고, 라벨은 읽기 시작 가장자리에서 시작해 반대쪽으로 넘칩니다.
  • 부모가 내용의 폭을 따르면(Flutter IntrinsicWidth, Web 의 inline-block 등) 나눌 상자가 없으므로 가중치가 있는 조각은 자기 여백이 허락하는 만큼만 깁니다. 기본 정렬의 구분선은 라벨과 패딩에 더 큰 여백의 두 배를 더한 폭이 되고 라벨은 그 가운데에 놓입니다.
  • 자체 이벤트·포커스·비활성 상태는 없습니다. 기본은 장식용이며 semantic: true 일 때 그룹 경계의 의미·로컬라이즈된 이름을 추가합니다.
  • 선이 필요 없고 빈 공간만 필요하면 Gap 입니다. 드래그로 패널 크기를 조정하는 경계이면 Resizable 을 씁니다.

Web DOM 구조 (라벨이 있을 때)#

라벨이 있는 구분선은 격자(grid) 하나에 세 요소를 담습니다. 요소마다 트랙 하나를 받고, 두 선 요소의 margin 이 각자의 여백입니다.

<!-- 가로: 기본 정렬, indent / endIndent 가 있을 때 -->
<div class="grid w-full items-center"
     style="grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr)">
  <div style="height: …; margin-inline-start: <indent>"></div>       <!-- 앞 선: 첫 트랙 -->
  <div style="padding-left: …; padding-right: …">라벨</div>           <!-- 라벨: 내용 길이 트랙 -->
  <div style="height: …; margin-inline-end: <endIndent>"></div>      <!-- 뒤 선: 마지막 트랙 -->
</div>
  • 선 요소는 격자 항목이라 트랙을 가로질러 늘어나고 자기 margin 만큼만 짧아집니다. 그래서 트랙의 길이는 여백과 무관하고, 두 트랙이 라벨이 남긴 길이를 가중치대로 나눠 가집니다 — Flutter 에서 Flexible 이 자기 Padding 을 안고 있는 것과 같은 분할입니다. 가운데 트랙은 라벨의 길이를 따릅니다.
  • 가중치가 양수인 조각은 minmax(0, Nfr) 트랙입니다. 최솟값이 0 이라 라벨이 축 전체를 차지해도 선의 margin 이 트랙을 붙들지 않습니다. 가중치가 0 인 조각(start / end 정렬)은 auto 트랙이라 자기 여백만큼의 길이가 됩니다.
  • 세로 구분선은 같은 구조에 grid-template-rows 와 grid justify-items-center h-full 을 쓰고 선의 margin-top / margin-bottom 이 여백입니다. 세로는 읽기 방향과 무관합니다.
  • 읽기 방향은 dir 이 정합니다. 격자의 첫 트랙이 읽기 시작 쪽이고, margin-inline-* 가 같은 방향을 따르므로 오른쪽→왼쪽 로케일에서도 앞 선이 오른쪽에서 indent 만큼 들여써집니다.
  • 라벨이 없는 구분선은 격자가 아니라 선 하나(<div>)이고, 위 구조와 무관합니다.

사용 가이드라인 (Usage Guidelines)#

✅ Do#

수직 구분선은 높이가 있는 부모로 감싸기

SizedBox(
  height: CoreSpace.space64,
  child: const Divider(orientation: .vertical),
)

수직 Divider는 내부적으로 height: double.infinity로 선을 그리므로, 부모가 명시적 높이를 주지 않으면 선이 렌더링되지 않거나 레이아웃 오류가 납니다.


❌ Don't#

구분선 의미 전달을 orientation 파라미터에 기대지 않기

// ❌ orientation만 주면 접근성 트리에도 구분선으로 전달될 거라 기대
Divider(orientation: .vertical)

orientation은 레이아웃만 바꿀 뿐 role="separator" / aria-orientation을 emit 하지 않습니다 — 리더에게 구분 의미를 전달하려면 Web은 attributes: {'role': 'separator', 'aria-orientation': 'vertical'}를 직접 넘기고, Flutter는 Semantics로 직접 감싸야 합니다.

접근성 (Accessibility)#

역할#

양 플랫폼 모두 아무 역할도 내보내지 않습니다. Flutter 는 Padding / Row / Column 과 선을 그리는 Container 만 조립하고 어디에도 Semantics 를 감싸지 않습니다. Web 은 role 도 aria-* 도 없는 평범한 <div> 를 emit 하며, 붙는 속성은 호출자가 넘긴 attributes passthrough 뿐입니다.

orientation (CoreDividerOrientation.horizontal / vertical) 은 레이아웃만 바꾸고 aria-orientation 으로 노출되지 않습니다. 즉 이 선은 접근성 트리에서 구분선으로 안내되지도, 장식 요소로 명시되지도 않은 상태입니다.

키보드#

키 입력을 처리하지 않습니다.

포커스 관리#

포커스를 받지 않습니다 — 양 플랫폼 모두 FocusNode / Focus / tabindex 가 없고 포커스 링도 없습니다.

스크린 리더#

선 자체는 아무것도 읽히지 않습니다. child 슬롯을 쓴 경우(예: "OR" 라벨) 그 자식의 텍스트는 읽히지만, 양옆의 선이 구분선으로 식별되지 않으므로 리더 사용자에게는 구조적 의미 없이 단어 하나가 떠 있는 것으로 들립니다.

알려진 제약#

  • 구분선 의미가 필요하면 소비자가 직접 붙여야 합니다. Web 은 passthrough 로 attributes: {'role': 'separator', 'aria-orientation': 'vertical'} 를 넘길 수 있습니다.
  • Flutter 에는 그런 훅이 없습니다 — Divider 를 소비자 코드에서 Semantics 로 감싸야 합니다.
  • 반대로 순수 장식으로 두고 싶다면 지금 상태로 충분합니다. 아무것도 읽히지 않으므로 추가 작업이 필요 없습니다.

전역으로 적용되는 항목(감소된 모션·고대비·강제 색상 등)은 전역 접근성 축을 참고하세요.

크로스 플랫폼 차이점 (Platform Differences)#

항목FlutterWeb
라벨과 선 배치 Row / Column 의 Flexible 조각이 남은 길이를 나눠 갖고 각 조각이 자기 Padding 을 안음 격자 트랙 하나가 조각 하나이고 선의 margin 이 트랙 안에 있음 — 라벨의 자리가 같음
여백 방향 EdgeInsetsDirectional (start / end) CSS margin-inline-start / margin-inline-end
라벨이 상자보다 길 때 라벨이 한 줄로 넘치고 RenderFlex 가 넘침을 보고하며, 물리 왼쪽 가장자리에서 시작(오른쪽→왼쪽에서도) 읽기 시작 가장자리에서 시작하고 보고는 없음, 텍스트 라벨은 상자 안에서 줄바꿈
의미 있는 경계 separator 전용 역할 대신 이름 있는 Semantics container role=separator와 로케일 aria-label
child 의미자식 의미 유지자식 DOM 의미 유지

레거시 vs 통일 비교 (Migration Notes)#

이전 버전의 CoUI 를 참조하는 코드에는 순수 얇은 선 전용 컴포넌트인 구버전 클래스가 남아 있을 수 있습니다(옛 이름: Separator). Divider 는 그 기능을 전부 포함하는 상위 집합이라 별도 컴포넌트 없이 하나로 통합되었습니다.

항목구버전 클래스통일 Divider
순수 선✅✅
중앙 라벨 슬롯("OR" 등)❌ 없음✅ child
비대칭 여백대칭 여백만indent + endIndent (비대칭 가능)
chrome override 평면 파라미터로 분산 dividerStyle: CoreDividerStyle(...) 단일 슬롯

마이그레이션: 색상 · 두께 파라미터는 dividerStyle 슬롯의 color/thickness 필드로, 대칭 여백은 indent(시작)·endIndent(끝) 로 그대로 옮기면 됩니다 — 위 Props 표를 참고하세요.

같은 얇은 선을 두 컴포넌트로 노출하면 "어느 걸 쓰지" 혼란이 생기므로, 새/수정 코드는 Divider 만 사용하세요.

관련 컴포넌트#

  • Gap — 시각적 선 없이 순수 간격.