Text#
Text 는 시멘틱 typography 토큰을 적용해 일관된 텍스트 렌더링을 제공하는 컴포넌트입니다. Flutter 와 Web 양쪽이 거의 동일한 사용 패턴을 갖도록 설계되어, 같은 코드 모양으로 양쪽 플랫폼 모두 작성 가능합니다.
⚡ 권장 패턴: chain 사용#
CoUI Text 의 권장 사용법은 chain modifier 입니다. .bodyMedium / .primary
/ .semiBold / .italic 처럼 token 이름을 chain 으로 적용하면 양쪽 플랫폼이 일관된 결과를 보장합니다.
// ✅ 권장: chain only
Text('Hello').bodyMedium.semiBold.primary
Text('Error').bodySmall.error.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').primary
Text('On Primary container').onPrimaryContainer
Text('Secondary').secondary
Text('Tertiary').tertiary
Text('accent (alias)').accent
Text('neutral (alias)').neutral
const Text('Primary').primary
const Text('On Primary container').onPrimaryContainer
const Text('Secondary').secondary
const Text('Tertiary').tertiary
const Text('accent (alias)').accent
const Text('neutral (alias)').neutral
Text('Error').error
Text('Error container').errorContainer
Text('Success').success
Text('Success container').successContainer
Text('Warning').warning
Text('Warning container').warningContainer
Text('Info').info
Text('Info container').infoContainer
const Text('Error').error
const Text('Error container').errorContainer
const Text('Success').success
const Text('Success container').successContainer
const Text('Warning').warning
const Text('Warning container').warningContainer
const Text('Info').info
const Text('Info container').infoContainer
// 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}',
[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,
borderRadius: BorderRadius.circular(CoreRadius.radius4),
border: Border.all(color: cs.outlineVariant!),
),
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.primary
Text('bodySmall + error + underline')
.bodySmall
.error
.underline
Text('s14 + semiBold + primary').s14.semiBold.primary
const Text('bodyLarge + semiBold').bodyLarge.semiBold
const Text('bodyMedium + primary').bodyMedium.primary
const Text('bodySmall + error + underline')
.bodySmall
.error
.underline
const Text('s14 + semiBold + primary').s14.semiBold.primary
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.primary.opacity(0.5)
Text('error @ opacity 0.7').bodyMedium.error.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.primary.opacity(0.5)
const Text('error @ opacity 0.7').bodyMedium.error.opacity(0.7)
Text.rich(TextSpan(
text: 'Mixed: ',
children: [
TextSpan(
text: 'primary ',
style: TextStyle(
color: cs.primary,
fontWeight: CoreFontWeight.scale.semibold,
),
),
const TextSpan(text: '+ '),
TextSpan(
text: 'italic error',
style: TextStyle(
color: cs.error,
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.primary,
fontWeight: FontWeight.w600,
),
),
const TextSpan(text: '+ '),
TextSpan(
text: 'italic error',
style: TextStyle(
color: cs.error,
fontStyle: FontStyle.italic,
),
),
const TextSpan(text: ' + '),
const TextSpan(
text: 'underline',
style: TextStyle(
decoration: TextDecoration.underline,
),
),
],
),
style: ts.bodyMedium,
)
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 시멘틱 typography (display / headline / title / body / label) 가 필요할 때
- 토큰 기반 색상 / 굵기 / 기울임 등 chain 으로 스타일을 표현하고 싶을 때
- light / dark 테마 변경에 자동 반응해야 할 때
대신 다른 컴포넌트를 사용하세요:
Badge: 텍스트를 배지 형태로 강조할 때Input/TextField: 사용자 텍스트 입력이 필요할 때Link: 클릭 가능한 링크 텍스트가 필요할 때
기본 사용법 (Basic Usage)#
// 기본 텍스트 — bodyMedium + onSurface 자동 적용
Text('안녕하세요')
// chain — 시멘틱 typography
Text('헤딩').headlineLarge
Text('본문').bodyMedium
// chain — 색상
Text('강조').primary
Text('에러 메시지').error
// chain — 조합 (뒤가 override)
Text('제목').headlineSmall.semiBold.primary
// 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('강조').primary
Text('에러 메시지').error
// chain — 조합 (뒤가 override)
Text('제목').headlineSmall.semiBold.primary
// TextStyle 직접 — CoUI 토큰 사용 (Flutter 와 같은 파라미터 + 같은 토큰)
Text(
'커스텀',
style: TextStyle(
color: cs.primary,
fontSize: CoreFontSize.s14,
fontWeight: CoreFontWeight.scale.semibold, // .scale.semibold: String 토큰
),
)
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 토큰) — 45 + 7 alias#
모든 theme.colorScheme.<token> chain 으로 노출. build 시점에 resolve 되어 Theme override 자동 반영.
| 카테고리 | chain |
|---|---|
| Surface (8) |
.surface
.surfaceDim
.surfaceBright
.surfaceContainerLowest
.surfaceContainerLow
.surfaceContainer
.surfaceContainerHigh
.surfaceContainerHighest
|
| On surface + tint (3) | .onSurface .onSurfaceVariant .surfaceTint |
| Primary (4) |
.primary
.onPrimary
.primaryContainer
.onPrimaryContainer
|
| Secondary (4) |
.secondary
.onSecondary
.secondaryContainer
.onSecondaryContainer
|
| Tertiary (4) |
.tertiary
.onTertiary
.tertiaryContainer
.onTertiaryContainer
|
| Error (4) |
.error
.onError
.errorContainer
.onErrorContainer
|
| Success (4) |
.success
.onSuccess
.successContainer
.onSuccessContainer
|
| Warning (4) |
.warning
.onWarning
.warningContainer
.onWarningContainer
|
| Info (4) | .info .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
)
|
스타일 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: Colors.red,
))
Web#
Web TextStyle 은 Flutter SDK TextStyle 과 동일한 파라미터 이름 을 사용합니다. 타입만 플랫폼별로 자연스럽게 다릅니다 (예: Flutter
Color ↔ Web String Tailwind 토큰).
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 String? fontStyle; // 'italic' / 'normal'
final String? fontFamily; // 'sans' / 'mono'
final double? letterSpacing; // em
final double? height; // line-height multiplier
final String? decoration; // 'underline' / 'line-through'
final String? decorationColor;
}
build 시 token 필드는 Tailwind 클래스 (text-${color}, font-${fontWeight} 등) 로 조립되고, raw 값 (fontSize
/ height / letterSpacing) 은 inline CSS 로 emit 됩니다. 이 패턴은 코드베이스의 다른 컴포넌트들('bg-${cs.primary}'
등 string-token 보간) 과 일관됩니다.
시멘틱 typography 적용은 chain 으로 (Text('hi').bodyMedium) — chain 이 내부적으로 CoreTypography.coui
의 토큰을 읽어 TextStyle 의 fontSize/fontWeight/height/letterSpacing
을 채워줍니다.
양쪽 플랫폼 차이 (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) |
String Tailwind 단일 utility |
fontStyle |
FontStyle enum |
String ('italic' / 'normal') |
파라미터 이름 양쪽 동일 + 토큰 이름 양쪽 동일 — Text('hi', style: TextStyle(color: cs.primary, fontSize: ..., fontWeight: ...))
형태로 사용자 코드가 같은 모양. 다른 컴포넌트들 (CoCard(cardStyle: CoreCardStyle(backgroundColor: cs.surface))
등) 과 동일한 토큰 패턴.
chain 표현 (Text('hi').bodyMedium.primary.semiBold) 은 양쪽 100% 동일.
접근성 (Accessibility)#
- 본문 텍스트: 배경 대비 WCAG AA (4.5:1) 이상
- 보조 텍스트 (
.neutralForeground): 배경 대비 최소 3:1 - light / dark 토글 시 CSS variable (
--coui-on-surface등) 자동 반응 — text 색이 테마에 맞게 자동 전환