Label#
폼 필드 위 또는 옆에 표시하는 정적 텍스트 라벨 컴포넌트입니다. 선택적으로 leading 아이콘과 필수 필드 표시(*)를 지원하며, 비활성 상태(disabled)도 양쪽 플랫폼 동일하게 적용됩니다.
Live Preview#
default
Web
Email
Flutter
Loading Flutter...
class LabelDefaultExample extends StatelessComponent {
const LabelDefaultExample({super.key});
@override
Component build(BuildContext context) {
return const Label(text: 'Email');
}
}
class LabelDefaultExample extends StatelessWidget {
const LabelDefaultExample({super.key});
@override
Widget build(BuildContext context) {
return const Label(text: 'Email');
}
}
required
Web
Email*
Flutter
Loading Flutter...
class LabelRequiredExample extends StatelessComponent {
const LabelRequiredExample({super.key});
@override
Component build(BuildContext context) {
return const Label(text: 'Email', isRequired: true);
}
}
class LabelRequiredExample extends StatelessWidget {
const LabelRequiredExample({super.key});
@override
Widget build(BuildContext context) {
return const Label(text: 'Email', isRequired: true);
}
}
with-icon
Web
Flutter
Loading Flutter...
class LabelWithIconExample extends StatelessComponent {
const LabelWithIconExample({super.key});
@override
Component build(BuildContext context) {
return const Label(
text: 'Email',
icon: Icon(LucideIcons.mail),
);
}
}
class LabelWithIconExample extends StatelessWidget {
const LabelWithIconExample({super.key});
@override
Widget build(BuildContext context) {
return const Label(
text: 'Email',
icon: Icon(LucideIcons.mail),
);
}
}
disabled
Web
Email
Flutter
Loading Flutter...
class LabelDisabledExample extends StatelessComponent {
const LabelDisabledExample({super.key});
@override
Component build(BuildContext context) {
return const Label(text: 'Email', disabled: true);
}
}
class LabelDisabledExample extends StatelessWidget {
const LabelDisabledExample({super.key});
@override
Widget build(BuildContext context) {
return const Label(text: 'Email', disabled: true);
}
}
chain
Web
Email*
Flutter
Loading Flutter...
class LabelChainExample extends StatelessComponent {
const LabelChainExample({super.key});
@override
Component build(BuildContext context) {
return const Label(text: 'Email', isRequired: true).withStyle(
const CoreLabelStyle(
textStyle: CoreTextStyle.token(
CoreTextStyles.titleMedium,
color: CoreColor.token(CoreColors.primary),
),
requiredColor: CoreColor.token(CoreColors.tertiary),
labelRequiredGapStyle: CoreGapStyle(size: CoreSpace.space12),
disabledOpacity: CoreOpacity.opacity50,
),
);
}
}
class LabelChainExample extends StatelessWidget {
const LabelChainExample({super.key});
@override
Widget build(BuildContext context) {
return const Label(text: 'Email', isRequired: true).withStyle(
const CoreLabelStyle(
textStyle: CoreTextStyle.token(
CoreTextStyles.titleMedium,
color: CoreColor.token(CoreColors.primary),
),
requiredColor: CoreColor.token(CoreColors.tertiary),
labelRequiredGapStyle: CoreGapStyle(size: CoreSpace.space12),
disabledOpacity: CoreOpacity.opacity50,
),
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- Input / Textarea / Checkbox 등 폼 컨트롤 옆에 라벨 텍스트가 필요할 때
- 필수 필드(
*)를 시각적으로 표시할 때 - 라벨에 leading 아이콘(메일, 사용자 아이콘 등)을 함께 표시할 때
- 폼 필드의 상태(
disabled)를 라벨에서도 함께 반영할 때
대신 다른 컴포넌트를 사용하세요:
Text: 폼과 무관한 일반 텍스트 표시Fieldset: 여러 라벨/입력을 묶는 그룹 컨테이너ValidationBadge: 라벨 옆 valid/invalid 인디케이터
기본 사용법 (Basic Usage)#
// 단순 라벨
const Label(text: 'Email')
// 필수 필드
const Label(text: 'Email', isRequired: true)
// 아이콘 함께
const Label(
text: 'Email',
icon: Icon(LucideIcons.mail),
)
// 비활성 상태
const Label(text: 'Email', disabled: true)
// 폼 컨트롤과 연결 (a11y identifier)
const Label(text: 'Email', htmlFor: 'email-input')
// 단순 라벨
const Label(text: 'Email')
// 필수 필드
const Label(text: 'Email', isRequired: true)
// 아이콘 함께
const Label(
text: 'Email',
icon: Icon(LucideIcons.mail),
)
// 비활성 상태
const Label(text: 'Email', disabled: true)
// 폼 컨트롤과 연결 (HTML `for` attribute)
const Label(text: 'Email', htmlFor: 'email-input')
빠른 오버라이드 (Chain)#
이미 만든 Label 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.
class LabelChainExample extends StatelessWidget {
const LabelChainExample({super.key});
@override
Widget build(BuildContext context) {
return const Label(text: 'Email', isRequired: true).withStyle(
const CoreLabelStyle(
textStyle: CoreTextStyle.token(
CoreTextStyles.titleMedium,
color: CoreColor.token(CoreColors.primary),
),
requiredColor: CoreColor.token(CoreColors.tertiary),
labelRequiredGapStyle: CoreGapStyle(size: CoreSpace.space12),
disabledOpacity: CoreOpacity.opacity50,
),
);
}
}
class LabelChainExample extends StatelessComponent {
const LabelChainExample({super.key});
@override
Component build(BuildContext context) {
return const Label(text: 'Email', isRequired: true).withStyle(
const CoreLabelStyle(
textStyle: CoreTextStyle.token(
CoreTextStyles.titleMedium,
color: CoreColor.token(CoreColors.primary),
),
requiredColor: CoreColor.token(CoreColors.tertiary),
labelRequiredGapStyle: CoreGapStyle(size: CoreSpace.space12),
disabledOpacity: CoreOpacity.opacity50,
),
);
}
}
Props / Parameters#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
text | String | 필수 | 라벨 텍스트 |
icon |
Widget? / Component? |
null |
leading 아이콘 (선택) |
isRequired |
bool |
false |
필수 필드 표시(*) |
disabled |
bool |
false |
비활성 상태 (전용 disabledContent 텍스트 색) |
semanticLabel |
String? |
null |
스크린 리더용 라벨 (Flutter Semantics, Web aria-label) |
htmlFor |
String? |
null |
연결된 폼 컨트롤의 id (Web for 속성, Flutter Semantics.identifier) |
labelStyle |
CoreLabelStyle? |
null |
icon size / spacing / 텍스트 chrome override |
스타일 시스템 (Style System)#
모든 chrome / dimensional / 텍스트 override 는 labelStyle 슬롯 하나로 흐릅니다.
CoreLabelStyle 필드#
| 필드 | 타입 | 설명 |
|---|---|---|
iconStyle |
CoreIconStyle? |
Leading icon style override. size defers to [defaultIconStyle]. |
iconLabelGapStyle |
CoreGapStyle? |
Nested [CoreGapStyle] slot for the gap between the leading icon and the label text — forwarded straight to
Gap(gapStyle: …)
.
Gap
runs its own resolve / scaling step so this slot is passed raw (pre-scaling) by the parent resolver.
|
labelRequiredGapStyle |
CoreGapStyle? |
Nested [CoreGapStyle] slot for the gap between the label text and the required indicator (
*
) — forwarded straight to
Gap(gapStyle: …)
.
Gap
runs its own resolve / scaling step so this slot is passed raw (pre-scaling) by the parent resolver.
|
disabledOpacity |
double? |
Opacity applied when disabled is true. |
requiredColor |
CoreColor? |
Required indicator (*) colour override. |
textStyle |
CoreTextStyle? |
Text style override for the label text (enabled state). Text colour is carried via [CoreTextStyle.color] inside this slot (sb8 — raw
textColor
field removed).
|
disabledTextStyle |
CoreTextStyle? |
Text style override for the label text when
disabled
is
true
. Mirrors [textStyle] but applies only in the disabled state — the resolver layers this on top of [defaultDisabledTextStyle]. Text colour is carried via [CoreTextStyle.color] inside this slot (sb8 — raw
disabledTextColor
field removed).
|
Resolve chain#
design system default (CoreLabelStyle.defaultX)
→ CoreLabelTheme.style // 프로젝트 공통
→ parent component slot override
→ widget.labelStyle // 인스턴스별
동작 스펙 (Behavior)#
인터랙션#
- 표시 전용 컴포넌트 — 클릭/호버 인터랙션 없음
-
disabled일 때 전용disabledContent색 (CoreLabelStyle.defaultDisabledTextStyle) 이 적용되고, 커서는forbidden으로 바뀝니다 -
disabledOpacity기본값은1— 비활성 라벨은 이미 전용 색을 들고 있어서 투명도를 더 곱하면 대비가 놓인 배경에 따라 달라지기 때문입니다. 필요하면labelStyle로 낮출 수 있습니다
레이아웃#
- 루트는
inline-flexrow — leading 아이콘 → 텍스트 → 필수(*) 순서 - 아이콘 크기:
CoreLabelStyle.defaultIconStyle(CoreIconSize.size16, 16px) - 아이콘-텍스트 간격:
CoreSpace.space6 - 텍스트-필수(
*) 간격:CoreSpace.space4
토큰#
| 항목 | Flutter | Web |
|---|---|---|
| 폰트 | labelLarge 역할 | text-${ts.labelLarge} |
| 굵기 | CoreFontWeight.medium |
font-${CoreFontWeight.scale.medium} |
| 색상 (기본) | colorScheme.onSurface |
text-${cs.onSurface} |
| 색상 (disabled) | colorScheme.disabledContent |
text-${cs.disabledContent} |
필수(*) 색상 |
colorScheme.error |
text-${cs.error} |
사용 가이드라인 (Usage Guidelines)#
✅ Do#
disabled 를 실제 폼 컨트롤의 상태와 동기화
Label(text: 'Email', disabled: !emailField.enabled)
이유: Label 은 클릭/호버 인터랙션이 없는 표시 전용 컴포넌트라 disabled 는 시각적 색상만 바꿉니다. 실제 입력 필드의 활성 상태와 따로 관리하면 라벨과 필드가 서로 다른 상태로 보여 사용자에게 혼란을 줍니다.
❌ Don't#
htmlFor 없이 시각적 근접만으로 라벨-필드를 연결하지 않기
// ❌ htmlFor 없이 라벨만 옆에 배치
Column(
children: [
const Label(text: 'Email'),
TextField(id: 'email-input'),
],
)
이유: htmlFor 로 연결하지 않으면 라벨을 클릭/탭해도 연결된 폼 컨트롤이 포커스를 받지 못하고, 스크린 리더도 라벨과 필드의 관계를 알 수 없습니다.
접근성 (Accessibility)#
-
htmlFor로 연결된 폼 컨트롤이 있으면 라벨 클릭/탭으로 컨트롤이 포커스를 받아야 한다 (Web<for>, FlutterSemantics.identifier메타). semanticLabel을 통해 스크린 리더용 텍스트를 별도 지정 가능 — 시각적 라벨과 다른 의미를 전달해야 할 때 사용.- 필수(
*) 만으로 의미 전달이 부족하면semanticLabel: 'Email (필수)'같이 명시.
관련 컴포넌트 (Related Components)#
- Form: 폼 필드 그룹화 + 검증
- Fieldset: 여러 라벨/입력의 그룹 컨테이너
- ValidationBadge: 라벨 옆 valid/invalid 인디케이터