Basic#
리스트 아이템, 설정 행, 썸네일 + 텍스트 + 액션 등 leading / title / subtitle / content / trailing 다섯 슬롯을 한 줄에 배치해야 하는 공통 패턴을 위한 레이아웃 컴포넌트입니다.
- title/subtitle/content는 가운데 셀 안에 세로로 쌓입니다.
- leading/trailing은 양쪽 끝 셀에 독립된 alignment로 배치됩니다.
- 제공된 슬롯만 렌더링됩니다. 누락된 슬롯은 공간을 차지하지 않습니다.
레거시 Basic / BasicLayout 두 클래스가 있었는데, 통일된 Basic의 forceTextStyle
플래그로 두 동작을 모두 포괄합니다.
forceTextStyle: true(기본값): CoUI 기본 타이포그래피 강제 적용 (레거시Basic).forceTextStyle: false: 슬롯은 전달받은 위젯을 있는 그대로 렌더 (레거시BasicLayout).
Live Preview#
class BasicDefaultExample extends StatelessComponent {
const BasicDefaultExample({super.key});
@override
Component build(BuildContext context) {
return Basic(
leading: const Icon(LucideIcons.user),
title: Text('User Profile'),
subtitle: Text('Manage account settings'),
content: Text('Signed in as alice@example.com'),
trailing: const Icon(LucideIcons.chevronRight),
);
}
}
class BasicDefaultExample extends StatelessWidget {
const BasicDefaultExample({super.key});
@override
Widget build(BuildContext context) {
return Basic(
leading: const Icon(LucideIcons.user),
title: const Text('User Profile'),
subtitle: const Text('Manage account settings'),
content: const Text('Signed in as alice@example.com'),
trailing: const Icon(LucideIcons.chevronRight),
);
}
}
class BasicChainExample extends StatelessComponent {
const BasicChainExample({super.key});
@override
Component build(BuildContext context) {
return Basic(
leading: const Icon(LucideIcons.user),
title: Text('User Profile'),
subtitle: Text('Manage account settings'),
content: Text('Signed in as alice@example.com'),
trailing: const Icon(LucideIcons.chevronRight),
).withStyle(
const CoreBasicStyle(
padding: CoreEdgeInsets.all(CoreSpace.space16),
leadingTitleGapStyle: CoreGapStyle(size: CoreSpace.space16),
titleSubtitleGapStyle: CoreGapStyle(size: CoreSpace.space4),
titleTextStyle: CoreTextStyle.token(
CoreTextStyles.titleMedium,
color: CoreColor.token(CoreColors.primary),
),
subtitleTextStyle: CoreTextStyle.token(
CoreTextStyles.bodySmall,
color: CoreColor.token(CoreColors.onSurfaceVariant),
),
),
);
}
}
class BasicChainExample extends StatelessWidget {
const BasicChainExample({super.key});
@override
Widget build(BuildContext context) {
return Basic(
leading: const Icon(LucideIcons.user),
title: const Text('User Profile'),
subtitle: const Text('Manage account settings'),
content: const Text('Signed in as alice@example.com'),
trailing: const Icon(LucideIcons.chevronRight),
).withStyle(
const CoreBasicStyle(
padding: CoreEdgeInsets.all(CoreSpace.space16),
leadingTitleGapStyle: CoreGapStyle(size: CoreSpace.space16),
titleSubtitleGapStyle: CoreGapStyle(size: CoreSpace.space4),
titleTextStyle: CoreTextStyle.token(
CoreTextStyles.titleMedium,
color: CoreColor.token(CoreColors.primary),
),
subtitleTextStyle: CoreTextStyle.token(
CoreTextStyles.bodySmall,
color: CoreColor.token(CoreColors.onSurfaceVariant),
),
),
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 리스트 아이템 / 설정 행 / 알림 카드처럼 아이콘 + 제목 + 본문 + 액션 구조가 반복될 때
- 여러 화면에서 같은 슬롯 배치를 재사용하고 싶을 때
- Card, Accordion 등 상위 컴포넌트의 header 슬롯에 일관된 구조를 넣고 싶을 때
대신 다른 컴포넌트를 사용하세요:
Card: 배경/보더/그림자 등 카드 스타일이 필요할 때 (카드 내부에서Basic을 header로 쓰는 조합 가능)Row/Column: 슬롯 개념이 필요 없는 자유 배치
기본 사용법 (Basic Usage)#
Basic(
leading: const Icon(LucideIcons.user),
title: const Text('User Profile'),
subtitle: const Text('Manage account settings'),
content: const Text('Signed in as alice@example.com'),
trailing: const Icon(LucideIcons.chevronRight),
)
빠른 오버라이드 (Chain)#
이미 만든 Basic 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.
class BasicChainExample extends StatelessWidget {
const BasicChainExample({super.key});
@override
Widget build(BuildContext context) {
return Basic(
leading: const Icon(LucideIcons.user),
title: const Text('User Profile'),
subtitle: const Text('Manage account settings'),
content: const Text('Signed in as alice@example.com'),
trailing: const Icon(LucideIcons.chevronRight),
).withStyle(
const CoreBasicStyle(
padding: CoreEdgeInsets.all(CoreSpace.space16),
leadingTitleGapStyle: CoreGapStyle(size: CoreSpace.space16),
titleSubtitleGapStyle: CoreGapStyle(size: CoreSpace.space4),
titleTextStyle: CoreTextStyle.token(
CoreTextStyles.titleMedium,
color: CoreColor.token(CoreColors.primary),
),
subtitleTextStyle: CoreTextStyle.token(
CoreTextStyles.bodySmall,
color: CoreColor.token(CoreColors.onSurfaceVariant),
),
),
);
}
}
class BasicChainExample extends StatelessComponent {
const BasicChainExample({super.key});
@override
Component build(BuildContext context) {
return Basic(
leading: const Icon(LucideIcons.user),
title: Text('User Profile'),
subtitle: Text('Manage account settings'),
content: Text('Signed in as alice@example.com'),
trailing: const Icon(LucideIcons.chevronRight),
).withStyle(
const CoreBasicStyle(
padding: CoreEdgeInsets.all(CoreSpace.space16),
leadingTitleGapStyle: CoreGapStyle(size: CoreSpace.space16),
titleSubtitleGapStyle: CoreGapStyle(size: CoreSpace.space4),
titleTextStyle: CoreTextStyle.token(
CoreTextStyles.titleMedium,
color: CoreColor.token(CoreColors.primary),
),
subtitleTextStyle: CoreTextStyle.token(
CoreTextStyles.bodySmall,
color: CoreColor.token(CoreColors.onSurfaceVariant),
),
),
);
}
}
Props / Parameters#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
leading |
Widget? / Component? |
null | 행 시작 영역 위젯 (아이콘/아바타 등) |
title |
Widget? / Component? |
null | 상단 제목 슬롯 |
subtitle |
Widget? / Component? |
null | 제목 아래 부제 슬롯 |
content |
Widget? / Component? |
null | 본문 슬롯 |
trailing |
Widget? / Component? |
null | 행 끝 영역 위젯 (chevron/버튼 등) |
forceTextStyle |
bool |
true |
CoUI 기본 타이포그래피 강제 적용 여부 |
basicStyle |
CoreBasicStyle? |
null |
모든 chrome (alignment / spacing / padding / nested text styles) 을 한 번에 주입하는 슬롯. 자세한 필드는 아래
CoreBasicStyle
표 참고.
|
CoreBasicStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
leadingAlignment |
CoreBasicAlignment? |
Alignment of the leading slot inside its cell. |
trailingAlignment |
CoreBasicAlignment? |
Alignment of the trailing slot inside its cell. |
titleAlignment |
CoreBasicAlignment? |
Alignment of the title slot inside its cell. |
subtitleAlignment |
CoreBasicAlignment? |
Alignment of the subtitle slot inside its cell. |
contentAlignment |
CoreBasicAlignment? |
Alignment of the content slot inside its cell. |
titleContentGapStyle |
CoreGapStyle? |
Nested [CoreGapStyle] slot for the horizontal gap between
leading
/ body /
trailing
— forwarded straight to the
Gap
widgets the row interleaves.
null
defers to [defaultTitleContentGapStyle]. (Per the receiving
Gap
owns its own scaling).
|
leadingTitleGapStyle |
CoreGapStyle? |
Nested [CoreGapStyle] slot for the vertical gap between the title+subtitle block and the
content
slot inside the body column — forwarded straight to the inner
Gap
.
null
defers to [defaultLeadingTitleGapStyle].
|
titleSubtitleGapStyle |
CoreGapStyle? |
Nested [CoreGapStyle] slot for the vertical gap between the
title
and
subtitle
rows inside the body column — forwarded straight to the inner
Gap
.
null
defers to [defaultTitleSubtitleGapStyle].
|
mainAxisAlignment |
CoreBasicMainAxisAlignment? |
Outer row main-axis alignment. |
padding |
CoreEdgeInsets? |
Outer padding (pre-scaling). null defers to [defaultPadding]. |
titleTextStyle |
CoreTextStyle? |
Title slot text style override (color + fontWeight + size + …). |
subtitleTextStyle |
CoreTextStyle? |
Subtitle slot text style override. |
contentTextStyle |
CoreTextStyle? |
Content slot text style override. |
사용 가이드라인 (Usage Guidelines)#
✅ Do#
슬롯에는 스타일 없는 Text를 넣고 forceTextStyle 기본값에 맡기기
Basic(
title: const Text('User Profile'),
subtitle: const Text('Manage account settings'),
)
forceTextStyle: true(기본값)가 DefaultTextStyle.merge로 title/subtitle/content 슬롯에 CoUI 표준 타이포그래피 계층을 자동 적용합니다. 슬롯마다 개별 TextStyle을 손으로 맞출 필요가 없습니다.
❌ Don't#
title 슬롯에 heading 시맨틱을 기대하지 않기
// ❌ title 이 스크린 리더의 heading 탐색에 잡힐 거라 기대
Basic(
title: const Text('Section Title'),
content: const Text('...'),
)
title 슬롯은 타이포그래피만 제목처럼 보이게 렌더될 뿐 Semantics(header: true)/heading role 을 붙이지 않아 일반 텍스트로 읽힙니다. heading 시맨틱이 필요하면 title 에 넣는 위젯을 직접 Semantics(Flutter) / role(Web) 로 감싸세요.
접근성 (Accessibility)#
역할 / Semantics#
양쪽 플랫폼 모두 역할을 내보내지 않습니다. Flutter 는 Column / Row / Align / Padding
조합만 그리며 Semantics · MergeSemantics · ExcludeSemantics 를 쓰지 않고, Web 은 루트와 슬롯 래퍼가 모두 평범한
<div> 라 role / aria-* 가 붙지 않습니다. 다섯 슬롯을 하나로 묶는 노드가 없으므로 보조기술에는
한 줄짜리 행이 아니라 나란히 놓인 다섯 개의 콘텐츠로 보입니다.
키보드#
없습니다. 양쪽 모두 키를 다루지 않습니다. Web 생성자는 attributes 와 eventHandlers 만 노출하므로, 키 핸들러가 필요하면
eventHandlers 맵으로 직접 넘겨야 합니다.
포커스#
없습니다. 포커스를 받지 않고 슬롯 사이 포커스 순서에도 관여하지 않습니다 — 각 슬롯에 넣은 위젯이 가져온 포커스 동작이 그대로 유지됩니다.
스크린 리더#
슬롯은 트리 순서대로 읽힙니다: leading → title → subtitle → content
→ trailing. title 은 타이포그래피만 제목처럼 렌더될 뿐 heading 역할이나 Semantics(header: true)
를 갖지 않아 일반 텍스트로 읽히며, 제목 단위 탐색(heading navigation)으로는 잡히지 않습니다.
알려진 제약#
- 그룹 이름도 역할도 없습니다. 리스트 아이템으로 쓰면서 "이 행 전체"를 하나로 읽히게 하려면 소비자가 직접 감싸야 합니다.
title에 heading 시맨틱이 없습니다. 제목으로 노출해야 한다면title슬롯에 넣는 위젯 쪽에서 처리하세요.forceTextStyle은 타이포그래피만 바꿉니다 — 어느 값에서도 시맨틱은 달라지지 않습니다.-
Web 은
attributespassthrough 로role/aria-label을 넣을 수 있지만 Flutter 생성자에는 대응 파라미터가 없어, Flutter 에서는 호출부가Semantics로 감싸야 합니다.
모든 컴포넌트에 공통으로 적용되는 축은 전역 접근성 축에 있습니다.
크로스 플랫폼 차이점 (Platform Differences)#
| 항목 | Flutter | Web |
|---|---|---|
| 클래스명 | Basic | Basic |
| chrome 입력 | basicStyle: CoreBasicStyle(...) |
basicStyle: CoreBasicStyle(...) |
| 렌더링 | Padding + IntrinsicWidth/Height + Row + Column |
<div> flex row + inner flex column |
| 텍스트 스타일 | DefaultTextStyle.merge |
Tailwind class (text-${ts.titleSmall} 등) |
양쪽 모두 동일한 CoreBasicAlignment / CoreBasicMainAxisAlignment 값을 사용하며, 시각적 결과는 같도록 토큰(spacing/typography)을 공유합니다.