ResponsiveContainer
최대 너비 제약과 수평 중앙 정렬을 제공하는 반응형 컨테이너
목차
ResponsiveContainer는 자식 콘텐츠에 최대 너비 제약 + 수평 중앙 정렬 + 기본 수평 패딩을 적용하는 레이아웃 래퍼입니다. Tailwind의
container 유틸리티, Flutter의 ConstrainedBox + Center 패턴을 한 번에 제공합니다. 긴 텍스트 영역, 페이지 본문, 카드 그리드 등의 바깥 래퍼로 사용합니다.
기본 사용법#
ResponsiveContainer(
maxWidth: CoreResponsiveContainerMaxWidth.sm,
child: Card(
cardStyle: const CoreCardStyle(
padding: CoreEdgeInsets.all(CoreSpace.space16),
),
child: Text('Centred, max-width content.'),
),
)
ResponsiveContainer(
maxWidth: CoreResponsiveContainerMaxWidth.sm,
child: Card(
child: div([Text('Centred, max-width content.').bodyMedium.onSurface]),
),
)
Live Preview#
코드 보기
class ResponsiveContainerDefaultExample extends StatelessComponent {
const ResponsiveContainerDefaultExample({super.key});
@override
Component build(BuildContext context) {
final cs = context.theme.colorScheme;
return ResponsiveContainer(
maxWidth: CoreResponsiveContainerMaxWidth.sm,
child: Card(
cardStyle: const CoreCardStyle(padding: CoreSpace.space16),
child: div(
classes: 'text-${CoreTextStyles.bodyMedium.name} text-${cs.onSurface}',
[
Text(
'This content is centred and capped at the "sm" breakpoint '
'(640 px). Resize the preview to see the container stay '
'inside the max width with symmetric horizontal padding.',
),
],
),
),
);
}
}
class ResponsiveContainerDefaultExample extends StatelessWidget {
const ResponsiveContainerDefaultExample({super.key});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final cs = theme.colorScheme;
return ResponsiveContainer(
maxWidth: CoreResponsiveContainerMaxWidth.sm,
child: Card(
cardStyle: const CoreCardStyle(
padding: CoreEdgeInsets.all(CoreSpace.space16),
),
child: DefaultTextStyle.merge(
style: theme.typography.bodyMedium.toValue(theme: theme).copyWith(color: cs.onSurface.toValue()),
child: const Text(
'This content is centred and capped at the "sm" breakpoint '
'(640 px). Resize the preview to see the container stay '
'inside the max width with symmetric horizontal padding.',
),
),
),
);
}
}
사용 시기#
- 본문 영역이 넓은 모니터에서 가독성 있게 너비가 제한되어야 할 때
- 폼/카드 리스트/블로그 포스트의 바깥 경계
- 좌우 여백을 일관되게 유지하고 싶을 때 (
CoreSpace.space16기본)
responsiveMaxWidth — tier별 max-width override#
정적인 maxWidth 하나만 쓰는 대신, Breakpoint의 3단계 tier(mobile/tablet/desktop)별로
서로 다른 max-width 프리셋을 지정할 수 있습니다. 예를 들어 mobile에서는 제약 없이(full) 화면을 꽉 채우고, tablet부터 점점 좁혀가는 레이아웃을 만들 때 유용합니다.
ResponsiveContainer(
// 정적 maxWidth는 responsiveMaxWidth가 지정하지 않은 tier의 폴백으로 쓰입니다.
maxWidth: CoreResponsiveContainerMaxWidth.lg,
responsiveMaxWidth: const {
CoreBreakpointTier.mobile: CoreResponsiveContainerMaxWidth.full,
CoreBreakpointTier.tablet: CoreResponsiveContainerMaxWidth.md,
CoreBreakpointTier.desktop: CoreResponsiveContainerMaxWidth.xl,
},
child: Card(
cardStyle: const CoreCardStyle(
padding: CoreEdgeInsets.all(CoreSpace.space16),
),
child: Text('Max-width switches per breakpoint tier.'),
),
)
ResponsiveContainer(
// 정적 maxWidth는 responsiveMaxWidth가 지정하지 않은 tier의 폴백으로 쓰입니다.
maxWidth: CoreResponsiveContainerMaxWidth.lg,
responsiveMaxWidth: const {
CoreBreakpointTier.mobile: CoreResponsiveContainerMaxWidth.full,
CoreBreakpointTier.tablet: CoreResponsiveContainerMaxWidth.md,
CoreBreakpointTier.desktop: CoreResponsiveContainerMaxWidth.xl,
},
child: Card(
child: div([
Text('Max-width switches per breakpoint tier.').bodyMedium.onSurface,
]),
),
)
우선순위는 responsiveMaxWidth[현재 tier] > maxWidth > 디자인 시스템 기본값(xl)입니다 — 맵에 현재 tier가 없으면 정적
maxWidth로 개별 폴백하고(부분(partial) 맵 허용, Breakpoint의 boundaries가 tier별로 개별 폴백하는 것과 동일한 패턴),
responsiveMaxWidth 자체가 null(기본값)이면 breakpoint 관측 자체가 생략되어 기존 정적 maxWidth
동작이 그대로 유지됩니다 — non-breaking 확장입니다.
Props / Parameters#
W는 Flutter의 Widget, Web의 Component를 뜻하는 콘텐츠 타입입니다.
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
child | W | 필수 | 래핑할 자식 |
maxWidth |
CoreResponsiveContainerMaxWidth? |
xl |
최대 너비 프리셋 (
sm
=640 /
md
=768 /
lg
=1024 /
xl
=1280 /
xxl
=1536 /
full
=제약 없음)
|
responsiveMaxWidth |
Map<CoreBreakpointTier, CoreResponsiveContainerMaxWidth>? |
null (정적 maxWidth 유지) |
tier별 max-width override. 누락된 tier는 개별적으로 maxWidth로 폴백 |
centered |
bool? |
true |
가로 중앙 정렬 여부. false이면 부모 왼쪽에 붙음 |
padding |
CoreEdgeInsets? |
CoreSpace.space16 (수평) |
수평 패딩. Flutter / Web 동일하게 CoreEdgeInsets |
responsiveContainerStyle |
CoreResponsiveContainerStyle? |
null |
위 필드를 프로젝트·테마 단위로 override하는 슬롯 |
CoreResponsiveContainerStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
maxWidth |
CoreResponsiveContainerMaxWidth? |
Max-width preset. |
centered |
bool? |
Whether the container is horizontally centred inside its parent. |
horizontalPadding |
CoreEdgeInsets? |
Horizontal padding. |
동작 스펙 (Behavior)#
- viewport ≥ maxWidth: 컨테이너는
maxWidth제약으로 중앙에 고정, 좌우 여백은 균등 - viewport < maxWidth: 컨테이너는 부모 전체 너비를 차지, 수평 패딩이 그대로 적용
maxWidth: .full— 제약 없음, 그냥 부모 너비 그대로 + 수평 패딩-
responsiveMaxWidth가 지정되면 위 판정 전에 현재CoreBreakpointTier(Breakpoint와 동일한resolveBreakpoint관측)로 먼저 유효maxWidth프리셋을 선택합니다
사용 가이드라인 (Usage Guidelines)#
단일 콘텐츠의 최대 폭과 중앙 정렬에는 ResponsiveContainer를 사용합니다. 단계별 여유 폭을 패딩으로 계산해 빌더에 전달하려면 StageContainer, 자식의 행/열 전환은 ResponsiveRowColumn, 여러 셀 배치는 ResponsiveGrid를 선택합니다.
✅ Do#
콘텐츠 성격에 맞는 maxWidth 프리셋 선택
// 텍스트 위주 폼/본문 — 가독성을 위해 좁게
ResponsiveContainer(
maxWidth: CoreResponsiveContainerMaxWidth.sm,
child: form,
)
// 카드 그리드/대시보드 — 기본값(xl)보다 넓게
ResponsiveContainer(
maxWidth: CoreResponsiveContainerMaxWidth.xxl,
child: cardGrid,
)
기본값 xl(1280px)은 일반적인 페이지 폭에 맞춰져 있지만, 긴 텍스트 줄 길이를 줄여야 하는 폼/본문에는 sm/md가, 카드 그리드처럼 가로 공간이 많이 필요한 콘텐츠에는 xl보다 넓은 프리셋이 더 잘 맞습니다.
❌ Don't#
centered: false로 두고 중앙 정렬된 페이지 본문을 기대하지 않기
// ❌ centered: false면 부모 왼쪽에 붙어 좌측 정렬된 컬럼처럼 보임
ResponsiveContainer(
maxWidth: CoreResponsiveContainerMaxWidth.md,
centered: false,
child: pageBody,
)
centered(기본값 true)를 끄면 컨테이너가 maxWidth로 제약되되 부모의 왼쪽 가장자리에 붙습니다 — 넓은 화면에서 중앙 정렬된 본문을 원한다면 centered를 명시적으로 끄지 마세요.
접근성 (Accessibility)#
역할#
양 플랫폼 모두 아무 역할도 내보내지 않습니다. Flutter는 ConstrainedBox > SizedBox
> Padding(옵션으로 Align)만 반환하고 Semantics 호출이 없으며, Web은 <main>/<section>
같은 시맨틱 요소가 아니라 평범한 <div>를 렌더하고 호출자가 넘긴 attributes만 통과시킵니다.
읽는 입장에서 이것은 max-width · 중앙 정렬 · 패딩만 담당하는 투명한 상자라는 뜻입니다. 페이지 셸로 쓰더라도 랜드마크 목록에는 나타나지 않으므로, 이 영역을 랜드마크로 만들 책임은 호출자에게 있습니다.
키보드#
처리하는 키가 없습니다.
포커스 관리#
포커스를 받지 않습니다. Flutter에 FocusNode/Focus/FocusScope가 없고 Web에
tabindex가 없어 탭 순서에 끼어들지 않습니다. "반응형"이라는 이름과 달리 tier 판정은 빌드 시점의 resolveBreakpoint
조회일 뿐, 인터랙티브 표면이 아닙니다.
스크린 리더#
스스로는 아무것도 읽히지 않고 리더는 곧장 child로 통과합니다. 브레이크포인트 tier가 바뀌어 max-width가 달라져도 아무런 안내가 없습니다
— 레이아웃 변경은 시각적으로만 일어납니다.
알려진 제약#
-
랜드마크 시맨틱이 없습니다. 페이지 본문 래퍼로 쓴다면 랜드마크는 직접 붙여야 합니다. Web은
attributes: {'role': 'main'}패스스루로 주입할 수 있지만, Flutter 생성자에는attributes/시맨틱 파라미터가 아예 없어 호출자가 바깥에서Semantics로 감싸야 합니다 — 이 비대칭은 코드 그대로입니다. - 포커스 관리·키보드 조작·레이아웃 변경 안내가 전부 없습니다.
전역으로 보장되는 항목(동작 줄이기·고대비·색 강제 모드 등)은 전역 접근성 축을 참고하세요.
크로스 플랫폼 차이점 (Platform Differences)#
Flutter는 ConstrainedBox + 전체 폭 SizedBox + Padding을 사용하고 중앙 정렬 시
Align(topCenter)를 추가합니다. Web은 <div>의 width, max-width, 가로 auto margin과 padding을 CSS로 적용합니다. 두 쪽 모두 같은 Core 프리셋을 읽지만 Flutter는 논리 px와
theme.scaling, Web은 CSS 토큰 길이로 해석하므로 같은 숫자만으로 최종 픽셀 일치를 단정하지 않습니다. 자체 클릭·선택·상태 콜백은 없습니다.
레거시 vs 통일 비교 (Migration Notes)#
이전 버전의 Web CoUI 를 참조하는 코드에는 이 컴포넌트가 Container 라는 이름이었을 수 있습니다. Flutter 자체 위젯인 Container
와 이름이 겹쳐 프레임워크 심볼과 충돌하므로, Web 도 Flutter-first 원칙에 따라 ResponsiveContainer 로 개명되었습니다.
| 항목 | 레거시 (구 Web Container) | 통일 ResponsiveContainer |
|---|---|---|
| API | 동일 named properties | 동일 named properties |
| 이름 충돌 | Flutter/Dart Container 와 겹침 |
겹치지 않음, 반응형 max-width 성격을 이름에 명시 |
마이그레이션: Container(...) → ResponsiveContainer(...) — 파라미터는 그대로이므로 클래스명만 바꾸면 됩니다.