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

ResponsiveRowColumn

switchAt breakpoint tier에서 Row↔Column으로 방향이 뒤바뀌는 레이아웃 primitive

목차

ResponsiveRowColumn은 children을 switchAt tier 이상에서는 Row(가로), 미만에서는 Column(세로) 로 렌더링하는 레이아웃 primitive입니다. responsive_framework의 동명 ResponsiveRowColumn을 CoUI 크로스플랫폼(Flutter+Web) 버전으로 옮긴 것입니다.

방향(direction)은 위젯 파라미터가 아니라 매 build마다 앰비언트 viewport breakpoint tier에서 파생됩니다 — Breakpoint/resolveBreakpoint 공유 접근자(Breakpoint 참조)를 그대로 재사용하며, 이 컴포넌트 자신은 별도의 측정(LayoutBuilder/MediaQuery 직접 호출)을 하지 않습니다.

기본 사용법#

ResponsiveRowColumn(
  responsiveRowColumnStyle: const CoreResponsiveRowColumnStyle(
    spacing: CoreSpace.space12,
  ),
  children: [
    _StatCard(label: 'Views', value: '1,204'),
    _StatCard(label: 'Likes', value: '86'),
    _StatCard(label: 'Comments', value: '12'),
  ],
)
ResponsiveRowColumn(
  responsiveRowColumnStyle: const CoreResponsiveRowColumnStyle(
    spacing: CoreSpace.space12,
  ),
  children: [
    _StatCard(label: 'Views', value: '1,204'),
    _StatCard(label: 'Likes', value: '86'),
    _StatCard(label: 'Comments', value: '12'),
  ],
)

Live Preview#

두 프리뷰가 다른 방향(Row/Column)으로 보일 수 있습니다 — 전환 기준이 각 데모 자신의 뷰포트이기 때문입니다. Web 데모는 브라우저 창 너비를, Flutter 데모는 페이지에 임베드된 Flutter 뷰의 너비를 기준으로 전환합니다(Breakpoint 와 동일한 시맨틱). 실제 Flutter 앱에서는 OS 창 너비가 기준입니다.

브라우저 창(또는 에뮬레이터)을 리사이즈하면 Row↔Column 전환을 확인할 수 있습니다. default variant는 switchAt: tablet(기본값)이라 tablet tier 이상에서 가로로 배치되고, desktop variant는 switchAt: .desktop으로 임계값을 높여 desktop tier 미만에서는 계속 세로로 쌓입니다.

사용 시기#

  • 카드/통계/폼 필드 묶음을 넓은 화면에서는 가로로, 좁은 화면에서는 세로로 쌓고 싶을 때
  • responsive_framework의 ResponsiveRowColumn을 CoUI 네이티브 컴포넌트로 교체할 때
  • 이미 다른 곳에서 소비 중인 CoreBreakpointTier(3단계: mobile/tablet/desktop)와 같은 판정 기준을 공유하고 싶을 때 — Breakpoint가 쓰는 것과 동일한 resolveBreakpoint 진입점을 사용합니다

값에 따라 자식을 통째로 다른 위젯으로 교체하고 싶다면 대신 MediaQuery(MediaQueryVisibility)를 사용하세요.

switchAt — tier 임계값 override#

ResponsiveRowColumn(
  switchAt: .desktop, // 기본값 .tablet 대신 .desktop 이상에서만 Row
  children: [...],
)

switchAt은 BEHAVIOUR 값(어느 tier에서 어느 axis로 렌더할지를 결정)이라 시각 chrome이 아니므로, CoreXxxStyle 슬롯이 아니라 CoreResponsiveRowColumnContract.defaultSwitchAt(기본값 .tablet) 위에 직접 얹힙니다 — Breakpoint의 resolveBreakpoint가 boundaries를 다루는 것과 같은 위치 원칙입니다.

Props / Parameters#

W는 Flutter의 Widget, Web의 Component를 뜻합니다.

속성타입기본값설명
childrenList<W>필수렌더할 자식 목록
switchAt CoreBreakpointTier? CoreBreakpointTier.tablet 이 tier 이상에서 Row, 미만에서 Column
responsiveRowColumnStyle CoreResponsiveRowColumnStyle? — 간격/정렬 Style 슬롯 (아래 표)

CoreResponsiveRowColumnStyle 필드#

필드타입설명
spacing double? Evenly-distributed spacing between children (logical px at 1.0 scaling). This is N-sibling spacing — it flows through the platform-native Row / Column / Flex.spacing API (Flutter) or a gap-* utility (Web), not a 1-off CoreGapStyle slot ( resolver/scaling-and-gap.md ).
mainAxisAlignment CoreMainAxisAlignment? Main-axis alignment — see the class doc for the direction-relative meaning.
crossAxisAlignment CoreCrossAxisAlignment? Cross-axis alignment — see the class doc for the direction-relative meaning.

