Text#
Text 는 시멘틱 typography 토큰을 적용해 일관된 텍스트 렌더링을 제공하는 컴포넌트입니다. Flutter 와 Web 양쪽이 거의 동일한 사용 패턴을 갖도록 설계되어, 같은 코드 모양으로 양쪽 플랫폼 모두 작성 가능합니다.
⚡ 권장 패턴: chain 사용#
CoUI Text 의 권장 사용법은 chain modifier 입니다. .bodyMedium / .onPrimaryContainer
/ .semiBold / .italic 처럼 token 이름을 chain 으로 적용하면 양쪽 플랫폼이 일관된 결과를 보장합니다.
// ✅ 권장: chain only
Text('Hello').bodyMedium.semiBold.onPrimaryContainer
Text('Error').bodySmall.onErrorContainer.underline
Text('Long text').s14.maxLines(2).overflow(CoreTextOverflow.ellipsis)
// ⚠️ 비권장: 직접 TextStyle 주입
// `style:` 와 같은 직접 prop 은 다른 디자인 시스템과의 호환을 위해 유지
// 되어 있지만, 양쪽 플랫폼의 동작 일치를 보장하기 어려운 케이스가 있어
// 권장하지 않습니다. CoUI 표준 chain 으로 작성하세요.
Text('Hello', style: theme.typography.bodyMedium.copyWith(...))
Live Preview#
Text('The quick brown fox jumps over the lazy dog').bodyMedium
const Text('The quick brown fox jumps over the lazy dog').bodyMedium
Per-facet variants#
Text('Display L').displayLarge
Text('Display S').displaySmall
const Text('Display L').displayLarge
const Text('Display S').displaySmall
Text('Headline Large').headlineLarge
Text('Headline Medium').headlineMedium
Text('Headline Small').headlineSmall
const Text('Headline Large').headlineLarge
const Text('Headline Medium').headlineMedium
const Text('Headline Small').headlineSmall
Text('Title Large').titleLarge
Text('Title Medium').titleMedium
Text('Title Small').titleSmall
const Text('Title Large').titleLarge
const Text('Title Medium').titleMedium
const Text('Title Small').titleSmall
Text('Body Large').bodyLarge
Text('Body Medium').bodyMedium
Text('Body Small').bodySmall
Text('Label Large').labelLarge
Text('Label Medium').labelMedium
Text('Label Small').labelSmall
const Text('Body Large').bodyLarge
const Text('Body Medium').bodyMedium
const Text('Body Small').bodySmall
const Text('Label Large').labelLarge
const Text('Label Medium').labelMedium
const Text('Label Small').labelSmall
Text('Light (300)').light
Text('Regular (400)').regular
Text('Medium (500)').medium
Text('Semi Bold (600)').semiBold
Text('Bold (700)').bold
Text('Black (900)').black
const Text('Light (300)').light
const Text('Regular (400)').regular
const Text('Medium (500)').medium
const Text('Semi Bold (600)').semiBold
const Text('Bold (700)').bold
const Text('Black (900)').black
Text('s10 (10px)').s10
Text('s14 (14px)').s14
Text('s20 (20px)').s20
Text('s32 (32px)').s32
Text('h160 (line-height 1.6)').h160
Text('tighter (-0.05em)').tighter
Text('sans (Pretendard)').sans
const Text('s10 (10px)').s10
const Text('s14 (14px)').s14
const Text('s20 (20px)').s20
const Text('s32 (32px)').s32
const Text('h160 (line-height 1.6)').h160
const Text('tighter (-0.05em)').tighter
const Text('sans (Pretendard)').sans
Text('Primary').onPrimaryContainer
Text('Secondary').onSecondaryContainer
Text('Tertiary').onTertiaryContainer
const Text('Primary').onPrimaryContainer
const Text('Secondary').onSecondaryContainer
const Text('Tertiary').onTertiaryContainer
// A fill and its foreground are a pair — the fill is drawn as a
// fill, with the paired foreground on top. Drawn as text on the
// page instead, six of these eight fall under 4.5:1.
final cs = context.theme.colorScheme;
div(
classes: 'px-${CoreSpace.scale.space12} py-${CoreSpace.scale.space4} rounded-${CoreRadius.scale.radius8} bg-${cs.onErrorContainer} text-${cs.onError}',
[Text('Error fill').labelMedium],
)
div(
classes: 'px-${CoreSpace.scale.space12} py-${CoreSpace.scale.space4} rounded-${CoreRadius.scale.radius8} bg-${cs.errorContainer} text-${cs.onErrorContainer}',
[Text('Error container').labelMedium],
)
// A fill and its foreground are a pair — the fill is drawn as a
// fill, with the paired foreground on top. Drawn as text on the
// page instead, six of these eight fall under 4.5:1.
final cs = Theme.of(context).colorScheme;
DecoratedBox(
decoration: BoxDecoration(
color: cs.onErrorContainer!.toValue(),
borderRadius: BorderRadius.circular(CoreRadius.radius8),
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: CoreSpace.space12,
vertical: CoreSpace.space4,
),
child: Text('Error fill', style: labelMedium.copyWith(
color: cs.onError!.toValue(),
)),
),
)
// Surface tokens are background colors — visualised as filled
// swatches with an on-surface label.
div(
classes: 'inline-flex items-center self-start px-${CoreSpace.scale.space12} py-${CoreSpace.scale.space8} rounded-${CoreRadius.scale.radius4} border bg-${cs.surface.toValue()}',
[Text('Surface', style: TextStyle(color: 'on-surface'))],
)
Text('On surface').onSurface
Text('Outline').outline
// Surface tokens are background colors — visualised as filled
// swatches. Inner Text uses chain `.bodyMedium` so the swatch
// label always renders with the project base typography.
Container(
padding: const EdgeInsets.symmetric(
horizontal: CoreSpace.space12,
vertical: CoreSpace.space8,
),
decoration: BoxDecoration(
color: cs.surface.toValue(),
borderRadius: BorderRadius.circular(CoreRadius.radius4),
border: Border.all(color: cs.outlineVariant.toValue()),
),
child: const Text('Surface').bodyMedium,
)
const Text('On surface').onSurface
const Text('Outline').outline
Text('Italic text').italic
Text('Underline text').underline
Text('Line-through text').lineThrough
Text('Overline text').overline
const Text('Italic text').italic
const Text('Underline text').underline
const Text('Line-through text').lineThrough
const Text('Overline text').overline
Text(
'Long text...',
overflow: CoreTextOverflow.ellipsis,
maxLines: 1,
style: TextStyle(fontSize: CoreFontSize.s14),
)
Text('Text aligned center', textAlign: CoreTextAlign.center)
.bodyMedium
Text('Text aligned end', textAlign: CoreTextAlign.end)
.bodyMedium
const Text(
'Long text...',
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: TextStyle(fontSize: CoreFontSize.s14),
)
const Text(
'Text aligned center',
textAlign: TextAlign.center,
).bodyMedium
const Text(
'Text aligned end',
textAlign: TextAlign.end,
).bodyMedium
Text('bodyLarge + semiBold').bodyLarge.semiBold
Text('bodyMedium + primary').bodyMedium.onPrimaryContainer
Text('bodySmall + error + underline')
.bodySmall
.onErrorContainer
.underline
Text('s14 + semiBold + primary').s14.semiBold.onPrimaryContainer
const Text('bodyLarge + semiBold').bodyLarge.semiBold
const Text('bodyMedium + primary').bodyMedium.onPrimaryContainer
const Text('bodySmall + error + underline')
.bodySmall
.onErrorContainer
.underline
const Text('s14 + semiBold + primary').s14.semiBold.onPrimaryContainer
Text('opacity 1.0 (default)').bodyMedium
Text('opacity 0.7').bodyMedium.opacity(0.7)
Text('opacity 0.5').bodyMedium.opacity(0.5)
Text('opacity 0.3').bodyMedium.opacity(0.3)
Text('primary @ opacity 0.5').bodyMedium.onPrimaryContainer.opacity(0.5)
Text('error @ opacity 0.7').bodyMedium.onErrorContainer.opacity(0.7)
const Text('opacity 1.0 (default)').bodyMedium
const Text('opacity 0.7').bodyMedium.opacity(0.7)
const Text('opacity 0.5').bodyMedium.opacity(0.5)
const Text('opacity 0.3').bodyMedium.opacity(0.3)
const Text('primary @ opacity 0.5').bodyMedium.onPrimaryContainer.opacity(0.5)
const Text('error @ opacity 0.7').bodyMedium.onErrorContainer.opacity(0.7)
Text.rich(TextSpan(
text: 'Mixed: ',
children: [
TextSpan(
text: 'primary ',
style: TextStyle(
color: cs.onPrimaryContainer.toValue(),
fontWeight: CoreFontWeight.scale.semibold,
),
),
const TextSpan(text: '+ '),
TextSpan(
text: 'italic error',
style: TextStyle(
color: cs.onErrorContainer.toValue(),
fontStyle: FontStyle.italic,
),
),
const TextSpan(text: ' + '),
const TextSpan(
text: 'underline',
style: TextStyle(
decoration: CoreTextDecoration.underline,
),
),
],
)).bodyMedium
Text.rich(
TextSpan(
text: 'Mixed: ',
children: [
TextSpan(
text: 'primary ',
style: TextStyle(
color: cs.onPrimaryContainer.toValue(),
fontWeight: FontWeight.w600,
),
),
const TextSpan(text: '+ '),
TextSpan(
text: 'italic error',
style: TextStyle(
color: cs.onErrorContainer.toValue(),
fontStyle: FontStyle.italic,
),
),
const TextSpan(text: ' + '),
const TextSpan(
text: 'underline',
style: TextStyle(
decoration: TextDecoration.underline,
),
),
],
),
style: ts.bodyMedium.toValue(theme: Theme.of(context)),
)
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 시멘틱 typography (display / headline / title / body / label) 가 필요할 때
- 토큰 기반 색상 / 굵기 / 기울임 등 chain 으로 스타일을 표현하고 싶을 때
- light / dark 테마 변경에 자동 반응해야 할 때
대신 다른 컴포넌트를 사용하세요:
Badge: 텍스트를 배지 형태로 강조할 때TextField: 사용자 텍스트 입력이 필요할 때Link: 클릭 가능한 링크 텍스트가 필요할 때
기본 사용법 (Basic Usage)#
// 기본 텍스트 — bodyMedium + onSurface 자동 적용
Text('안녕하세요')
// chain — 시멘틱 typography
Text('헤딩').headlineLarge
Text('본문').bodyMedium
// chain — 색상
Text('강조').onPrimaryContainer
Text('에러 메시지').onErrorContainer
// chain — 조합 (뒤가 override)
Text('제목').headlineSmall.semiBold.onPrimaryContainer
// TextStyle 직접 — CoUI 토큰 사용 (Flutter SDK 표준)
Text(
'커스텀',
style: TextStyle(
color: cs.primary,
fontSize: CoreFontSize.s14,
fontWeight: CoreFontWeight.semiBold.weight, // .weight: int → FontWeight
),
)
// 기본 텍스트 — bodyMedium + onSurface 자동 적용
Text('안녕하세요')
// chain — 시멘틱 typography (Flutter 와 동일)
Text('헤딩').headlineLarge
Text('본문').bodyMedium
// chain — 색상 (Flutter 와 동일)
Text('강조').onPrimaryContainer
Text('에러 메시지').onErrorContainer
// chain — 조합 (뒤가 override)
Text('제목').headlineSmall.semiBold.onPrimaryContainer
// TextStyle 직접 — CoUI 토큰 사용 (Flutter 와 같은 파라미터 + 같은 토큰)
Text(
'커스텀',
style: TextStyle(
color: cs.primary,
fontSize: CoreFontSize.s14,
fontWeight: CoreFontWeight.scale.semibold, // .scale.semibold: String 토큰
),
)
Props / Parameters#
Text 는 양 플랫폼에서 같은 이름 Text 를 씁니다. Web 은 CoUI 가 소유한 Text 컴포넌트이고, Flutter 는 SDK
Text 위젯을 그대로 쓰며 chain 이 그 위에 얹힙니다 — 따라서 아래 파라미터 이름은 양쪽 동일하고 타입만 플랫폼 native 로 갈립니다.
| 속성 | 타입 (Flutter / Web) | 기본값 | 설명 |
|---|---|---|---|
data | String | 필수 (첫 positional 인자) | 표시할 텍스트 |
style |
TextStyle? |
null |
직접 스타일 주입 (chain 권장) |
softWrap |
bool? |
null (→ true) |
공백에서 줄바꿈 허용 |
overflow |
TextOverflow? / CoreTextOverflow? |
null |
넘칠 때 처리 (ellipsis 등) |
maxLines |
int? |
null |
최대 줄 수 클램프 |
textAlign |
TextAlign? / CoreTextAlign? |
null |
가로 정렬 |
textDirection |
TextDirection? / CoreTextDirection? |
null |
읽기 방향 (Web → dir 속성) |
locale |
Locale? / String? (BCP 47) |
null |
로케일 (Web → lang 속성) |
semanticsLabel |
String? |
null |
접근성 이름 override (Web → aria-label) |
textScaler |
TextScaler? / double? |
null |
텍스트 배율 |
Text.rich(InlineSpan) 생성자는 data 대신 textSpan 을 받고 나머지 파라미터는 동일합니다.
Web 전용 styleBuilder: TextStyleBuilder? 는 chain modifier 가 build 시점에 토큰을 읽도록 채우는 내부 슬롯입니다 — 직접 넘기는 대신 chain 을 쓰세요.
chain 메서드#
Typography (시멘틱) — 15#
| chain | Tailwind 클래스 |
|---|---|
.displayLarge / .displayMedium / .displaySmall |
text-display-large / text-display-medium / text-display-small |
.headlineLarge / .headlineMedium / .headlineSmall |
text-headline-large
/
text-headline-medium
/
text-headline-small
|
.titleLarge / .titleMedium / .titleSmall |
text-title-large / text-title-medium / text-title-small |
.bodyLarge / .bodyMedium / .bodySmall |
text-body-large / text-body-medium / text-body-small |
.labelLarge / .labelMedium / .labelSmall |
text-label-large / text-label-medium / text-label-small |
Font weight (primitive) — 9#
.thin (100) / .extraLight (200) / .light (300) / .regular
(400) / .medium (500) / .semiBold (600) / .bold (700) / .extraBold
(800) / .black (900)
색상 (ColorScheme M3 토큰) — 39 + 7 alias#
theme.colorScheme.<token> chain 으로 노출. build 시점에 resolve 되어 Theme override 자동 반영.
accent 7 종의 채움(fill) 은 텍스트 색에서 빠집니다 — Web 은 제거, Flutter 는 @Deprecated. 이유는 아래 "accent 를 텍스트 색으로 쓸 때".
| 카테고리 | chain |
|---|---|
| Surface (8) |
.surface
.surfaceDim
.surfaceBright
.surfaceContainerLowest
.surfaceContainerLow
.surfaceContainer
.surfaceContainerHigh
.surfaceContainerHighest
|
| On surface + tint (3) | .onSurface .onSurfaceVariant .surfaceTint |
| Primary (3) | .onPrimary .primaryContainer .onPrimaryContainer |
| Secondary (3) | .onSecondary .secondaryContainer .onSecondaryContainer |
| Tertiary (3) | .onTertiary .tertiaryContainer .onTertiaryContainer |
| Error (3) | .onError .errorContainer .onErrorContainer |
| Success (3) | .onSuccess .successContainer .onSuccessContainer |
| Warning (3) | .onWarning .warningContainer .onWarningContainer |
| Info (3) | .onInfo .infoContainer .onInfoContainer |
| Outline (2) | .outline .outlineVariant |
| Inverse (3) | .inverseSurface .inverseOnSurface .inversePrimary |
| Misc (2) | .shadow .scrim |
| Aliases (7) |
.accent
(=
.tertiary
) /
.accentForeground
(=
.onTertiary
) /
.neutral
(=
.outline
) /
.neutralForeground
(=
.onSurfaceVariant
) /
.baseContent
(=
.onSurface
) /
.primaryForeground
(=
.onPrimary
) /
.secondaryForeground
(=
.onSecondary
)
|
accent 를 텍스트 색으로 쓸 때#
accent 토큰(.primary / .secondary / .tertiary / .error
/ .success / .warning / .info)은 채움(fill) 으로 설계됐고, 그 위에 얹을 전경이
on* 로 따로 있습니다. 그래서 텍스트 색으로는 제공되지 않습니다 — Web 은 getter 자체가 없고, Flutter 는
@Deprecated 라 애널라이저가 호출부를 전부 지목합니다.
accent 색조의 텍스트가 필요하면 .on{Role}Container 를 쓰세요.
// ❌ Web 에는 없고, Flutter 는 @Deprecated
Text('디스크 공간 부족').bodyMedium.warning
Text('활성 사용자').bodyMedium.primary
// ✅ 같은 색조, 페이지 위에서 읽히는 톤
Text('디스크 공간 부족').bodyMedium.onWarningContainer
Text('활성 사용자').bodyMedium.onPrimaryContainer
왜 통과하던 여섯까지 뺐나
.warning 은 처음부터 불가능했습니다. warning 은 onWarning 이 백색이 아니라 흑색인 유일한 role 이고, 이는 그 fill 이
설계상 밝다는 말과 같습니다 — 경고색은 노랑이기 때문입니다. 페이지 위 텍스트로 그리면 1.82:1 이 나오고, fill 을 어둡게 해 고치면 그 위에 얹힌 흑색 전경이 깨집니다.
나머지 여섯은 통과했습니다 — 실측 4.63:1~7.04:1 로 WCAG 1.4.3 의 4.5:1 위입니다. 뺀 이유는 읽히지 않아서가 아니라 여유가 그것뿐이어서입니다.
check_contrast.dart 가 이 쌍들을 감사한 이유 자체가 "팔레트를 조금만 조정해도 이 선을 넘는다" 였고, 브랜드 색 한 번 조정에 읽히지 않게 되는 표현 수단은 계속 제공할 만한 것이 아닙니다.
그리고 대체 톤은 이미 있었습니다 — 예제들이 이미 on{Role}Container 를 쓰고 있었고, 그 톤은 페이지 위에서 감사됩니다.
fill 자체는 그대로입니다
Badge / Alert / Banner 의 accent variant 는 채움 + 짝 전경을 계속 씁니다. 바뀐 건
"채움을 텍스트 색으로 빌려 쓰는" 경로 하나뿐입니다.
scripts/guards/tokens/check_accent_text_contrast.sh 가 7 role 전부에 대해 이 상태를 지킵니다 — getter 가 되살아나면 실패합니다. 그게 없으면
check_contrast.dart 에서 그 쌍들을 뺀 것이 "고치기" 가 아니라 "발견을 지우기" 가 됩니다.
스타일 modifier — 4#
.italic / .underline / .lineThrough / .overline
chain 동작 메커니즘#
체인은 왼쪽부터 순서대로 적용되며, 같은 prop 은 뒤의 chain 이 override 합니다 (copyWith 시멘틱):
// 1. bodyLarge typography 적용
// 2. .semiBold 가 fontWeight override (typography 의 base weight 위에)
// 3. 최종: text-body-large + font-semibold
Text('hi').bodyLarge.semiBold
// 1. headlineSmall typography 적용
// 2. .italic 가 fontStyle 추가
// 3. .accent 가 color 추가
// 결과: text-headline-small + italic + text-tertiary
Text('hi').headlineSmall.italic.accent
빌드 시 단일 <span> 으로 emit — nested wrapper 없이 Tailwind class 로 조립됩니다.
TextStyle#
Flutter#
Flutter SDK TextStyle 그대로 사용 — 모든 Flutter 표준 prop (fontSize, fontWeight,
color, decoration, shadows, foreground, ...) 사용 가능.
Text('hi', style: theme.typography.bodyMedium.copyWith(
fontWeight: FontWeight.w600,
color: theme.colorScheme.error.toValue(),
))
Web#
Web TextStyle 은 Flutter SDK TextStyle 과 동일한 파라미터 이름 을 사용합니다. 타입만 플랫폼별로 자연스럽게 다릅니다 — 색 · 굵기 · 폰트패밀리는 Tailwind 토큰
String (Flutter 는 Color / FontWeight) 이고, decoration 계열과 fontStyle
은 열거형입니다.
class TextStyle {
final String? color; // 'primary' (Tailwind color token)
final String? backgroundColor; // 'surface-container'
final double? fontSize; // CoreFontSize.s14 등
final String? fontWeight; // 'semibold' (CoreFontWeight.scale.*)
final FontStyle? fontStyle; // FontStyle.italic / .normal (coui_web enum)
final String? fontFamily; // 'sans' / 'mono'
final double? letterSpacing; // em
final double? wordSpacing; // em
final double? height; // line-height multiplier
final CoreTextDecoration? decoration; // .underline / .overline / .lineThrough / .none
final String? decorationColor; // Tailwind color token
final CoreTextDecorationStyle? decorationStyle; // .solid / .double / .dotted / .dashed / .wavy
final double? decorationThickness; // 입력은 px (rem 으로 emit)
final List<Shadow>? shadows; // Shadow(color: 'shadow', offset: (dx:, dy:), blurRadius:)
}
build 시 token 필드는 Tailwind 클래스로 조립되고 (text-${color} · bg-${backgroundColor}
· font-${fontWeight} · font-${fontFamily} · italic / not-italic
· decoration utility · decoration-${decorationColor}), 수치 필드는 inline CSS 로 emit 됩니다 (font-size
/ text-decoration-thickness 는 rem, line-height 는 배수, letter-spacing
/ word-spacing 은 em, shadows 는 text-shadow). 이 패턴은 코드베이스의 다른 컴포넌트들('bg-${cs.primary}'
등 string-token 보간) 과 일관됩니다.
시멘틱 typography 적용은 chain 으로 (Text('hi').bodyMedium) — chain 이 내부적으로 CoreTypography.coui
의 토큰을 읽어 TextStyle 의 fontSize/fontWeight/height/letterSpacing
을 채워줍니다.
사용 가이드라인 (Usage Guidelines)#
✅ Do#
시맨틱 typography / 색상은 chain 으로 적용
Text('제목').headlineSmall.semiBold.onPrimaryContainer
이유: chain 은 build 시점에 CoreTypography / theme.colorScheme 토큰을 resolve 하므로 light/dark 테마 전환에 자동 반응하고, Flutter와 Web이 같은 토큰 이름을 참조해 두 플랫폼 렌더링이 어긋나지 않습니다.
❌ Don't#
style:에 raw 리터럴을 직접 박지 않기
// ❌ 하드코딩된 값 — 테마와 무관하게 고정됨
Text(
'가격',
style: TextStyle(fontSize: 14, color: Color(0xFF111111)),
)
이유: style: 프로퍼티는 다른 디자인 시스템과의 호환을 위해 유지되지만, raw 값은 테마 토큰을 거치지 않아 다크모드 전환에 반응하지 않고 Web 쪽 동일 컴포넌트와 값이 갈릴 위험이 있습니다. CoUI 토큰(CoreFontSize.* / cs.*)을 참조하거나 chain 을 쓰세요.
접근성 (Accessibility)#
- 본문 텍스트: 배경 대비 WCAG AA (4.5:1) 이상
- 보조 텍스트 (
.neutralForeground): 배경 대비 최소 3:1 - light / dark 토글 시 CSS variable (
--coui-on-surface등) 자동 반응 — text 색이 테마에 맞게 자동 전환
크로스 플랫폼 차이점 (Platform Differences)#
핵심: 양쪽 모두 같은 CoUI 토큰 (cs.primary / CoreFontWeight.scale.semibold
등) 을 사용합니다. 단 ColorScheme/Typography 가 반환하는 타입 이 플랫폼 native API 에 맞춰 다릅니다.
| prop | Flutter 반환 타입 | Web 반환 타입 |
|---|---|---|
cs.primary |
Color (dart:ui) — 위젯에 직접 주입 |
String 'primary' — Tailwind 클래스 보간 |
CoreFontWeight.scale.semibold |
(Flutter는 보통 FontWeight.w600 사용) |
String 'semibold' — Tailwind 클래스 보간 |
CoreFontSize.s14 / CoreLineHeight.h160 |
double |
double ✅ (양쪽 동일) |
decoration |
TextDecoration (combinable) |
CoreTextDecoration enum (단일 값 — 조합 불가) |
fontStyle |
Flutter SDK FontStyle enum |
coui_web FontStyle enum (.italic / .normal) |
shadows |
List<Shadow> (Color / Offset) |
List<Shadow> (토큰 String / (dx:, dy:) 레코드) |
파라미터 이름 양쪽 동일 + 토큰 이름 양쪽 동일 — Text('hi', style: TextStyle(color: cs.primary, fontSize: ..., fontWeight: ...))
형태로 사용자 코드가 같은 모양. 다른 컴포넌트들 (Card(cardStyle: CoreCardStyle(backgroundColor: cs.surface))
등) 과 동일한 토큰 패턴.
chain 표현 (Text('hi').bodyMedium.onPrimaryContainer.semiBold) 은 양쪽 100% 동일.