Basic | CoUI
LogoCoUI

Basic

다섯 개의 슬롯(leading, title, subtitle, content, trailing)을 가진 공통 슬롯 기반 레이아웃 컴포넌트

Basic#

리스트 아이템, 설정 행, 썸네일 + 텍스트 + 액션 등 leading / title / subtitle / content / trailing 다섯 슬롯을 한 줄에 배치해야 하는 공통 패턴을 위한 레이아웃 컴포넌트입니다.

  • title/subtitle/content는 가운데 셀 안에 세로로 쌓입니다.
  • leading/trailing은 양쪽 끝 셀에 독립된 alignment로 배치됩니다.
  • 제공된 슬롯만 렌더링됩니다. 누락된 슬롯은 공간을 차지하지 않습니다.

레거시 Basic / BasicLayout 두 클래스가 있었는데, 통일된 BasicforceTextStyle 플래그로 두 동작을 모두 포괄합니다.

  • forceTextStyle: true (기본값): CoUI 기본 타이포그래피 강제 적용 (레거시 Basic).
  • forceTextStyle: false: 슬롯은 전달받은 위젯을 있는 그대로 렌더 (레거시 BasicLayout).

Live Preview#

사용 시기 (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 생성자는 attributeseventHandlers 만 노출하므로, 키 핸들러가 필요하면 eventHandlers 맵으로 직접 넘겨야 합니다.

포커스#

없습니다. 포커스를 받지 않고 슬롯 사이 포커스 순서에도 관여하지 않습니다 — 각 슬롯에 넣은 위젯이 가져온 포커스 동작이 그대로 유지됩니다.

스크린 리더#

슬롯은 트리 순서대로 읽힙니다: leadingtitlesubtitlecontenttrailing. title 은 타이포그래피만 제목처럼 렌더될 뿐 heading 역할이나 Semantics(header: true) 를 갖지 않아 일반 텍스트로 읽히며, 제목 단위 탐색(heading navigation)으로는 잡히지 않습니다.

알려진 제약#

  • 그룹 이름도 역할도 없습니다. 리스트 아이템으로 쓰면서 "이 행 전체"를 하나로 읽히게 하려면 소비자가 직접 감싸야 합니다.
  • title 에 heading 시맨틱이 없습니다. 제목으로 노출해야 한다면 title 슬롯에 넣는 위젯 쪽에서 처리하세요.
  • forceTextStyle 은 타이포그래피만 바꿉니다 — 어느 값에서도 시맨틱은 달라지지 않습니다.
  • Web 은 attributes passthrough 로 role / aria-label 을 넣을 수 있지만 Flutter 생성자에는 대응 파라미터가 없어, Flutter 에서는 호출부가 Semantics 로 감싸야 합니다.

모든 컴포넌트에 공통으로 적용되는 축은 전역 접근성 축에 있습니다.

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

항목FlutterWeb
클래스명BasicBasic
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)을 공유합니다.

  • Card: 배경/보더/그림자가 있는 컨테이너. 내부 header로 Basic을 재사용 가능
  • Gap: 슬롯 사이 간격 토큰