Label | CoUI
๋ณธ๋ฌธ์œผ๋กœ ๊ฑด๋„ˆ๋›ฐ๊ธฐ
LogoCoUI

Label

ํผ ํ•„๋“œ์šฉ ์ •์  ํ…์ŠคํŠธ ๋ผ๋ฒจ ์ปดํฌ๋„ŒํŠธ

๋ชฉ์ฐจ

ํผ ํ•„๋“œ ์œ„ ๋˜๋Š” ์˜†์— ํ‘œ์‹œํ•˜๋Š” ์ •์  ํ…์ŠคํŠธ ๋ผ๋ฒจ ์ปดํฌ๋„ŒํŠธ์ž…๋‹ˆ๋‹ค. ์„ ํƒ์ ์œผ๋กœ leading ์•„์ด์ฝ˜๊ณผ ํ•„์ˆ˜ ํ•„๋“œ ํ‘œ์‹œ(*)๋ฅผ ์ง€์›ํ•˜๋ฉฐ, ๋น„ํ™œ์„ฑ ์ƒํƒœ(disabled)๋„ ์–‘์ชฝ ํ”Œ๋žซํผ ๋™์ผํ•˜๊ฒŒ ์ ์šฉ๋ฉ๋‹ˆ๋‹ค.

Label ๋‹จ๋…์œผ๋กœ๋Š” ํผ ์ปจํŠธ๋กค๊ณผ ์—ฐ๊ฒฐ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ผ๋ฒจ๊ณผ ์ž…๋ ฅ์„ ์‹ค์ œ๋กœ ์—ฐ๊ฒฐ(ํด๋ฆญ ์‹œ ํฌ์ปค์Šค ์ด๋™, ์Šคํฌ๋ฆฐ ๋ฆฌ๋” ๊ด€๊ณ„)ํ•˜๋ ค๋ฉด FormField ๋ฅผ ์“ฐ์„ธ์š” โ€” Flutter Semantics container+label, Web aria-labelledby ๋กœ ์ด๋ฏธ ๊ทธ ์—ฐ๊ฒฐ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ• (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)
// ๋‹จ์ˆœ ๋ผ๋ฒจ
const Label(text: 'Email')

// ํ•„์ˆ˜ ํ•„๋“œ
const Label(text: 'Email', isRequired: true)

// ์•„์ด์ฝ˜ ํ•จ๊ป˜
const Label(
  text: 'Email',
  icon: Icon(LucideIcons.mail),
)

// ๋น„ํ™œ์„ฑ ์ƒํƒœ
const Label(text: 'Email', disabled: true)

๋น ๋ฅธ ์˜ค๋ฒ„๋ผ์ด๋“œ (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,
      ),
    );
  }
}

Live Preview#

์‚ฌ์šฉ ์‹œ๊ธฐ (When to Use)#

์ด ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”:

  • Input / Textarea / Checkbox ๋“ฑ ํผ ์ปจํŠธ๋กค ์˜†์— ๋ผ๋ฒจ ํ…์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•  ๋•Œ
  • ํ•„์ˆ˜ ํ•„๋“œ(*)๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œ์‹œํ•  ๋•Œ
  • ๋ผ๋ฒจ์— leading ์•„์ด์ฝ˜(๋ฉ”์ผ, ์‚ฌ์šฉ์ž ์•„์ด์ฝ˜ ๋“ฑ)์„ ํ•จ๊ป˜ ํ‘œ์‹œํ•  ๋•Œ
  • ํผ ํ•„๋“œ์˜ ์ƒํƒœ(disabled)๋ฅผ ๋ผ๋ฒจ์—์„œ๋„ ํ•จ๊ป˜ ๋ฐ˜์˜ํ•  ๋•Œ

