Badge
배지 컴포넌트
목차
상태 · 개수 · 분류를 짧게 표시하는 배지 컴포넌트입니다. 세 축 — 무슨 뜻의 색인지(tone), 얼마나 센지(variant), 무엇을 담는지(form) — 을 따로 고릅니다. 아무 값도 주지 않은
Badge 는 분류 태그입니다(neutral · tonal · label · md).
레이블 배지는 부모가 제공하는 최대 너비 안에서 긴 문구를 줄바꿈합니다. 아이콘과 상태 점의 크기는 유지하며, 문구를 자동으로 생략하지 않습니다. 너비 제약이 없는 배지는 내용 크기를 따릅니다.
count와 dot 폼의 기존 크기 동작은 유지됩니다.
기본 사용법 (Basic Usage)#
// 분류 태그 — 모든 기본값 (neutral · tonal · label · md)
Badge(child: Text('디자인'))
// 상태 라벨 — 톤과 앞 점
Badge(tone: CoreBadgeTone.success, showDot: true, child: Text('완료'))
// 개수 — 채운 숫자 알약
Badge(
tone: CoreBadgeTone.info,
variant: CoreBadgeVariant.filled,
form: CoreBadgeForm.count,
size: CoreComponentSize.xs,
child: Text('3'),
)
// 점 — 내용 없이 톤만
Badge(tone: CoreBadgeTone.destructive, form: CoreBadgeForm.dot)
// 분류 태그 — 모든 기본값 (neutral · tonal · label · md)
Badge(child: Text('디자인'))
// 상태 라벨 — 톤과 앞 점
Badge(tone: CoreBadgeTone.success, showDot: true, child: Text('완료'))
// 개수 — 채운 숫자 알약
Badge(
tone: CoreBadgeTone.info,
variant: CoreBadgeVariant.filled,
form: CoreBadgeForm.count,
size: CoreComponentSize.xs,
child: Text('3'),
)
// 점 — 내용 없이 톤만
Badge(tone: CoreBadgeTone.destructive, form: CoreBadgeForm.dot)
Live Preview#
코드 보기
class BadgeDefaultExample extends StatelessComponent {
const BadgeDefaultExample({super.key});
@override
Component build(BuildContext context) {
// Every default — a neutral tone, tonal strength, a word, md.
return Badge(child: Text('Label'));
}
}
class BadgeDefaultExample extends StatelessWidget {
const BadgeDefaultExample({super.key});
@override
Widget build(BuildContext context) {
// Every default — a neutral tone, tonal strength, a word, md.
return const Badge(child: Text('Label'));
}
}
긴 레이블과 좁은 너비#
Flutter와 Web 모두 label 형태에서 부모가 너비를 제한하면 긴 레이블이 줄바꿈되고 배지 높이가 늘어납니다. count 형태는 한 줄을 유지하며 제한된 너비에 들어가지 않으면 말줄임으로 표시합니다. 전체 텍스트는 접근성 정보에 남습니다. 짧은 레이블의 기본 크기와 스타일은 유지됩니다. 상태 점과 앞뒤 아이콘은 크기를 유지하고, 남은 너비를 레이블이 사용합니다.
Flutter의 Row처럼 자식에게 무제한 너비를 주는 부모에서는 Flexible(child: Badge(...)) 또는 SizedBox(width: ..., child: Badge(...))로 배지에 가용 너비를 전달하세요. Web에서는 너비가 제한된 컨테이너 안에 배치하면 됩니다.
child에 명시한 줄 수 제한이나 말줄임 설정은 그대로 적용됩니다. 아이콘과 패딩 자체보다 좁은 너비까지 배지를 압축하는 것은 지원하지 않습니다.
줄바꿈은 번역이나 글자 확대 시의 넘침을 방지하기 위한 동작입니다. 배지는 짧은 상태·분류 문구에 사용하세요. 매우 좁은 너비에 점과 여러 아이콘을 함께 넣으면 글자가 한두 개씩 줄바꿈되어 읽기 어려울 수 있습니다. 실제 화면의 가용 너비와 큰 글자 설정에서 확인하고, 긴 설명은 배지 밖의 본문으로 배치하세요.
구조 (Anatomy)#
코드가 정본입니다. 부분 이름은 위젯 슬롯과 같고, Figma ❖ Badge
레이어와도 맞습니다.
| 부분 | 역할 | 슬롯 |
|---|---|---|
leading dot |
글 앞의 상태 점. 글자색으로 칠합니다(outlined 상태 톤은 선 색) |
showDot |
leading | 앞쪽 | leading |
child | 레이블 (낱말 또는 숫자) | child |
trailing | 뒤쪽 | trailing |
form: CoreBadgeForm.dot 은 이 부분을 하나도 그리지 않고 원 하나만 그립니다.
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 항목의 상태를 라벨로 표시할 때 — 진행 중, 완료, 오류 (
tone+showDot) - 읽지 않은 개수 같은 숫자를 붙일 때 (
form: CoreBadgeForm.count) - 카테고리나 태그를 간결하게 라벨링할 때 — 누를 수 없는 분류 표시는 Chip 이 아니라 Badge 입니다
- 내용 없이 "여기에 무언가 있다" 만 알릴 때 (
form: CoreBadgeForm.dot)
대신 다른 컴포넌트를 사용하세요:
- Status: 사람이나 기기의 접속 상태(online · away · busy · offline)를 표시할 때
- ValidationBadge: 일반 상태 라벨 대신 검증 규칙의 valid / invalid / pending 상태가 필요할 때
- Chip: 배지 표시·단일 클릭 외에 선택 상태나 제거 동작이 필요한 태그일 때
- Alert: 라벨·개수 대신 제목 · 설명 · 복구 액션을 가진 지속적인 인라인 메시지가 필요할 때
- Toast: 상태 라벨을 붙이는 대신 잠시 나타나는 오버레이 피드백이 필요할 때
Props / Parameters#
W 는 Flutter의 Widget, Web의 Jaspr Component 입니다. W?
는 선택 슬롯입니다.
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
tone |
CoreBadgeTone |
neutral |
색이 뜻하는 것 (neutral, primary, info, success, warning, destructive) |
variant |
CoreBadgeVariant |
tonal |
세기 — 채움(filled) · 옅은 채움(tonal) · 선(outlined) |
child |
W? |
null |
레이블. label 은 낱말, count 는 숫자. 점 꼴은 이 슬롯을 그리지 않음 |
form |
CoreBadgeForm |
label |
담는 것 — 낱말(label, 둥근 사각) · 숫자(count, 알약) · 없음(dot, 원) |
size |
CoreComponentSize |
md |
배지 크기 (xs, sm, md, lg, xl, xl2) |
showDot |
bool |
false |
글 앞에 글자색 상태 점을 그림. label · count 꼴만 |
leading | W? | null | 앞쪽 아이콘 |
trailing | W? | null | 뒤쪽 아이콘 |
onPressed |
VoidCallback? (Flutter) / CoreVoidCallback? (Web) |
null |
클릭 콜백 (설정 시 인터랙티브) |
badgeStyle |
CoreBadgeStyle? |
null |
인스턴스 스타일 (Style 시스템 참조) |
테두리 없는 마케팅 크롬#
배지 반경은 항상 inline 입니다. 테두리 굵기는 선 색이 정해질 때 — 기본적으로 outlined 이거나 호출자가 borderColor
를 줄 때 — inline 으로 나갑니다. 제품 CSS 로 그 값을 덮으면 !important 가 필요하고, 슬롯은 그 표면을 직접 바꿉니다.
Badge(
badgeStyle: const CoreBadgeStyle(
borderWidth: CoreStrokeWidth.stroke0,
borderRadius: CoreBorderRadius.all(CoreRadius.radius8),
backgroundColor: CoreColor.token(CoreColors.surfaceContainer),
),
child: const Text('베타'),
)
스타일 시스템 (Style System)#
Badge 의 모든 chrome / dimensional / nested-slot 오버라이드는 CoreBadgeStyle 단일 슬롯으로 흐릅니다. 디자인 축 (tone,
variant, form, size, showDot) 과 behaviour (child
/ leading / trailing / onPressed) 는 위젯 파라미터로 직접 전달합니다.
시맨틱 vs 스타일#
-
디자인 축 / behaviour: 위젯/컴포넌트 파라미터로 직접 (
tone,variant,form,size,showDot,child,leading,trailing,onPressed) -
chrome / dimensional / 슬롯 스타일:
CoreBadgeStyle한 곳으로 (backgroundColor/foregroundColor/borderColor/borderWidth/borderRadius/padding/minHeight/iconLabelGapStyle/transitionDuration/labelStyle/iconStyle/dotSize/leadingDotSize/focusOutlineStyle/stateLayer)
Resolve chain#
defaultsBySize[size] → defaultsByForm[form] → defaultsByTone[tone] × variant
→ CoreBadgeTheme.style // 프로젝트 공통
→ CoreBadgeTheme.toneStyles[tone]
→ CoreBadgeTheme.variantStyles[variant]
→ parent component slot override
→ widget.badgeStyle // 인스턴스별
각 nested 슬롯 스타일 (labelStyle / iconStyle / iconLabelGapStyle /
focusOutlineStyle) 은 자기 컴포넌트의 자체 resolve chain 으로 다시 한 번 머지됩니다. 크기 표의 글자 역할 위에 꼴의 굵기(count
의 600)가 머지되므로, labelStyle 로 역할을 바꿔도 숫자의 굵기는 꼴의 것이 남습니다.
CoreBadgeStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
backgroundColor |
CoreColor? |
Fill colour override. For [CoreBadgeForm.dot] this is the dot's colour.
null
defers to the tone row's colour for the variant ([CoreBadgeToneStyle.restingStyleFor]).
|
foregroundColor |
CoreColor? |
Foreground (label / icon / leading dot) colour override.
null
defers to the tone row's colour for the variant.
|
borderColor |
CoreColor? |
Border stroke colour.
null
defers to the tone row, which states one only for [CoreBadgeVariant.outlined]; with no colour no border is drawn.
|
borderWidth |
double? |
Border stroke width (logical px). null defers to [defaultBorderWidth]. |
borderRadius |
CoreBorderRadius? |
Border radius.
null
defers to the form's corner — the step's in [defaultsBySize] for a label, the pill in [defaultsByForm] for a number and a dot.
|
padding |
CoreEdgeInsets? |
Padding applied to the content row.
null
defers to
CoreBadgeStyle.defaultsBySize[size]!.padding!
. Horizontal only by default — the height is [minHeight].
|
minHeight |
double? |
Height of the label and count forms (logical px); the content is centred in it. A count is never narrower than this, so a single digit stays a circle.
null
defers to
CoreBadgeStyle.defaultsBySize[size]!.minHeight!
. Unused by [CoreBadgeForm.dot], which is [dotSize] across.
|
iconLabelGapStyle |
CoreGapStyle? |
Nested [CoreGapStyle] slot for the leading-dot / leading / label / trailing spacers.
null
defers to
CoreBadgeStyle.defaultsBySize[size]!.iconLabelGapStyle!
.
|
transitionDuration |
Duration? |
Chrome transition duration override. null defers to [defaultTransitionDuration]. |
labelStyle |
CoreTextStyle? |
Label text style override.
null
defers to the step's role in [defaultsBySize] with the form's weight from [defaultsByForm] laid over it.
|
iconStyle |
CoreIconStyle? |
Leading / trailing icon style override (applied uniformly to both slots).
size
defers to
CoreBadgeStyle.defaultsBySize[size]!.iconStyle!.size!
.
|
dotSize |
double? |
Diameter of the dot in [CoreBadgeForm.dot] (logical px).
null
defers to
defaultsBySize[size]!.dotSize!
. Unused by the label and count forms, whose box is [minHeight] tall.
|
leadingDotSize |
double? |
Diameter of the status dot drawn before the content when the badge's
showDot
is on (logical px).
null
defers to
defaultsBySize[size]!.leadingDotSize!
.
|
leadingDotColor |
CoreColor? |
Colour of the status dot drawn before the content when
showDot
is on.
null
takes the badge's [foregroundColor], so the dot reads as part of the label; the
outlined
strength of a status tone states it (with the icon's colour) as the tone's line colour — see [CoreBadgeToneStyle.outlinedMarkColor].
|
focusOutlineStyle |
CoreFocusOutlineStyle? |
Focus-ring style override. Nested [CoreFocusOutlineStyle] slot —
null
defers to [defaultFocusOutlineStyle]. Only consumed when the badge is interactive (
onPressed != null
).
|
stateLayer |
CoreStateLayer? |
Hover / pressed wash weights over the fill, in the label's colour.
null
defers to [defaultStateLayer]. Only consumed when the badge is interactive (
onPressed != null
); the focus rung is unused.
|
CoreBadgeStyle 톤별 기본값 (CoreBadgeToneStyle)#
| 필드 | neutral |
primary |
info |
success |
warning |
destructive |
|---|---|---|---|---|---|---|
filledBackgroundColor |
surfaceContainerHighest | primary | info | success | warning | error |
filledForegroundColor |
onSurfaceVariant | onPrimary | onInfo | onSuccess | onWarning | onError |
tonalBackgroundColor |
surfaceContainerHigh | primaryContainer | infoContainer | successContainer | warningContainer | errorContainer |
tonalForegroundColor |
onSurfaceVariant | onPrimaryContainer | onInfoContainer | onSuccessContainer | onWarningContainer | onErrorContainer |
outlinedBorderColor |
outline | primary | info | success | warning | error |
outlinedForegroundColor |
onSurface | primaryText | onInfoContainer | onSuccessContainer | onWarningContainer | onErrorContainer |
dotColor |
outline | primary | info | success | warning | error |
outlinedMarkColor |
— | — | info | success | warning | error |
사용 예 (Flutter)#
Badge(
tone: CoreBadgeTone.primary,
badgeStyle: CoreBadgeStyle(
padding: CoreEdgeInsets.symmetric(horizontal: CoreSpace.space16),
borderRadius: CoreBorderRadius.all(CoreRadius.radius8),
labelStyle: CoreTextStyle.token(CoreTextStyles.labelLarge),
iconStyle: CoreIconStyle(size: CoreIconSize.size16),
),
leading: Icon(LucideIcons.star),
child: Text('Featured'),
)
사용 예 (Web)#
Badge(
tone: CoreBadgeTone.primary,
badgeStyle: CoreBadgeStyle(
padding: CoreEdgeInsets.symmetric(horizontal: CoreSpace.space16),
borderRadius: CoreBorderRadius.all(CoreRadius.radius8),
),
leading: Icon(LucideIcons.star),
child: Text('Featured'),
)
테마로 톤 · 세기 덮어쓰기#
CoreBadgeTheme(
// 모든 success 배지
toneStyles: {
CoreBadgeTone.success: CoreBadgeStyle(
foregroundColor: CoreColor.token(CoreColors.onSuccessContainer),
),
},
// 모든 outlined 배지
variantStyles: {
CoreBadgeVariant.outlined: CoreBadgeStyle(borderWidth: CoreStrokeWidth.stroke2),
},
)
같은 필드를 두 곳이 덮으면 variantStyles 가 toneStyles 를 이깁니다(위 resolve chain 순서).
빠른 오버라이드 (Chain)#
이미 만든 Badge 인스턴스에 badgeStyle을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 위의
badgeStyle: CoreBadgeStyle(...) 생성자 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다. .radius4처럼 Core 토큰 상수 이름과 똑같은 이름의 getter도 있습니다 —
withStyle을 한 번 더 줄인 sugar로, 이름이 곧 값이라(radius4 == CoreRadius.radius4) 어느 컴포넌트에서 써도 뜻이 갈리지 않습니다. 색 getter(.primary
등)는 면(backgroundColor)만 칠하고 글자색은 그대로 두므로, 톤을 바꾸려면 getter 대신 tone 을 씁니다.
class BadgeChainExample extends StatelessWidget {
const BadgeChainExample({super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
Badge(
tone: CoreBadgeTone.primary,
variant: CoreBadgeVariant.filled,
child: const Text('Primary'),
).radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
Badge(
child: const Text('Full control'),
).withStyle(
const CoreBadgeStyle(
backgroundColor: CoreColor.token(CoreColors.tertiaryContainer),
foregroundColor: CoreColor.token(CoreColors.onTertiaryContainer),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
padding: CoreEdgeInsets.symmetric(horizontal: CoreSpace.space12),
),
),
],
);
}
}
class BadgeChainExample extends StatelessComponent {
const BadgeChainExample({super.key});
@override
Component build(BuildContext context) {
return div(
[
// 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
Badge(
tone: CoreBadgeTone.primary,
variant: CoreBadgeVariant.filled,
child: Text('Primary'),
).radius16.primary,
const Gap.space12(),
// withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
Badge(
child: Text('Full control'),
).withStyle(
const CoreBadgeStyle(
backgroundColor: CoreColor.token(CoreColors.tertiaryContainer),
foregroundColor: CoreColor.token(CoreColors.onTertiaryContainer),
borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
padding: CoreEdgeInsets.symmetric(horizontal: CoreSpace.space12),
),
),
],
classes: 'flex flex-col items-start',
);
}
}
변형 (Variants)#
세 축#
| 축 | 값 (굵게 = 기본) | 답하는 질문 |
|---|---|---|
tone |
neutral
·
primary
·
info
·
success
·
warning
·
destructive
|
무슨 뜻의 색인가 |
variant |
filled · tonal · outlined |
얼마나 센가 |
form |
label · count · dot |
무엇을 담나 — 낱말 · 숫자 · 없음 |
세 축은 서로 독립입니다. 성공 배지는 채움 · 옅은 채움 · 선 어느 세기로도 쓸 수 있고, 점도 여전히 톤을 가집니다 — 뜻과 세기를 한 축에 섞으면 "옅은 빨간 라벨" 이나 "빨간 점" 을 말할 수 없습니다.
Badge(child: Text('Neutral'))
Badge(tone: CoreBadgeTone.primary, child: Text('Primary'))
Badge(tone: CoreBadgeTone.info, child: Text('Info'))
Badge(tone: CoreBadgeTone.success, child: Text('Success'))
Badge(tone: CoreBadgeTone.warning, child: Text('Warning'))
Badge(tone: CoreBadgeTone.destructive, child: Text('Destructive'))
Badge(tone: CoreBadgeTone.primary, variant: CoreBadgeVariant.filled, child: Text('Filled'))
Badge(tone: CoreBadgeTone.primary, variant: CoreBadgeVariant.tonal, child: Text('Tonal'))
Badge(tone: CoreBadgeTone.primary, variant: CoreBadgeVariant.outlined, child: Text('Outlined'))
톤 × 세기 색#
톤마다 세 세기와 점 꼴의 색 일곱 개가 CoreBadgeStyle.defaultsByTone 의 CoreBadgeToneStyle 한 행에 있습니다. 값은 위 스타일 시스템의 "CoreBadgeStyle
톤별 기본값" 표가 그 행을 그대로 옮긴 것입니다 — filled* · tonal* 은 면과 글자, outlined*
는 선과 글자, dotColor 는 점 꼴의 색입니다.
-
상태 톤 넷(
info·success·warning·destructive)과primary는 그 톤의 시맨틱 슬롯을 읽습니다 —filled는 톤 색 위의on색,tonal은 컨테이너 위의 컨테이너 글자색입니다.destructive의 슬롯 이름은error입니다. -
neutral에는 상태 슬롯이 없어 표면 단계를 읽습니다.filled가 가장 높은 단(surfaceContainerHighest),tonal이 한 단 아래(surfaceContainerHigh)이고 글자는 둘 다onSurfaceVariant입니다. -
outlined의 면은 표에 없습니다 — 모든 톤이CoreBadgeStyle.defaultOutlinedBackgroundColor(surface0%, 투명)를 쓰고, 선은 1px(stroke1)입니다. 테두리는 선 색이 있을 때만 그려지므로 기본적으로outlined에만 있습니다. -
앞 점(
showDot)과 아이콘은 글자색을 따릅니다. 그래서tonal의 앞 점은onXContainer입니다. 예외는 상태 톤 넷의outlined로, 앞 점과 아이콘이 선과 같은 톤 색(outlinedMarkColor)이고 글자는 컨테이너 글자색 그대로입니다 — 글자는 어느 바탕에서도 4.5 : 1 을 지켜야 하는데 톤 색은 회색 바탕에서 그 선에 못 미칩니다. - 점 꼴은 세기와 무관하게 그 톤의
dotColor하나로 칠합니다 — 지켜야 할 글자가 없으니 세 세기가 다를 까닭이 없습니다.
꼴 — 낱말 · 숫자 · 점#
모양이 내용을 따릅니다.
Badge(child: Text('신규')) // label — 기본값
Badge(form: CoreBadgeForm.count, child: Text('12')) // count
Badge(tone: CoreBadgeTone.destructive, form: CoreBadgeForm.dot) // dot
-
label— 그 단계의 높이에 고정된 둥근 사각입니다. 모서리는 높이의 1/4 을 반경 사다리로 내린 값이고(아래 크기 표), 글자는 역할의 500 입니다. -
count— 같은 높이의 알약(9999)입니다. 폭이 높이보다 좁아지지 않아 한 자리 숫자도 원으로 남고, 숫자는 600 입니다. -
dot—dotSize지름의 원입니다.child·leading·trailing· 앞 점을 그리지 않고 패딩도 쓰지 않습니다. 빈 콘텐츠 행이라도 패딩을 참조하면 박스가 단계가 아니라 패딩 크기가 되는데, 그것이 이 꼴이 막으려는 바로 그 일입니다.
크기#
세로 패딩은 없습니다. 높이를 고정하고 내용을 가운데에 둡니다.
size |
높이 (minHeight) |
글자 역할 | 가로 패딩 | 간격 | label 반경 |
아이콘 | 점 꼴 지름 (dotSize) |
앞 점 지름 (leadingDotSize) |
|---|---|---|---|---|---|---|---|---|
xs |
18 | labelXs |
6 | 4 | 4 | 10 | 4 | 4 |
sm |
20 | labelXs |
6 | 4 | 4 | 12 | 6 | 4 |
md |
24 | labelSmall |
8 | 4 | 6 | 14 | 8 | 6 |
lg |
28 | labelMedium |
10 | 4 | 6 | 16 | 12 | 6 |
xl |
32 | labelLarge |
12 | 6 | 8 | 16 | 16 | 6 |
xl2 |
36 | labelXl |
12 | 6 | 8 | 18 | 16 | 8 |
굵기는 낱말(label)이 역할의 500, 숫자(count)가 600 입니다.
상태 라벨 — 앞 점#
Badge(tone: CoreBadgeTone.info, showDot: true, child: Text('검토 중'))
Badge(tone: CoreBadgeTone.success, showDot: true, child: Text('승인'))
Badge(tone: CoreBadgeTone.destructive, showDot: true, child: Text('반려'))
앞 점은 leading 보다 앞에, 같은 iconLabelGapStyle 간격을 두고 그려집니다.
Leading / Trailing 아이콘#
Badge(
tone: CoreBadgeTone.primary,
leading: Icon(LucideIcons.star),
child: Text('Featured'),
)
Badge(
trailing: Icon(LucideIcons.arrowRight),
child: Text('Next'),
)
이전 API 에서 옮기기 (Migration)#
옛 CoreBadgeVariant 값 넷(primary · secondary · outline
· destructive)은 색과 세기를 한 축에 섞었습니다. 이제 둘은 tone 과 variant 로 나뉘고, 옛 값은 남아 있지 않습니다. 옛 모양(알약)을 그대로 두려면 아래처럼 옮깁니다.
| 옛 | 새 |
|---|---|
variant: CoreBadgeVariant.primary |
tone: CoreBadgeTone.primary, variant: CoreBadgeVariant.filled, form: CoreBadgeForm.count
|
variant: CoreBadgeVariant.secondary |
tone: CoreBadgeTone.neutral, variant: CoreBadgeVariant.filled, form: CoreBadgeForm.count
|
variant: CoreBadgeVariant.outline |
tone: CoreBadgeTone.neutral, variant: CoreBadgeVariant.outlined, form: CoreBadgeForm.count
|
variant: CoreBadgeVariant.destructive |
tone: CoreBadgeTone.destructive, variant: CoreBadgeVariant.filled, form: CoreBadgeForm.count
|
인자 없는 Badge(child: …) |
이제
neutral
·
tonal
·
label
·
md
로 그립니다 (옛 기본은 primary 채운 알약 ·
sm
)
|
-
낱말을 담은 배지라면
form을 생략해label꼴(둥근 사각)로 두는 것이 새 기본 의도입니다. 위 표의form: count는 옛 알약 모양을 그대로 두는 길입니다. -
primary·outline·destructive는 옛 색 슬롯을 그대로 읽습니다.secondary만 색이 바뀝니다 — 옛 값은secondary·onSecondary슬롯(CoUI 팔레트에서 라이트는 짙은 회색 면에 흰 글자, 다크는 밝은 회색 면에 검은 글자)을 칠했고,neutral의filled는 표면 단계(surfaceContainerHighest)에onSurfaceVariant글자를 칠합니다. CoUI 팔레트에서는 두 모드 모두 면의 명암이 뒤집힙니다. -
기본 크기가
sm에서md로, 기본 꼴이count에서label로 바뀌었습니다. -
CoreBadgeVariantStyle과CoreBadgeStyle.defaultsByVariant는 없습니다. 색은CoreBadgeStyle.defaultsByTone(CoreBadgeToneStyle)에 있습니다. -
CoreBadgeTheme.variantStyles는 새CoreBadgeVariant로 키를 잡고, 톤별 덮어쓰기는 새CoreBadgeTheme.toneStyles로 합니다. - 인터랙티브 배지의 hover 는 채움을 80% 로 옅게 하던 방식 대신 글자색 상태층(
stateLayer)을 덮습니다.
동작 스펙 (Behavior)#
표시#
- 배지는 정적 인디케이터로, 기본적으로 인터랙션 없음
onPressed가 있으면 클릭 가능한 배지 (버튼 동작)- Flutter: 통합
Clickable이 hover / focus / 커서 / 키보드 활성화를 담당 -
Web:
role="button"+tabindex="0"속성을 달고,click과 포커스된 배지의Enter/Space를 받습니다 -
label·count꼴은 단계의 높이(minHeight)를 바닥으로 두고 내용을 가로 · 세로 가운데에 둡니다.count는 폭도 그 높이보다 좁아지지 않습니다. -
form: dot은child/leading/trailing/ 앞 점을 그리지 않고dotSize지름의 원만 표시합니다. - 선택 상태 · 자체 제거 · 비활성 플래그는 없습니다.
onPressed: null이면 표시 전용입니다.
상태#
누를 수 있는 배지(onPressed != null)만 상태를 그립니다. 표시 전용 배지는 어떤 상태도 칠하지 않습니다.
| 상태 | 표시 |
|---|---|
| hover | 글자색 8% 상태층을 면 위에 덮음 (CoreStateLayer.standard) |
| pressed | 글자색 16% 상태층 |
| 키보드 포커스 | 포커스 링 (focusOutlineStyle). 상태층은 덮지 않음 |
토큰 기반 스타일링#
Badge는 coui_core의 CoreBadgeStyle 기본값 표를 사용하여 크로스 플랫폼 일관된 스타일링을 제공합니다.
| 토큰 | 값 | 설명 |
|---|---|---|
CoreBadgeStyle.defaultsBySize[size] |
size별
minHeight
· 가로
padding
·
iconLabelGapStyle
·
label
꼴
borderRadius
·
labelStyle
역할 ·
iconStyle
·
dotSize
·
leadingDotSize
|
위 크기 표 |
CoreBadgeStyle.defaultsByForm[form] |
label 그대로 · count 600 + 알약 · dot 알약 |
꼴이 크기 행 위에 얹는 것 |
CoreBadgeStyle.defaultsByTone[tone] |
CoreBadgeToneStyle — 세기 셋의 면 · 글자 · 선과 점 색 |
위 톤 × 세기 표 |
CoreBadgeStyle.defaultBorderRadius |
CoreRadius.radius9999 |
count · dot 꼴의 알약 |
CoreBadgeStyle.defaultBorderWidth |
CoreStrokeWidth.stroke1 (1px) |
선 색이 있을 때의 테두리 |
CoreBadgeStyle.defaultOutlinedBackgroundColor |
surface 0% |
outlined 의 투명한 면 |
CoreBadgeStyle.defaultStateLayer |
CoreStateLayer.standard (8 / 8 / 16) |
인터랙티브 배지의 hover · pressed 상태층 |
CoreBadgeStyle.defaultTransitionDuration |
CoreDuration.fast (150ms) |
인터랙티브 상태 트랜지션 |
CoreBadgeStyle.defaultFocusOutlineStyle |
CoreFocusOutlineStyle |
인터랙티브 배지 포커스 링 |
사용 가이드라인 (Usage Guidelines)#
Do#
뜻은 tone 으로 고르세요.
Badge(tone: CoreBadgeTone.success, showDot: true, child: Text('완료'))
Badge(tone: CoreBadgeTone.destructive, showDot: true, child: Text('오류'))
Badge(tone: CoreBadgeTone.warning, showDot: true, child: Text('대기'))
톤 하나가 면 · 글자 · 선 · 점 · 상태층 색을 함께 정합니다.
Don't#
badgeStyle 로 색을 칠해 뜻을 만들지 마세요.
Badge(
badgeStyle: CoreBadgeStyle(
backgroundColor: CoreColor.token(CoreColors.errorContainer),
),
child: Text('오류'),
)
면만 바뀌고 글자 · 앞 점 · 아이콘 색은 neutral 톤에 남아 대비가 보장되지 않습니다.
Do#
배지 텍스트는 짧게, 숫자는 count 로.
Badge(child: Text('신규'))
Badge(form: CoreBadgeForm.count, child: Text('12'))
한두 단어나 숫자로 간결하게 표현합니다.
Don't#
배지에 긴 텍스트를 넣지 마세요.
Badge(child: Text('새로 추가된 기능입니다'))
배지는 높이가 고정된 한 줄 라벨입니다. 문장이 필요하면 Alert를 씁니다.
접근성 (Accessibility)#
역할#
-
Flutter —
onPressed가 있을 때만Clickable이Semantics(button: true, enabled: …)를 답니다. 표시 전용 배지에는Semantics가 없습니다. -
Web —
onPressed가 있으면<span>루트에role="button"과tabindex="0"이 직접 붙습니다(Clickable을 거치지 않습니다). 표시 전용 배지는 role 없는<span>입니다.
키보드#
| 키 | Flutter | Web |
|---|---|---|
Tab (인터랙티브 배지) | 하나의 tab stop | 하나의 tab stop |
Enter / Space |
onPressed 실행 |
onPressed 실행 |
루트가 <span> 이라 브라우저는 활성화를 합성해 주지 않습니다. 그래서 Web 배지는
자기 keydown 으로 Enter 와 Space 를 받아
onPressed 를 부르고(Space 의
페이지 스크롤은 막습니다), 키가 눌린 동안 pressed 상태층을 그립니다 — Flutter 의
Clickable 이 같은 키에 누름을 보여 주는 것과 같습니다. 호출자가 eventHandlers
로 건 click · 키 핸들러는 배지의 처리보다 먼저 돌고, 대체되지 않습니다.
스크린 리더#
이름은 child 로 넘긴 텍스트에서 나옵니다 — 라벨 파라미터가 따로 없습니다.
아이콘만 넣은 배지와 점 꼴은 이름이 비어, 인터랙티브라면 "버튼"으로만 읽힙니다.
앞 점(showDot)은 장식입니다 — 옆의 레이블이 뜻을 담으므로 이름에 들어가지 않습니다(Web aria-hidden).
상태를 색과 점으로만 말하지 말고 레이블 낱말이 그 상태를 말하게 합니다.
상태가 바뀐 사실을 알리는 live region 은 양쪽 다 없습니다.
포커스 관리#
- 진입 / 이탈 / 트랩 — 없음. 포커스는 배지 밖으로 그대로 빠져나갑니다.
-
포커스 링은 키보드로 도달했을 때만 보입니다. Flutter 는
Clickable의 포커스 하이라이트를, Web 은:focus-visible을 따르므로 마우스로 누른 배지에는 양쪽 다 링이 없습니다. 링은 배지 모양(둥근 사각 · 알약 · 원)을 따르고, 링과 배지 사이 2px 간격은 투명입니다.
알려진 제약#
-
Web
Badge는attributes를 받습니다. 아이콘·점 배지의 이름은aria-label로 제공할 수 있습니다. Flutter 는Semantics(label:)래퍼를 사용합니다. -
인터랙티브 배지는 작은 시각 크기와 히트 영역을 분리합니다. Flutter는
TouchTarget, Web은coui-touch-target마커로 hit 영역을 확장합니다. 표시용 배지에는 확장을 적용하지 않습니다.
색상 대비#
톤 × 세기의 채움과 글자는 전부 시맨틱 색 쌍이고, 대비 가드(check_contrast.dart)가 defaultsByTone 을 직접 읽어 18짝(6 톤 × 3 세기)을 라이트·다크·고대비 네 모드에서 감사합니다. 채움이 없는
outlined 의 글자는 배지가 놓이는 바탕(surface · surfaceContainerLow · surfaceContainer
· surfaceBright) 위에서, 선과 점 꼴은 같은 바탕 위에서 비텍스트 기준(3:1)으로 잽니다. 감사는 CoUI 팔레트와 브랜드 표준 매핑(CoreSemanticMappingSet.standard) 둘 다에서 돕니다.
알려진 결손이 둘 있습니다.
-
표준 매핑의 라이트
warning선 · 점. 표준 매핑의 라이트warning은 밝은 채움이라,outlined의 선과 점 꼴이 네 바탕 위에서 3:1 에 못 미칩니다(1.48–1.82:1). CoUI 팔레트의warning은 통과합니다(가장 낮은 값 4.30:1). 표준 매핑의warning을 어둡게 할지, 상태 표시용 선 · 점 슬롯을 따로 둘지는 아직 정하지 않았습니다. -
hover · pressed 상태층. 상태층은 이 감사 밖입니다. 글자색을 면 위에 덮으므로 쉬는 상태보다 면과 글자 사이 대비가 낮아지고, 기본 상태층(
CoreStateLayer.standard)에서는 라이트의filledinfo · success · warning · destructive 와 다크의filledneutral 이 hover · pressed 동안, 다크의tonalneutral 이 pressed 동안 글자 대비 4.5:1 아래로 내려갑니다(가장 낮은 값은 pressed 에서 3.5:1 근처). 더 옅은 상태층이 필요하면stateLayer로 바꿉니다.
크로스 플랫폼 차이점 (Platform Differences)#
tone / variant / form / size / showDot
/ child / leading / trailing / onPressed / badgeStyle
은 양 플랫폼 동일 API 입니다.
| 항목 | Flutter | Web |
|---|---|---|
| 슬롯 타입 | Widget / Widget? |
Component / Component? |
| 인터랙션 | 통합 Clickable |
click 이벤트 + role="button" |
| 키보드 활성화 | Clickable 의 Enter / Space 단축키 |
루트의 keydown 에서 Enter / Space |
| hover · pressed 상태층 | resolver 가 면 위에 섞은 색을 미리 구워 위젯이 고름 |
범위 지정
<style>
규칙의
:hover
·
:active
· 키 눌림 (
color-mix
). 반투명 서페이스 스타일 아래에서는 섞은 색을 서페이스 변수로 넘겨 재질의 몫이 그대로 접힘
|
| 아이콘 슬롯 | IconTheme(size, color) |
IconTheme(iconStyle) — Icon 이 크기 · 색을 냄 |
| chrome emit | paint-ready BoxDecoration / TextStyle |
Tailwind class + inline CSS |
| 스케일링 | theme.scaling 을 resolver 에서 곱함 |
rem 이 브라우저 텍스트 배율에 반응 |