ResponsiveRowColumn | CoUI
LogoCoUI

ResponsiveRowColumn

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

ResponsiveRowColumn#

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

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

Live Preview#

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

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

사용 시기#

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

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

기본 사용법#

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'),
  ],
)

switchAt — tier 임계값 override#

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

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

Props#

속성타입기본값설명
children List<Widget> / List<Component> required 렌더할 자식 목록
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.

동작#

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

플랫폼 차이#

  • Flutter: 단일 Flex(direction:, mainAxisAlignment:, crossAxisAlignment:, spacing:)direction만 tier에 따라 Axis.horizontal/Axis.vertical로 선택되고 나머지는 그대로 네이티브 Flex API로 전달
  • Web: 단일 <div>flex flex-row/flex flex-col Tailwind 클래스 + justify-*/items-* 정렬 클래스 + inline-CSS gap. 브라우저가 CSS 변수로 spacing을 런타임 해석하므로 텍스트 스케일에 자동 반응

두 플랫폼 모두 같은 CoreBreakpointTier 판정을 공유하므로 전환 시점이 픽셀 단위로 일치합니다.

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

✅ Do#

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

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

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


❌ Don't#

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

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

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

접근성 (Accessibility)#

역할 / Semantics#

양 플랫폼 모두 아무 역할도 내보내지 않습니다. 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 대응 패스스루가 아예 없어 호출자가 바깥에서 직접 감싸지 않으면 시맨틱을 주입할 수 없습니다.

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

관련 컴포넌트#

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