๋Œ€์‹  ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”:

  • Text: ํผ๊ณผ ๋ฌด๊ด€ํ•œ ์ผ๋ฐ˜ ํ…์ŠคํŠธ ํ‘œ์‹œ
  • FormField: ์ž…๋ ฅ๊ณผ ๋ผ๋ฒจยท์„ค๋ช…ยท์˜ค๋ฅ˜ยท๊ฒ€์ฆ ์ปจํ…์ŠคํŠธ๋ฅผ ํ•จ๊ป˜ ๊ตฌ์„ฑํ•ด์•ผ ํ•  ๋•Œ. ๋ผ๋ฒจ ํด๋ฆญ์œผ๋กœ ์ž…๋ ฅ์„ ํฌ์ปค์Šคํ•˜๋Š” ๋™์ž‘์„ ์ž๋™ ๋ณด์žฅํ•˜๋Š” ๋ž˜ํผ๋Š” ์•„๋‹™๋‹ˆ๋‹ค
  • Fieldset: ์—ฌ๋Ÿฌ ๋ผ๋ฒจยท์ž…๋ ฅ์— ๊ณตํ†ต legendยท๋น„ํ™œ์„ฑ ์Šค์ฝ”ํ”„๊ฐ€ ํ•„์š”ํ•  ๋•Œ
  • ValidationBadge: ํ•„๋“œ ์ด๋ฆ„ ๋Œ€์‹  valid/invalid ์ƒํƒœ๋ฅผ ํ‘œ์‹œํ•  ๋•Œ

Props / Parameters#

W ๋Š” Flutter์˜ Widget, Web์˜ Jaspr Component ์ž…๋‹ˆ๋‹ค. W? ๋Š” ์„ ํƒ ์Šฌ๋กฏ์ž…๋‹ˆ๋‹ค.

์†์„ฑํƒ€์ž…๊ธฐ๋ณธ๊ฐ’์„ค๋ช…
textStringํ•„์ˆ˜๋ผ๋ฒจ ํ…์ŠคํŠธ
icon W? null leading ์•„์ด์ฝ˜ (์„ ํƒ)
isRequired bool false ํ•„์ˆ˜ ํ•„๋“œ ํ‘œ์‹œ(*)
disabled bool false ๋น„ํ™œ์„ฑ ์ƒํƒœ (์ „์šฉ disabledContent ํ…์ŠคํŠธ ์ƒ‰)
semanticLabel String? null ์Šคํฌ๋ฆฐ ๋ฆฌ๋”์šฉ ๋ผ๋ฒจ (Flutter Semantics, Web aria-label)
htmlFor String? null Deprecated โ€” ์–ด๋А ํ”Œ๋žซํผ์—์„œ๋„ ๋ผ๋ฒจโ†”์ปจํŠธ๋กค์„ ์ž‡์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋Œ€์‹  FormField ๋ฅผ ์“ฐ์„ธ์š”.
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 for the enabled state. While disabled the * follows the colour of [disabledTextStyle].
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 ์œผ๋กœ ๋ฐ”๋€๋‹ˆ๋‹ค
  • ๊ธ€์ž ยท ํ•„์ˆ˜ ํ‘œ์‹œ(*) ยท ์•„์ด์ฝ˜์€ ์ด ๋น„ํ™œ์„ฑ ๊ธ€์ž ์Šคํƒ€์ผ(disabledTextStyle)์˜ ์ƒ‰ ํ•˜๋‚˜๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ disabledTextStyle ์˜ ์ƒ‰์„ ๋ฐ”๊พธ๋ฉด ์…‹์ด ํ•จ๊ป˜ ๋ฐ”๋€Œ๊ณ , requiredColor ๋Š” ํ™œ์„ฑ ์ƒํƒœ์—์„œ๋งŒ ์“ฐ์ž…๋‹ˆ๋‹ค โ€” ๋น„ํ™œ์„ฑ ํ•„๋“œ์— ๋ถ‰์€ * ๊ฐ€ ๋‚จ์•„ "์ง€๊ธˆ ์ž…๋ ฅํ•ด์•ผ ํ•œ๋‹ค"๋Š” ์ž˜๋ชป๋œ ์‹ ํ˜ธ๋ฅผ ์ฃผ์ง€ ์•Š์Šต๋‹ˆ๋‹ค
  • 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}
ํ•„์ˆ˜(*) ์ƒ‰์ƒ (disabled) colorScheme.disabledContent (๊ธ€์ž์ƒ‰๊ณผ ๊ฐ™์Œ) text-${cs.disabledContent} (๊ธ€์ž์ƒ‰๊ณผ ๊ฐ™์Œ)

