Card
슬롯 기반 레이아웃을 제공하는 카드 컨테이너 컴포넌트
목차
관련 정보를 하나의 단위로 묶어 표시하는 컨테이너입니다. media / header / body / footer
/ overlay 슬롯을 제공합니다.
기본 사용법 (Basic Usage)#
// 기본 카드 (filled, no elevation)
Card(
body: Text('카드 내용'),
)
// 슬롯 기반 레이아웃
Card(
header: Text('제목'),
body: Text('본문 내용입니다.'),
footer: Text('푸터'),
)
// variant + elevation (chrome / dimensional 은 cardStyle 로 통합)
Card(
variant: CoreCardVariant.filled,
cardStyle: CoreCardStyle(
elevation: CoreCardElevation.medium,
),
body: Text('강조된 카드'),
)
// 클릭 가능
Card(
onTap: () => print('clicked'),
body: Text('눌러 보세요'),
)
Live Preview#
코드 보기
class CardDefaultExample extends StatelessComponent {
const CardDefaultExample({super.key});
@override
Component build(BuildContext context) {
return Card(
header: Text('Card Title'),
body: Text('This is the card body content.'),
footer: Button(
variant: CoreButtonVariant.primary,
onPressed: () {},
child: Text('Action'),
),
);
}
}
class CardDefaultExample extends StatelessWidget {
const CardDefaultExample({super.key});
@override
Widget build(BuildContext context) {
return Card(
header: const Text('Card Title'),
body: const Text('This is the card body content.'),
footer: Button(
variant: CoreButtonVariant.primary,
onPressed: () {},
child: const Text('Action'),
),
);
}
}
구조 (Anatomy)#
코드가 정본입니다. 부분 이름은 위젯 슬롯과 같고, Figma ❖ Card
레이어와도 맞습니다.
| 부분 | 역할 | 슬롯 |
|---|---|---|
media | 상단 미디어 | media |
overlay | media 위 | overlay |
header | 헤더 | header |
body | 본문 | body |
footer | 푸터 | footer |
child | 슬롯 대신 쓰는 커스텀 자식 | child |
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 관련 정보를 하나의 단위로 묶어 표시할 때 (프로필, 상품, 게시물 등)
- 시각적으로 콘텐츠를 구분하고 계층을 만들 때
- 클릭 가능한 콘텐츠 블록이 필요할 때
대신 다른 컴포넌트를 사용하세요:
- OutlinedContainer: 카드 콘텐츠 슬롯 없이 저수준 표면 chrome만 조립할 때
- Stat: 자유 콘텐츠 묶음 대신 지표 값과 변화량을 표시할 때
- Hero: 항목 카드가 아니라 페이지의 인트로 섹션이 필요할 때
- Accordion: 접고 펼 수 있는 콘텐츠 섹션이 필요할 때
- Table: 구조화된 데이터를 행과 열로 표시할 때
- Dialog: 본문 흐름의 표면 대신 콘텐츠를 모달 오버레이로 표시할 때
Props / Parameters#
W 는 Flutter의 Widget, Web의 Jaspr Component 입니다. W?
는 선택 슬롯입니다.
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
variant |
CoreCardVariant |
.filled |
시각 변형 (filled / outlined) |
media |
W? |
null |
상단 미디어 영역 (이미지 등) |
header | W? | null | 헤더 영역 |
body | W? | null | 본문 영역 |
footer | W? | null | 푸터 영역 |
overlay |
W? |
null |
media 위에 얹는 오버레이 |
child | W? | null | 커스텀 자식 (슬롯 대신) |
onTap |
VoidCallback? |
null |
탭/클릭 핸들러 |
role |
CoreSurfaceRole |
content (defaultRole) |
표면의 콘텐츠/기능 레이어 역할. ARIA 역할이 아님 |
cardStyle |
CoreCardStyle? |
null |
chrome + 치수 오버라이드 묶음 (아래 표 참고) |
CoreCardStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
surfaceStyle |
CoreSurfaceStyleId? |
Material override for this card; null inherits the surrounding material. The material — translucency, backdrop blur, chroma lift, backdrop dimming, edge sheen and refraction — is a theme axis that reaches every surface at once. A value here replaces it for this card alone: [CoreSurfaceStyleId.defaultStyle] is an opaque, stroke-bounded card inside a glass theme, [CoreSurfaceStyleId.liquidGlass] a glass card inside an opaque one. Palette, typography, geometry and density stay the surrounding theme's. [surfaceOpacity] and [surfaceBlur] tune the fill and the blur within whichever material applies. They cannot remove one, because neither reaches the sheen, the chroma lift or the backdrop dimming. Deliberately no default: null inherits the active theme material axis; a fixed default would override that axis before the resolver sees it. |
padding | CoreEdgeInsets? | Content padding override. |
borderRadius |
CoreBorderRadius? |
Border radius override. |
borderWidth |
double? |
Border width override (logical px, pre-scaling). |
backgroundColor |
CoreColor? |
Background colour override. |
borderColor | CoreColor? | Border colour override. |
boxShadow |
List<CoreShadowLayer>? |
Box shadow override. Takes precedence over [elevation]-derived shadow when set. |
headerBodyGapStyle |
CoreGapStyle? |
Gap style between the header and body slots — nested [CoreGapStyle] slot forwarded to the
Gap
widget that separates the two slots.
null
defers to [defaultHeaderBodyGapStyle].
|
bodyFooterGapStyle |
CoreGapStyle? |
Gap style between the body and footer slots — nested [CoreGapStyle] slot forwarded to the
Gap
widget that separates the two slots.
null
defers to [defaultBodyFooterGapStyle].
|
chromeAnimationDuration |
Duration? |
Chrome animation duration override (background / border / borderRadius / padding under theme change or per-instance style swap).
null
defers to [defaultChromeAnimationDuration].
|
hoverShadowAnimationDuration |
Duration? |
Hover-shadow animation duration override (AnimatedContainer wrapping the interactive surface).
null
defers to [defaultHoverShadowAnimationDuration].
|
width |
double? |
Fixed width override (logical px). Honoured when [sizing] is [CoreCardSizing.fixed].
Deliberately has no default* — absence is the design.
[sizing] is the axis that decides the geometry and it already has one ([defaultSizing] =
intrinsic
); this field is the operand the caller states for the
fixed
case, and only that case reads it. Null there still means "no measurement given": Flutter falls through to
IntrinsicWidth
on
width == null && height == null
, and the Web resolver writes the
width
rule only inside
if (merged.width != null)
. A constant would hand every
sizing: fixed
card a width nobody chose — a card that set only [height] would jump from auto to that number — and there is no design-system answer to "how wide is a card" to put here.
|
height |
double? |
Fixed height override (logical px). Honoured when [sizing] is [CoreCardSizing.fixed].
Deliberately has no default* — same reasoning as [width]
, and it shares the branch: Flutter's fall-through to
IntrinsicWidth
tests both fields at once, so a constant on either one alone puts every
fixed
card into a
SizedBox
. A card's height is its content's, which is why the two other sizing modes never read this field at all.
|
sizing |
CoreCardSizing? |
Sizing behaviour (intrinsic / expand / fixed). |
elevation |
CoreCardElevation? |
Shadow elevation level (none / soft / medium / strong). When [boxShadow] is also set, [boxShadow] wins. |
clipBehavior |
CoreClipMode? |
Clip behaviour override. |
bodyTextStyle |
CoreTextStyle? |
Body text style override (typography role + colour + weight in one
CoreTextStyle
).
null
defers to [defaultBodyTextStyle].
|
headerTextStyle |
CoreTextStyle? |
Header text style override (typography role + colour + weight).
null
defers to [defaultHeaderTextStyle].
|
overlayInset |
double? |
Overlay inset (logical px) — applied to the absolutely-positioned overlay slot.
null
defers to [defaultOverlayInset].
|
shadowBaseColor |
CoreColor? |
Shadow base colour override. null defers to [defaultShadowBaseColor]. |
hoverElevation |
CoreCardElevation? |
Hover elevation override (applied when an interactive card is hovered).
null
defers to [defaultHoverElevation].
|
surfaceBlur |
double? |
Backdrop blur sigma (logical px) for a glassmorphism surface — forwarded to the
OutlinedContainer
surface (Flutter
SurfaceBlur
/ Web
backdrop-filter: blur
).
null
/
0
= no blur. Opt-in: the design-system default cards are opaque.
|
surfaceOpacity |
double? |
Surface fill opacity (0.0–1.0) for a glassmorphism surface — lets the blurred backdrop show through the fill.
null
= fully opaque fill.
Deliberately has no
default*
— the baseline is a theme axis, and a
static const
is downstream of it.
The card raw-forwards this into the composed
OutlinedContainer
, whose resolver reads
merged.surfaceOpacity ?? theme.surfaceOpacity
: a constant here would always be non-null, so it would shadow the theme-wide surface treatment for cards specifically and put the value beyond the axis's reach — which
core/theme-axis-composition.md
forbids, since the axis's seat is token resolution and this class sits after it. The Web half additionally keys its emit on the null: it scales the background token's alpha only when this is set, and the resolver says why — an absent override has to stay null so
emitColor
still reads "no override" and leaves the background on its Tailwind token class rather than manufacturing an inline
rgb(… / a)
for every card.
|
clickableStyle |
CoreClickableStyle? |
Nested [CoreClickableStyle] slot for the composed
Clickable
on a tappable card (
onTap != null
) — press scale / durations / focus ring / disabled opacity. Merged on top of [defaultClickableStyle] (which pins
pressedScale: 1
) and raw-forwarded — the Clickable's own resolver fills the rest.
|
CoreCardStyle 변형별 기본값 (CoreCardVariantStyle)#
| 필드 | outlined | filled |
|---|---|---|
backgroundColor |
surface (fallback: surfaceContainerLowest) | surfaceContainer (fallback: surface) |
borderWidth | stroke1 | 0 |
borderColor | outlineVariant (fallback: outline) | transparent |
빠른 오버라이드 (Chain)#
이미 만든 Card 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.
.radius4처럼 Core 토큰 상수 이름과 똑같은 이름의 getter도 있습니다 — withStyle을 한 번 더 줄인 sugar로, 이름이 곧 값이라(radius4 ==
CoreRadius.radius4) 어느 컴포넌트에서 써도 뜻이 갈리지 않습니다.
class CardChainExample extends StatelessWidget {
const CardChainExample({super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
Card(
header: const Text('Card Title'),
body: const Text('This is the card body content.'),
footer: Button(
variant: CoreButtonVariant.primary,
onPressed: () {},
child: const Text('Action'),
),
).radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(borderWidth/padding)까지.
Card(
header: const Text('Card Title'),
body: const Text('This is the card body content.'),
footer: Button(
variant: CoreButtonVariant.primary,
onPressed: () {},
child: const Text('Action'),
),
).withStyle(
const CoreCardStyle(
padding: CoreEdgeInsets.all(CoreSpace.space24),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
borderWidth: CoreStrokeWidth.stroke2,
backgroundColor: CoreColor.token(
CoreColors.surfaceContainerHighest,
),
borderColor: CoreColor.token(CoreColors.tertiary),
),
),
],
);
}
}
class CardChainExample extends StatelessComponent {
const CardChainExample({super.key});
@override
Component build(BuildContext context) {
return div(
[
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
Card(
header: Text('Card Title'),
body: Text('This is the card body content.'),
footer: Button(
variant: CoreButtonVariant.primary,
onPressed: () {},
child: Text('Action'),
),
).radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(borderWidth/padding)까지.
Card(
header: Text('Card Title'),
body: Text('This is the card body content.'),
footer: Button(
variant: CoreButtonVariant.primary,
onPressed: () {},
child: Text('Action'),
),
).withStyle(
const CoreCardStyle(
padding: CoreEdgeInsets.all(CoreSpace.space24),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
borderWidth: CoreStrokeWidth.stroke2,
backgroundColor: CoreColor.token(
CoreColors.surfaceContainerHighest,
),
borderColor: CoreColor.token(CoreColors.tertiary),
),
),
],
classes: 'flex flex-col items-start',
);
}
}
스타일 시스템 (Style System)#
Card 의 모든 chrome / dimensional 오버라이드는 단일 cardStyle 필드(CoreCardStyle)
하나로 흐릅니다. width / padding / boxShadow
/ elevation 같은 값은 모두
CoreCardStyle 안의 nullable 필드이며, 위젯 생성자에는 chrome 파라미터가 없습니다.
Card(
variant: CoreCardVariant.outlined,
cardStyle: CoreCardStyle(
padding: CoreEdgeInsets.all(CoreSpace.space24),
borderRadius: CoreBorderRadius.all(CoreRadius.radius12),
backgroundColor: CoreColor.token(CoreColors.surfaceContainer),
elevation: CoreCardElevation.soft,
sizing: CoreCardSizing.fixed,
width: 320,
),
header: Text('제목'),
body: Text('본문'),
)
Resolve chain#
cardStyle 은 다음 체인을 거쳐 최종 값으로 해석됩니다 (오른쪽이 이김):
design system default
→ CoreCardTheme.style // 프로젝트 공통
→ CoreCardTheme.variantStyles[variant] // variant 별 오버라이드
→ parent component slot override // 예: TimePicker.popoverPanelStyle
→ widget.cardStyle // 인스턴스별 오버라이드
각 레이어는 CoreCardStyle 의 어떤 필드든 부분적으로 채울 수 있습니다. null 인 필드는
다음 레이어로 넘겨주고, non-null 인 필드는 해당 레이어에서 확정됩니다.
테마 적용#
ThemeData.fromCore(
coreComponentTheme: CoreComponentTheme(
card: CoreCardTheme(
style: CoreCardStyle(
// 모든 카드에 적용되는 기본
borderRadius: CoreBorderRadius.all(CoreRadius.radius16),
padding: CoreEdgeInsets.all(CoreSpace.space20),
),
variantStyles: {
CoreCardVariant.filled: CoreCardStyle(
backgroundColor: CoreColor.token(CoreColors.primaryContainer),
),
},
),
),
)
유리 테마 안의 불투명 카드 (Material Override)#
liquidGlass 프리셋에서는 모든 표면이 같은 유리 재질을 받습니다. 목록 안의 카드처럼
내용이 많은 면을 불투명하게 두려면 프리셋 전체가 아니라 그 카드의 재질만
cardStyle.surfaceStyle 로 지정합니다. defaultStyle 은 불투명한 기본 재질입니다 —
반투명 채움·배경 블러·가장자리 빛띠·굴절(그리고 앱이 켠 서리·몸체 음영)이 모두 빠지고, 평소의 외곽선이 그대로 남습니다.
Flutter 와 Web 에서 같은 호출입니다.
Card(
variant: CoreCardVariant.outlined,
cardStyle: const CoreCardStyle(surfaceStyle: .defaultStyle),
header: Text('주문 내역'),
body: Text('불투명한 카드'),
)
- 재질만 바뀝니다. 색 팔레트·타이포·모서리와 선 두께·밀도는 둘러싼 테마 그대로입니다.
-
null(기본)은 둘러싼 재질을 그대로 씁니다. 모든 카드에 적용하려면CoreCardTheme.style에 같은 값을 넣고, 한 장만 다시 바꾸려면 인스턴스cardStyle이 그 위에서 이깁니다. -
반대 방향도 같습니다.
surfaceStyle: .liquidGlass는 불투명 테마 안의 카드 한 장만 유리로 만듭니다. -
surfaceOpacity/surfaceBlur는 재질 안에서 채움과 블러를 조절하는 값입니다. 재질을 없애는 수단이 아닙니다 — 가장자리 빛띠·채도·배경 어둡힘은 두 값이 닿지 않습니다. - 접근성 설정(투명도 줄이기·고대비)은 이 오버라이드보다 항상 우선합니다.
- 카드 안의 버튼·입력 같은 컨트롤은 자기 재질을 따로 정하지 않으면 둘러싼 재질을 받습니다. 불투명 카드 안에서도 그 컨트롤은 유리일 수 있습니다.
OutlinedContainer 는 같은 필드(containerStyle.surfaceStyle)를 받고, Card
는 그 값을
합성한 컨테이너에 그대로 넘깁니다.
변형 (Variants)#
Filled (기본)#
Card(
variant: CoreCardVariant.filled,
body: Text('배경색 채움'),
)
Outlined#
Card(
variant: CoreCardVariant.outlined,
body: Text('외곽선만'),
)
Elevation 단계#
Card(
cardStyle: CoreCardStyle(elevation: CoreCardElevation.none),
body: Text('평면'),
)
Card(
cardStyle: CoreCardStyle(elevation: CoreCardElevation.soft),
body: Text('약한 그림자'),
)
Card(
cardStyle: CoreCardStyle(elevation: CoreCardElevation.medium),
body: Text('중간 그림자'),
)
Card(
cardStyle: CoreCardStyle(elevation: CoreCardElevation.strong),
body: Text('강한 그림자'),
)
미디어 카드 (이미지 + 콘텐츠)#
Card(
media: Image.network('https://example.com/photo.jpg'),
header: Text('상품명'),
body: Text('상품 설명'),
footer: Text('가격'),
)
동작 스펙 (Behavior)#
인터랙션#
- 탭/클릭:
onTap설정 시 전체 카드 영역 클릭 가능 - 호버 (onTap 있을 때): 커서가 pointer 로 변경 + hover shadow 애니메이션
-
child가 있으면 media / header / body / footer 슬롯 대신 그 내용을 렌더합니다. 슬롯 경로의overlay는media위에만 그려지며 미디어가 없으면 표시하지 않습니다. - 자체 선택·닫기·비활성 플래그는 없습니다.
onTap: null은 표시 전용이며, 자식 버튼·링크의 동작은 그 자식이 소유합니다.
애니메이션#
-
chrome (배경/보더/반경/패딩) 전환:
cardStyle.chromeAnimationDuration(기본CoreDuration.normal= 200ms) -
hover shadow 전환:
cardStyle.hoverShadowAnimationDuration(기본CoreDuration.normal= 200ms)
사용 가이드라인 (Usage Guidelines)#
✅ Do#
고정 크기가 필요하면 sizing: .fixed 와 함께 width/height 지정
Card(
cardStyle: CoreCardStyle(
sizing: CoreCardSizing.fixed,
width: 320,
height: 200,
),
body: Text('고정 크기 카드'),
)
width/height는 sizing이 .fixed일 때만 적용되는 값이라, sizing 없이 width만 주면 무시됩니다.
❌ Don't#
클릭 가능한 카드에서 onTap을 자식 위젯에만 걸지 않기
// ❌ body 안의 버튼에만 onTap — 카드 전체 클릭 영역을 놓침
Card(
body: GestureDetector(onTap: handleTap, child: Text('상세보기')),
)
// ✅ Card 자체의 onTap 사용 — 전체 영역 + hover shadow + cursor 파리티까지 포함
Card(
onTap: handleTap,
body: Text('상세보기'),
)
Card.onTap은 카드 전체를 클릭 영역으로 만들고 hover shadow 애니메이션과 커서 변경(cursor-pointer)까지 함께 처리합니다. 자식에만 핸들러를 걸면 클릭 가능 영역이 좁아지고 hover 피드백도 사라집니다.
접근성 (Accessibility)#
역할#
-
onTap이 없을 때 — 역할 없음. Web 은 클래스만 실린<div>, Flutter 는OutlinedContainer로 끝나고Semantics래핑이 없습니다. 카드는 자기 이름도 역할도 갖지 않는 순수 컨테이너입니다. -
onTap이 있을 때 — 카드 전체가Clickable로 감싸지고 역할은 그Clickable이 붙입니다. Web 은role="button"+tabindex="0", Flutter 는Semantics(button: true, enabled: true). -
이름을 받는 파라미터가 없습니다. 접근 가능한 이름은
header/body등 슬롯에 넣은 텍스트에서 계산됩니다. -
Web 은 caller
attributes를 컴포넌트 기본값보다 나중에 병합하므로Card(attributes: {'role': 'article'})처럼 역할을 덮어쓸 수 있습니다. Flutter 에는 대응 진입점이 없어 호출부가Semantics(...)로 감싸야 합니다.
키보드#
onTap 이 없는 카드는 포커스를 받지 않습니다 — 아래는 전부 onTap 이 있을 때입니다.
| 키 | Flutter | Web |
|---|---|---|
Tab | 카드 자신이 tab stop 하나 | 동일 (tabindex="0") |
Enter | onTap 실행 | onTap 실행 |
Space |
onTap 실행 |
onTap 실행 (' ' / 'Spacebar') |
| 화살표 | 없음 | 없음 |
카드 안쪽 요소에 포커스가 있을 때 Enter / Space 는 그 요소만 활성화하고 카드의
onTap 은 실행되지 않습니다 — Web 은 keydown 핸들러가 이벤트 타깃이 카드 루트
자신인지 확인하고, Flutter 는 안쪽 Clickable 의 ActivateIntent
가 먼저 처리합니다.
스크린 리더#
-
이름의 출처는 슬롯 콘텐츠입니다.
onTap카드는header·body·footer텍스트가 이어져 버튼 이름이 되므로, 본문이 긴 카드에onTap을 걸면 이름도 그만큼 길어집니다. - 상태 변화를 알리는 live region 은 없습니다. hover shadow 같은 시각 변화는 통보되지 않습니다.
-
media슬롯의 대체 텍스트에는 카드가 관여하지 않습니다 — 호출자가 넣은 위젯이 그대로 노출됩니다.
포커스 관리#
- 진입 / 이탈 — 이동 없음. 카드는 오버레이가 아니라서 포커스를 옮기지 않습니다.
- 트랩 — 없음.
Tab은 카드 밖으로 그대로 빠져나갑니다. -
포커스 표시 —
onTap카드는:focus-visible링을 그립니다 (Web 은focus-visible:outline-*유틸, Flutter 는FocusOutline). 링과 카드 사이 2px 간격은 투명이라 카드가 놓인 배경이 그대로 보입니다. -
onTap카드 안에 포커스 가능한 요소가 있으면 tab stop 이 카드 하나 + 요소마다 하나로 늘어납니다.
알려진 제약#
-
onTap카드 안에 버튼·링크를 두면 컨트롤이 중첩됩니다. Web 에서는role="button"안에 또 다른 버튼이 들어가고 리더는 두 개의 컨트롤로 읽습니다. 대안: 카드 전체가 아니라 내부 단일 액션에만 핸들러를 걸거나,onTap카드 안에는 포커스 가능한 요소를 두지 않습니다. -
filledvariant 는 경계가 배경 톤 차이뿐입니다 — 보더 두께가0, 보더 색이 투명이고 기본elevation이none입니다. OS 색 강제 모드에서 배경색이 시스템 색으로 치환되면 카드 경계가 사라집니다. Web 카드 루트에는 surface 마커가 붙어 있어 활성 표면 스타일이 강제-안전 경계를 선언한 경우에만 보더가 복원됩니다. 대안:variant: CoreCardVariant.outlined. -
Web 은 포커스 링의
ringOpacity를 반영하지 않습니다. 기본 경로는 알파 채널이 없는 Tailwindoutline-*클래스이고, 오버라이드 시 전환되는 scoped 규칙 경로도ringOpacity를1로 고정해 넘깁니다. Flutter 는scaleAlpha로 반영하므로 같은 Style 이 플랫폼마다 다르게 보입니다. 대안: 알파를 링 색 자체에 담아focusOutlineStyle.borderColor로 넘깁니다 — scoped 규칙이 색의 알파는 보존합니다. -
로딩 자리를 대신 그려주지 않습니다.
loading슬롯도 자체 스켈레톤도 없어, 값이 아직 없을 때는 호출자가 슬롯에Skeleton트리를 직접 넣어야 합니다 (header: isLoading ? Skeleton(...) : Text(title)). -
RTL 에서 네 모서리가 서로 다른
borderRadius는 뒤집히지 않습니다.CoreBorderRadius는 물리 네 모서리라, 예컨대 왼쪽 두 모서리만 둥글게 준 값은 오른쪽-왼쪽 로케일에서도 그대로 왼쪽에 렌더됩니다. 기본값은 네 모서리가 같아 해당 없습니다. 대안: RTL 로케일에서 호출부가 좌우를 뒤집은 값을 직접 넘깁니다.
크로스 플랫폼 차이점 (Platform Differences)#
| 항목 | Flutter | Web |
|---|---|---|
| 커스텀 자식 | child 콘텐츠 | DOM 커스텀 콘텐츠 래퍼 |
| 그림자 | CoreCardElevation.* → BoxShadow |
CoreCardElevation.* → CSS shadow |
| 표면 blur | surfaceBlur → SurfaceBlur |
surfaceBlur → backdrop-filter: blur() |
| 카드 한 장의 재질 | surfaceStyle → 합성한 OutlinedContainer 가 그 재질을 그림 |
surfaceStyle
→ 카드 루트가 문서 마커 대신
coui-material-surface coui-material-<id>
를 달아 그 프리셋의 규칙만 선택됨
|
| 서브트리 단위 재질 | SurfaceTheme |
대응 없음 — 인스턴스 단위 surfaceStyle 만 있음 |
| 품질 적응 | 합성한 OutlinedContainer 를 통해 받음 |
카드 루트의 표면 마커를 통해 받음 — 스타일시트와 굴절 호스트가 같은 마커를 찾음 |
Web은 기존 호환을 위해 children: List<Component>? 추가 생성자 경로를 유지합니다. 목록이 child
보다 우선하며, 양 플랫폼에서 같은 호출을 쓰려면 child 에 조립한 콘텐츠를 넣습니다.