Label | CoUI
LogoCoUI

Label

폼 필드용 정적 텍스트 라벨 컴포넌트

Label#

폼 필드 위 또는 옆에 표시하는 정적 텍스트 라벨 컴포넌트입니다. 선택적으로 leading 아이콘과 필수 필드 표시(*)를 지원하며, 비활성 상태(disabled)도 양쪽 플랫폼 동일하게 적용됩니다.

Live Preview#

사용 시기 (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#

속성타입기본값설명
textString필수라벨 텍스트
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-flex row — leading 아이콘 → 텍스트 → 필수(*) 순서
  • 아이콘 크기: CoreLabelStyle.defaultIconStyle (CoreIconSize.size16, 16px)
  • 아이콘-텍스트 간격: CoreSpace.space6
  • 텍스트-필수(*) 간격: CoreSpace.space4

토큰#

항목FlutterWeb
폰트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>, Flutter Semantics.identifier 메타).
  • semanticLabel 을 통해 스크린 리더용 텍스트를 별도 지정 가능 — 시각적 라벨과 다른 의미를 전달해야 할 때 사용.
  • 필수(*) 만으로 의미 전달이 부족하면 semanticLabel: 'Email (필수)' 같이 명시.
  • Form: 폼 필드 그룹화 + 검증
  • Fieldset: 여러 라벨/입력의 그룹 컨테이너
  • ValidationBadge: 라벨 옆 valid/invalid 인디케이터