์‚ฌ์šฉ ๊ฐ€์ด๋“œ๋ผ์ธ (Usage Guidelines)#

โœ… Do#

disabled ๋ฅผ ์‹ค์ œ ํผ ์ปจํŠธ๋กค์˜ ์ƒํƒœ์™€ ๋™๊ธฐํ™”

Label(text: 'Email', disabled: !emailField.enabled)

์ด์œ : Label ์€ ํด๋ฆญ/ํ˜ธ๋ฒ„ ์ธํ„ฐ๋ž™์…˜์ด ์—†๋Š” ํ‘œ์‹œ ์ „์šฉ ์ปดํฌ๋„ŒํŠธ๋ผ disabled ๋Š” ์‹œ๊ฐ์  ์ƒ‰์ƒ๋งŒ ๋ฐ”๊ฟ‰๋‹ˆ๋‹ค. ์‹ค์ œ ์ž…๋ ฅ ํ•„๋“œ์˜ ํ™œ์„ฑ ์ƒํƒœ์™€ ๋”ฐ๋กœ ๊ด€๋ฆฌํ•˜๋ฉด ๋ผ๋ฒจ๊ณผ ํ•„๋“œ๊ฐ€ ์„œ๋กœ ๋‹ค๋ฅธ ์ƒํƒœ๋กœ ๋ณด์—ฌ ์‚ฌ์šฉ์ž์—๊ฒŒ ํ˜ผ๋ž€์„ ์ค๋‹ˆ๋‹ค.


โŒ Don't#

Label ์„ ํผ ์ปจํŠธ๋กค ์˜†์— ์‹œ๊ฐ์ ์œผ๋กœ๋งŒ ๋ฐฐ์น˜ํ•˜๊ณ  ์—ฐ๊ฒฐ๋๋‹ค๊ณ  ๊ฐ€์ •ํ•˜์ง€ ์•Š๊ธฐ

// โŒ Label ์€ ์ปจํŠธ๋กค๊ณผ ์—ฐ๊ฒฐ๋˜์ง€ ์•Š๋Š”๋‹ค โ€” htmlFor ๋ฅผ ์ค˜๋„ ๋งˆ์ฐฌ๊ฐ€์ง€
Column(
  children: [
    const Label(text: 'Email'),
    TextField(id: 'email-input'),
  ],
)

// โœ… FormField ๊ฐ€ ๋ผ๋ฒจ-์ปจํŠธ๋กค ์—ฐ๊ฒฐ์„ ์‹ค์ œ๋กœ ๋งŒ๋“ ๋‹ค
FormField(
  label: 'Email',
  child: TextField(),
)

์ด์œ : Label ์€ ์ •์  ํ…์ŠคํŠธ ํ‘œ์‹œ ์ปดํฌ๋„ŒํŠธ๋ผ ๋ผ๋ฒจ์„ ํด๋ฆญ/ํƒญํ•ด๋„ ์ปจํŠธ๋กค์ด ํฌ์ปค์Šค๋ฅผ ๋ฐ›์ง€ ๋ชปํ•˜๊ณ , ์Šคํฌ๋ฆฐ ๋ฆฌ๋”๋„ ๋ผ๋ฒจ๊ณผ ํ•„๋“œ์˜ ๊ด€๊ณ„๋ฅผ ์•Œ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ ์—ฐ๊ฒฐ์€ FormField ๊ฐ€ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค.

์ ‘๊ทผ์„ฑ (Accessibility)#

