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#
코드 보기
class DividerDefaultExample extends StatelessComponent {
const DividerDefaultExample({super.key});
@override
Component build(BuildContext context) {
return const Divider();
}
}
class DividerDefaultExample extends StatelessWidget {
const DividerDefaultExample({super.key});
@override
Widget build(BuildContext context) {
return const Divider();
}
}
사용 시기#
- 리스트 항목 사이나 섹션 사이에 시각적 경계선이 필요할 때
- 폼 필드나 메뉴 항목 사이를 구분할 때
- "또는" 구분선처럼 라벨이 있는 구분선이 필요할 때
Props / Parameters#
W 는 Flutter의 Widget, Web의 Jaspr Component 입니다. W?
는 선택 슬롯입니다.
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
orientation |
CoreDividerOrientation |
horizontal |
구분선 방향 |
child |
W? |
null |
선 사이에 표시할 슬롯 ("OR" 등) |
semantic |
bool |
false |
장식 선이 아닌 의미 있는 그룹 경계로 알릴지 |
dividerStyle |
CoreDividerStyle? |
null |
chrome 단일 진입점 (아래 표 참고) |
CoreDividerStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
color | CoreColor? | 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)#
| 항목 | Flutter | Web |
|---|---|---|
| 라벨과 선 배치 |
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 — 시각적 선 없이 순수 간격.