OutlinedContainer#
child 를 토큰 기반 chrome(border + radius + optional padding + shadow + blur)으로 감싸는 박스 프리미티브(인프라)입니다.
Card·Window·Alert·ChipInput 등 다수 컴포넌트가 이 박스 chrome 위에 쌓입니다. Web 은
<div> + Tailwind 유틸리티, Flutter 는 동일 CoreOutlinedContainerContract 필드를 읽어 양 플랫폼이 default 를 공유합니다.
Live Preview#
class OutlinedContainerDefaultExample extends StatelessComponent {
const OutlinedContainerDefaultExample({super.key});
@override
Component build(BuildContext context) {
return OutlinedContainer(
containerStyle: const CoreOutlinedContainerStyle(
padding: CoreEdgeInsets.all(CoreSpace.space16),
),
child: Text('테두리 컨테이너').bodyMedium,
);
}
}
class OutlinedContainerDefaultExample extends StatelessWidget {
const OutlinedContainerDefaultExample({super.key});
@override
Widget build(BuildContext context) {
return OutlinedContainer(
containerStyle: const CoreOutlinedContainerStyle(
padding: CoreEdgeInsets.all(CoreSpace.space16),
),
child: Text('테두리 컨테이너').bodyMedium,
);
}
}
class OutlinedContainerChainExample extends StatelessComponent {
const OutlinedContainerChainExample({super.key});
@override
Component build(BuildContext context) {
return div(
[
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
OutlinedContainer(
containerStyle: const CoreOutlinedContainerStyle(
padding: CoreEdgeInsets.all(CoreSpace.space16),
),
child: Text('테두리 컨테이너').bodyMedium,
).radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(borderWidth)까지 한 번에.
OutlinedContainer(
child: Text('Full control').bodyMedium,
).withStyle(
const CoreOutlinedContainerStyle(
borderColor: CoreColor.token(CoreColors.tertiary),
borderWidth: CoreStrokeWidth.stroke2,
padding: CoreEdgeInsets.symmetric(
horizontal: CoreSpace.space24,
vertical: CoreSpace.space8,
),
),
),
],
classes: 'flex flex-col items-start',
);
}
}
class OutlinedContainerChainExample extends StatelessWidget {
const OutlinedContainerChainExample({super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
OutlinedContainer(
containerStyle: const CoreOutlinedContainerStyle(
padding: CoreEdgeInsets.all(CoreSpace.space16),
),
child: Text('테두리 컨테이너').bodyMedium,
).radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(borderWidth)까지 한 번에.
OutlinedContainer(
child: Text('Full control').bodyMedium,
).withStyle(
const CoreOutlinedContainerStyle(
borderColor: CoreColor.token(CoreColors.tertiary),
borderWidth: CoreStrokeWidth.stroke2,
padding: CoreEdgeInsets.symmetric(
horizontal: CoreSpace.space24,
vertical: CoreSpace.space8,
),
),
),
],
);
}
}
사용법#
OutlinedContainer(
containerStyle: const CoreOutlinedContainerStyle(
padding: CoreEdgeInsets.all(CoreSpace.space16),
),
child: Text('테두리 컨테이너').bodyMedium,
)
Props#
| 파라미터 | 타입 | 기본값 | 설명 |
|---|---|---|---|
child |
Widget / Component? |
null |
chrome 안에 배치되는 콘텐츠 |
containerStyle |
CoreOutlinedContainerStyle? |
null |
backgroundColor·borderColor·borderWidth·borderRadius·padding·shadow·blur 단일 진입점 |
스타일 시스템 (Style System)#
CoreOutlinedContainerStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
backgroundColor |
CoreColor? |
Background fill colour override. |
borderColor | CoreColor? | Border colour override. |
borderRadius |
CoreBorderRadius? |
Border radius override. |
borderWidth |
double? |
Border width override (logical px, pre-scaling). |
borderStyle |
CoreOutlinedContainerBorderStyle? |
Border style override (solid / dashed / dotted / none). |
dashLength |
double? |
Dash segment length when [borderStyle] is dashed (logical px).
null
→ [defaultDashLength].
|
dashSpacing |
double? |
Gap between dash segments (logical px). null → [defaultDashSpacing]. |
elevation |
CoreOutlinedContainerElevation? |
Shadow elevation override. When [boxShadow] is also set, [boxShadow] wins. |
boxShadow |
List<CoreShadowLayer>? |
Explicit shadow stack, overriding the [elevation]-derived one. The elevation enum can only name three prepared stacks, all of them soft ambient shadows. Some surface treatments are defined by shadows it cannot describe at all: a hard offset shadow has zero blur, and a raised-from-the-page look needs two layers going in opposite directions. This slot takes the stack directly so those are expressible without inventing a new enum member per look. |
padding |
CoreEdgeInsets? |
Inner padding override. Falls back to [defaultPadding] when null. |
surfaceOpacity |
double? |
Background opacity multiplier (0.0 – 1.0). Multiplied into the alpha channel of [backgroundColor]. |
surfaceBlur |
double? |
Backdrop blur sigma (logical px). When > 0, applies a
BackdropFilter
(Flutter) /
backdrop-filter: blur
(Web).
|
width |
double? |
Explicit width override (logical px).
No default, and must not have one.
Null is not "width unknown", it is "size to the child" — both platforms branch on it rather than reading a value. Flutter hands the null straight to the container's
width:
, where it means unconstrained; Web only writes the
width
declaration inside
if (width != null)
. A constant here would pin every surface built on this primitive —
Card
,
Alert
,
Window
, every picker panel — to one width, and nothing in the tree asks for that.
|
height |
double? |
Explicit height override (logical px). No default, and must not have one — same branch as [width], on the block axis. |
duration |
Duration? |
Animation duration for chrome property changes (color / radius / width / height).
null
or [Duration.zero] means no animation.
|
clipBehavior |
CoreClipBehavior? |
Clip behaviour override. |
사용 가이드라인 (Usage Guidelines)#
✅ Do#
모든 chrome을 containerStyle 하나로 지정하기
OutlinedContainer(
containerStyle: const CoreOutlinedContainerStyle(
borderRadius: CoreBorderRadius.all(CoreRadius.radius12),
padding: CoreEdgeInsets.all(CoreSpace.space16),
),
child: Text('내용').bodyMedium,
)
background·border·radius·padding·shadow·blur는 모두 containerStyle 단일 슬롯 하나로 흐릅니다 — 바깥에 또 다른 Container/<div>로 같은 chrome을 덧그리면 박스가 이중으로 그려집니다.
❌ Don't#
role/이름이 필요한 자리에 그대로 쓰지 않기
// ❌ role/aria 없이 대화상자·영역처럼 쓰는 컨테이너로 오용
OutlinedContainer(
child: importantDialogContent,
)
OutlinedContainer는 role도 이름도 포커스 대상도 내보내지 않는 순수 chrome 프리미티브입니다. 영역/대화상자 의미가 필요하면 소비자가 직접 Semantics/attributes로 감싸야 하고, 포커스 표시가 필요하면 FocusOutline을 별도로 합성해야 합니다.
접근성 (Accessibility)#
역할 / Semantics#
자체 role 이 없습니다. Flutter 에는 role 을 부여하는 Semantics 래퍼가 없고, 유일한 semantics 호출은 dashed/dotted 테두리를 그리는
CustomPaint 오버레이를 감싼 ExcludeSemantics 입니다. Web 은 <div> 에 호출자 passthrough 속성만 싣고, dashed 테두리용
<svg> 오버레이에는 aria-hidden="true" 를 붙입니다. landmark·region·group·aria-label
중 어느 것도 양 플랫폼에서 나오지 않습니다.
읽는 사람에게 이는 이 박스가 접근성 트리에 존재하지 않는 것과 같다는 뜻입니다 — 이 표면에 이름이나 영역 의미가 필요하면 소비자가 직접 부여해야 합니다.
키보드#
처리하는 키가 없습니다. Web 은 호출자 eventHandlers 를 그대로 전달하지만 자체적으로는 아무 핸들러도 설치하지 않습니다.
포커스#
포커스 대상이 아니며 어느 플랫폼에서도 traversal 정지점이 되지 않습니다. Flutter 에 FocusNode / Focus /
autofocus 가 없고 Web 에 tabindex 가 없습니다. 포커스 링도 그리지 않습니다 — 이 컴포넌트의 style 은 border·radius·shadow·blur 만 해석합니다. 포커스 표시가 필요하면
FocusOutline 을 함께 쓰세요.
스크린 리더#
자기 몫으로 안내되는 것이 없고, 리더는 곧바로 child 로 통과합니다. 장식용 dashed/dotted 테두리는 양 플랫폼 모두 접근성 트리 밖에 유지되므로(Flutter
ExcludeSemantics, Web aria-hidden="true") 테두리 스타일이 읽히는 일은 없습니다.
알려진 제약#
- 순수 chrome 이라 role·이름·설명·포커스 가능성이 전부 없습니다. 이 표면을 region / group / dialog 로 만들어야 한다면 소비자가 직접 주입해야 합니다.
-
주입 통로가 플랫폼마다 다릅니다 — Web 은
attributes:passthrough 로role/aria-*를 얹을 수 있지만, Flutter 생성자는key·child·containerStyle만 받습니다. Flutter 쪽에서는 이 표면에 이름이나 role 을 붙일 방법이 아예 없으므로, 필요하면OutlinedContainer를 소비자가 자기Semantics로 감싸야 합니다. -
전환 애니메이션에
aria-busy나 live region 처리가 없습니다 — Flutter 의AnimatedContainerchrome 전환은 보조기술에 아무것도 알리지 않습니다. 전환의 모션 축 자체는 전역 접근성 축 에서 다룹니다.