์—ญํ• #

  • Flutter โ€” role ์—†์Œ. semanticLabel ์„ ์ฃผ๋ฉด Semantics(label:) ๋กœ, htmlFor ๋ฅผ ์ฃผ๋ฉด Semantics(identifier:) ๋กœ ํ•œ ๊ฒน์”ฉ ๊ฐ์‹ธ์ง€์ง€๋งŒ ์–ด๋А ์ชฝ๋„ role ์„ ์„ธ์šฐ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • Web โ€” ๋ฃจํŠธ ํƒœ๊ทธ๊ฐ€ <span> ์œผ๋กœ ๊ณ ์ •์ด๊ณ  role ์„ ๋ถ™์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค. htmlFor ๋Š” ๊ทธ <span> ์˜ for ์†์„ฑ์œผ๋กœ, semanticLabel ์€ aria-label ๋กœ ์‹ค๋ฆฝ๋‹ˆ๋‹ค. ๋‚ด๋ถ€์˜ ์•„์ด์ฝ˜ ๋ž˜ํผ์™€ ํ•„์ˆ˜ ํ‘œ์‹œ(*) <span> ์—๋„ ์—ญํ• ์ด ์—†์Šต๋‹ˆ๋‹ค.

ํ‚ค๋ณด๋“œ#

์—†์Œ. ๋ผ๋ฒจ์€ ์–ด๋А ํ”Œ๋žซํผ์—์„œ๋„ ํฌ์ปค์Šค๋ฅผ ๋ฐ›์ง€ ์•Š์Šต๋‹ˆ๋‹ค โ€” Flutter ๊ตฌํ˜„์— Focus / FocusNode ๊ฐ€ ์—†๊ณ  Web <span> ์— tabindex ๊ฐ€ ๋ถ™์ง€ ์•Š์•„ tab stop ์ด ์•„๋‹ˆ๋ฉฐ, ํ‚ค ํ•ธ๋“ค๋Ÿฌ๋„ ์–‘์ชฝ ๋ชจ๋‘ ์—†์Šต๋‹ˆ๋‹ค. ๋ผ๋ฒจ์„ ๋ˆŒ๋Ÿฌ ์—ฐ๊ฒฐ๋œ ์ปจํŠธ๋กค๋กœ ๋„˜์–ด๊ฐ€๋Š” ๋™์ž‘๋„ ์—†์Šต๋‹ˆ๋‹ค(โ†’ ์•Œ๋ ค์ง„ ์ œ์•ฝ).

์Šคํฌ๋ฆฐ ๋ฆฌ๋”#

  • ์ ‘๊ทผ ๊ฐ€๋Šฅํ•œ ์ด๋ฆ„์˜ ์ถœ์ฒ˜๋Š” ํ‘œ์‹œ ํ…์ŠคํŠธ(text)์ž…๋‹ˆ๋‹ค. semanticLabel ์„ ์ฃผ๋ฉด ๊ทธ ๊ฐ’์ด ๋Œ€์‹  ์ฝํž™๋‹ˆ๋‹ค โ€” Flutter ๋Š” Semantics(label:), Web ์€ aria-label ๋กœ ๋‚˜๊ฐ€๋ฉฐ Web ์ชฝ ๋„๋‹ฌ์„ฑ์—๋Š” ์ œ์•ฝ์ด ์žˆ์Šต๋‹ˆ๋‹ค(โ†’ ์•Œ๋ ค์ง„ ์ œ์•ฝ).
  • isRequired: true ๋Š” * ๊ธ€๋ฆฌํ”„ ํ•˜๋‚˜๋กœ๋งŒ ํ‘œํ˜„๋ฉ๋‹ˆ๋‹ค. aria-required ๊ฐ™์€ ์ƒํƒœ๋‚˜ "ํ•„์ˆ˜" ๋ผ๋Š” ํ…์ŠคํŠธ๋กœ๋Š” ๋‚˜๊ฐ€์ง€ ์•Š์œผ๋ฏ€๋กœ, ํ•„์ˆ˜์ž„์„ ํ™•์‹คํžˆ ์•Œ๋ ค์•ผ ํ•˜๋ฉด semanticLabel: 'Email (ํ•„์ˆ˜)' ์ฒ˜๋Ÿผ ์ด๋ฆ„์— ์ง์ ‘ ๋‹ด์œผ์„ธ์š”.
  • ์ƒํƒœ ํ†ต๋ณด ์—†์Œ โ€” ๋ผ๋ฒจ์€ ์ •์  ํ…์ŠคํŠธ์ด๊ณ  live region ์ด ์•„๋‹™๋‹ˆ๋‹ค.

ํฌ์ปค์Šค ๊ด€๋ฆฌ#