동작 스펙 (Behavior)#

  • tier ≥ switchAt: Axis.horizontal — 자식이 Row처럼 가로로 배치
  • tier < switchAt: Axis.vertical — 자식이 Column처럼 세로로 스택
  • tier 판정은 Breakpoint와 동일한 resolveBreakpoint(context) 호출 결과(CoreBreakpointTierOrder.isAtLeast)를 그대로 재사용합니다 — 별도의 breakpoint 정의나 측정 로직이 없습니다

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

한 줄의 자식을 tier에 따라 가로/세로로 전환할 때 사용합니다. 열 수·셀 span이 필요한 배치는 ResponsiveGrid, 콘텐츠 최대 폭만 제약하려면 ResponsiveContainer를 사용합니다.

✅ Do#

간격은 수동 Gap/SizedBox 대신 spacing으로

ResponsiveRowColumn(
  responsiveRowColumnStyle: const CoreResponsiveRowColumnStyle(
    spacing: CoreSpace.space12,
  ),
  children: [statCardA, statCardB, statCardC],
)

spacing은 Row/Column 어느 쪽으로 렌더되든 네이티브 Flex.spacing(Flutter) / CSS gap(Web)으로 그대로 흐르므로, 방향이 바뀌어도 간격을 다시 계산할 필요가 없습니다.


❌ Don't#

mainAxisAlignment/crossAxisAlignment이 고정된 화면 방향을 뜻한다고 가정하지 않기

// ❌ "항상 세로 중앙 정렬"을 기대하면 안 됨
ResponsiveRowColumn(
  responsiveRowColumnStyle: const CoreResponsiveRowColumnStyle(
    crossAxisAlignment: CoreCrossAxisAlignment.center,
  ),
  children: [...],
)

두 정렬 필드는 direction-relative입니다 — tier가 바뀌어 Row↔Column으로 전환되면 같은 crossAxisAlignment: .center도 어떤 tier에서는 세로 중앙, 다른 tier에서는 가로 중앙을 의미하게 됩니다. "항상 왼쪽 정렬" 같은 절대 방향을 기대하지 마세요.

접근성 (Accessibility)#

역할#

양 플랫폼 모두 아무 역할도 내보내지 않습니다. Flutter는 순수한 Flex를 반환하고 디렉터리 어디에도 Semantics가 없으며, Web은 flex/gap 클래스만 붙은 평범한 <div>를 렌더하고 호출자가 넘긴 attributes만 통과시킵니다.

읽는 입장에서 이것은 자식을 그대로 통과시키는 레이아웃 래퍼입니다. 이 묶음이 하나의 그룹으로 읽혀야 한다면 그룹 역할과 이름은 호출자가 붙여야 합니다.

키보드#

처리하는 키가 없습니다. Web 생성자는 attributes / classes / css / eventHandlers / id / key만 받으므로 키보드 핸들러 패스스루 prop조차 없습니다 — 키 처리가 필요하면 eventHandlers로 직접 넣어야 합니다.

포커스 관리#

포커스를 받지 않습니다. 양 플랫폼 모두 FocusNode/Focus/tabindex가 없어 탭 순서에 끼어들지 않고, 자식은 각자 가져온 포커스 동작을 그대로 유지합니다.

스크린 리더#

스스로는 아무것도 읽히지 않고, 읽히는 것은 자식의 내용뿐입니다. 축이 가로에서 세로로 바뀌어도 자식은 선언 순서 그대로 읽히며, 축이 바뀌었다는 사실 자체는 어디에도 알려지지 않습니다.

알려진 제약#

  • 순수한 레이아웃 스위치입니다. 브레이크포인트 tier에 따라 row/column을 고르는 것 외에 아무것도 하지 않습니다 — 자식의 논리적 순서를 재배치하지 않고, 자식을 시맨틱하게 묶지도 않습니다. 시각 순서와 읽기 순서가 달라야 하는 배치라면 그 조정은 호출자 몫입니다.
  • Flutter에는 attributes 대응 패스스루가 아예 없어 호출자가 바깥에서 직접 감싸지 않으면 시맨틱을 주입할 수 없습니다.

전역으로 보장되는 항목(동작 줄이기·고대비·색 강제 모드 등)은 전역 접근성 축을 참고하세요.

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

항목FlutterWeb
배치 Flex의 direction/spacing/정렬 <div>의 flex direction, 정렬 class와 CSS gap
폭 변경MediaQuery 기반 breakpoint 의존성viewport resize 리스너로 다시 빌드
간격 단위논리 px와 테마 scalingCSS 길이와 토큰 변수
시맨틱 주입호출자가 바깥에서 감쌈루트 attributes passthrough

관련 컴포넌트#

  • Breakpoint — ResponsiveRowColumn이 방향을 파생시키는 데 재사용하는 공유 tier 접근자
  • ResponsiveContainer — 단일 콘텐츠에 max-width 제약을 주는 래퍼
  • ResponsiveGrid — N×M 셀 그리드가 필요할 때