ํ•ด๋‹น ์—†์Œ. ์˜ค๋ฒ„๋ ˆ์ด๊ฐ€ ์•„๋‹ˆ๊ณ  ํฌ์ปค์Šค๋ฅผ ๋ฐ›์ง€๋„ ์•Š์œผ๋ฏ€๋กœ ์ง„์ž…ยท์ดํƒˆยทํŠธ๋žฉ ๋ชจ๋‘ ๋Œ€์ƒ์ด ์•„๋‹™๋‹ˆ๋‹ค. Tab ์€ ๋ผ๋ฒจ์„ ๊ฑด๋„ˆ๋œ๋‹ˆ๋‹ค.

์•Œ๋ ค์ง„ ์ œ์•ฝ#

  • htmlFor ๋Š” ์–ด๋А ํ”Œ๋žซํผ์—์„œ๋„ ๋ผ๋ฒจ๊ณผ ์ปจํŠธ๋กค์„ ์‹ค์ œ๋กœ ์ž‡์ง€ ์•Š์Šต๋‹ˆ๋‹ค. Web ์˜ ๋ฃจํŠธ๋Š” tag: 'span' ์œผ๋กœ ๊ณ ์ •์ธ๋ฐ for ๋Š” <label> / <output> ์—์„œ๋งŒ ์˜๋ฏธ๋ฅผ ๊ฐ–๋Š” ์†์„ฑ์ด๋ผ <span for="..."> ๋Š” ์•„๋ฌด ๊ด€๊ณ„๋„ ๋งŒ๋“ค์ง€ ์•Š๊ณ , Flutter ๋Š” ๊ทธ ๊ฐ’์„ Semantics(identifier:) ๋ฉ”ํƒ€๋กœ๋งŒ ์‹ค์„ ๋ฟ์ž…๋‹ˆ๋‹ค. ๋‘ ๊ตฌํ˜„ ๋ชจ๋‘ ํƒญ/ํด๋ฆญ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ์—†์–ด ๋ผ๋ฒจ์„ ๋ˆŒ๋Ÿฌ๋„ ์ปจํŠธ๋กค์ด ํฌ์ปค์Šค๋ฅผ ๋ฐ›์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ฃจํŠธ ํƒœ๊ทธ๋Š” ํ˜ธ์ถœ๋ถ€๊ฐ€ ๋ฐ”๊ฟ€ ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ์šฐํšŒํ•  ๋ฐฉ๋ฒ•๋„ ์—†์Šต๋‹ˆ๋‹ค. htmlFor ๋Š” deprecated ์ด๋ฉฐ, ์‹ค์ œ ๋ผ๋ฒจ-์ปจํŠธ๋กค ์—ฐ๊ฒฐ์ด ํ•„์š”ํ•˜๋ฉด FormField ๋ฅผ ์“ฐ์„ธ์š”.
  • Web ์˜ semanticLabel ์€ role ์ด ์—†๋Š” <span> ์— aria-label ๋กœ ์‹ค๋ฆฝ๋‹ˆ๋‹ค. ARIA ๋Š” ์ œ๋„ค๋ฆญ ์š”์†Œ์— ์ด๋ฆ„์„ ๋ถ™์ด๋Š” ๊ฒƒ์„ ๊ธˆ์ง€ํ•˜๋ฏ€๋กœ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด ๊ฐ’์„ ์ ‘๊ทผ์„ฑ ํŠธ๋ฆฌ์— ๋…ธ์ถœํ•˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Flutter ๋Š” Semantics(label:) ๋กœ ๊ฐ์‹ธ ๊ฐ’์„ ๊ทธ๋Œ€๋กœ ์ „๋‹ฌํ•˜๋ฏ€๋กœ ๊ฐ™์€ ์ฝ”๋“œ๊ฐ€ ๋‘ ํ”Œ๋žซํผ์—์„œ ๋‹ค๋ฅด๊ฒŒ ์ฝํž™๋‹ˆ๋‹ค.
  • disabled: true ๋Š” ์ƒํƒœ๋กœ ํ†ต๋ณด๋˜์ง€ ์•Š๊ณ  ์‹œ๊ฐ์œผ๋กœ๋งŒ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค. Web ์€ cursor-not-allowed ์™€ ํ…์ŠคํŠธ ์ƒ‰์„ ๋ฐ”๊พธ๊ณ  disabledOpacity ๋ฅผ ์ธ๋ผ์ธ opacity ๋กœ ๋‚ด๋ณด๋‚ด๋ฉฐ, Flutter ๋„ SystemMouseCursors.forbidden ๊ณผ Opacity ๋ฅผ ์”Œ์›๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์–ด๋А ์ชฝ๋„ aria-disabled ๋‚˜ Semantics(enabled: false) ๋ฅผ ์„ธ์šฐ์ง€ ์•Š์•„ ์Šคํฌ๋ฆฐ ๋ฆฌ๋”์—๋Š” ํ™œ์„ฑ ๋ผ๋ฒจ๊ณผ ๊ตฌ๋ณ„๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ disabledOpacity ์˜ ๊ธฐ๋ณธ๊ฐ’์ด CoreOpacity.opacity100(= 1) ์ด๋ผ ๊ธฐ๋ณธ ๊ฒฝ๋กœ์—์„œ๋Š” ์•ŒํŒŒ ๊ฐ์‡ ๊ฐ€ ์ผ์–ด๋‚˜์ง€ ์•Š๊ณ , ๋‚จ๋Š” ์‹œ๊ฐ ์ฐจ์ด๋Š” ์ƒ‰ ํ† ํฐ(onSurface โ†” disabledContent)๊ณผ ์ปค์„œ๋ฟ์ž…๋‹ˆ๋‹ค โ€” ์ƒ‰ ๊ฐ•์ œ ๋ชจ๋“œ์—์„œ๋Š” ๋‘ ์ƒ‰์ด ๊ฐ™์€ ์‹œ์Šคํ…œ ์ƒ‰์œผ๋กœ ์ ‘ํžˆ๊ณ  ์ปค์„œ๋Š” ๋งˆ์šฐ์Šค ์‚ฌ์šฉ์ž์—๊ฒŒ๋งŒ ๋ณด์ž…๋‹ˆ๋‹ค. ๊ฐ์‡ ๊ฐ€ ํ•„์š”ํ•˜๋ฉด CoreLabelStyle(disabledOpacity: โ€ฆ) ๋กœ ์ง์ ‘ ๋‚ฎ์ถ”์„ธ์š”.

ํฌ๋กœ์Šค ํ”Œ๋žซํผ ์ฐจ์ด์  (Platform Differences)#

ํ•ญ๋ชฉFlutterWeb
ํ‘œ์‹œ ๋ฃจํŠธRowlabel ํƒœ๊ทธ๊ฐ€ ์•„๋‹Œ span
semanticLabelSemantics.labelgeneric span์˜ aria-label
deprecated htmlFor์‹œ๋งจํ‹ฑ identifierspan์˜ for ์†์„ฑ. ์ž…๋ ฅ ์—ฐ๊ฒฐ ํšจ๊ณผ๋Š” ์—†์Œ
๋น„ํ™œ์„ฑ ํ‘œ์‹œํ•ด์„๋œ ํ…์ŠคํŠธ ์ƒ‰ยท์„ ํƒ์  Opacityยท์ปค์„œCSS ๋น„ํ™œ์„ฑ ์ƒ‰ยทopacityยท์ปค์„œ
  • Form: ํผ ํ•„๋“œ ๊ทธ๋ฃนํ™” + ๊ฒ€์ฆ
  • FormField: ๋ผ๋ฒจ-์ปจํŠธ๋กค์„ ์‹ค์ œ๋กœ ์—ฐ๊ฒฐํ•˜๋Š” ํผ ํ•„๋“œ ๋ž˜ํผ
  • Fieldset: ์—ฌ๋Ÿฌ ๋ผ๋ฒจ/์ž…๋ ฅ์˜ ๊ทธ๋ฃน ์ปจํ…Œ์ด๋„ˆ
  • ValidationBadge: ๋ผ๋ฒจ ์˜† valid/invalid ์ธ๋””์ผ€์ดํ„ฐ