SwitchField | CoUI
LogoCoUI

SwitchField

라벨과 설명이 포함된 스위치 폼 필드 컴포넌트

SwitchField#

라벨과 보조 설명이 포함된 폼 친화적인 스위치 컴포넌트입니다.

Live Preview#

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • 설정 화면에서 기능을 켜거나 끄는 이진 상태를 제어하는 경우
  • 라벨과 설명 텍스트가 함께 필요한 스위치 형태의 폼 필드
  • 알림, 다크 모드, 자동 저장 등 즉각 효과가 있는 토글 설정
  • 폼 내에서 체크박스보다 직관적인 ON/OFF 표현이 필요한 경우

대신 다른 컴포넌트를 사용하세요:

  • Toggle: 라벨 없이 단독으로 사용하는 간단한 스위치
  • Checkbox: 여러 항목의 선택 여부를 폼 내에서 관리하는 경우
  • RadioGroup: 상호 배타적인 여러 옵션 중 하나를 선택하는 경우

기본 사용법 (Basic Usage)#

// 기본 스위치 필드
SwitchField(
  value: isNotificationEnabled,
  onChanged: handleNotificationToggled,
  label: '알림 수신',
)

// 설명 포함
SwitchField(
  value: isDarkMode,
  onChanged: handleDarkModeToggled,
  label: '다크 모드',
  description: '어두운 테마를 사용합니다',
)

// 큰 크기 — 크기는 chrome 이므로 switchFieldStyle 로 진입합니다
SwitchField(
  value: isAutoSave,
  onChanged: handleAutoSaveToggled,
  label: '자동 저장',
  description: '변경 사항을 자동으로 저장합니다',
  switchFieldStyle: const CoreSwitchFieldStyle(size: CoreComponentSize.lg),
)
// 기본 스위치 필드
SwitchField(
  value: isNotificationEnabled,
  onChanged: handleNotificationToggled,
  label: '알림 수신',
)

// 설명 텍스트 포함 — description으로 보조 설명 표시
SwitchField(
  value: isDarkMode,
  onChanged: handleDarkModeToggled,
  label: '다크 모드',
  description: '어두운 테마를 사용합니다',
)

// 비활성화 상태 — enabled: false로 상호작용 차단
SwitchField(
  value: isAutoSave,
  onChanged: handleAutoSaveToggled,
  label: '자동 저장',
  description: '변경 사항을 자동으로 저장합니다',
  enabled: false,
)

Props / Parameters#

속성타입기본값설명
value bool 필수 (Flutter) / false (Web) 현재 스위치 상태
onChanged ValueChanged<bool>? 필수 (Flutter) / null (Web) 상태 변경 콜백
labelString필수스위치 라벨
description String? null 보조 설명 텍스트
enabledbooltrue활성화 여부
switchFieldStyle CoreSwitchFieldStyle? null 인스턴스별 chrome 단일 진입점

CoreSwitchFieldStyle 필드#

필드타입설명
size CoreComponentSize? Toggle size within the switch field.
labelToggleGapStyle CoreGapStyle? Nested [CoreGapStyle] slot for the horizontal gap between the label section and the toggle. Forwarded to the Gap widget rendered between the two siblings on Flutter, and drives the root <div> 's inline gap (via size ) on Web. Merged on top of [defaultLabelToggleGapStyle].
labelDescriptionGapStyle CoreGapStyle? Nested [CoreGapStyle] slot for the vertical gap between the label and the description inside the label section. Forwarded to the Gap widget rendered between the two Text siblings on Flutter, and drives the text column <div> 's inline row-gap (via size ) on Web. Merged on top of [defaultLabelDescriptionGapStyle].
clickableStyle CoreClickableStyle? Nested [CoreClickableStyle] slot for the composed Clickable (press scale / durations / focus ring / disabled opacity). Merged on top of [defaultClickableStyle] and raw-forwarded.
activeColorCoreColor?Active (on) track colour.
inactiveColor CoreColor? Inactive (off) track colour.
labelTextStyle CoreTextStyle? Label text style when enabled (typography + colour). Merged with [defaultLabelTextStyle] by the resolver.
disabledLabelTextStyle CoreTextStyle? Label text style when disabled. Merged with [defaultDisabledLabelTextStyle] by the resolver.
descriptionTextStyle CoreTextStyle? Description text style when enabled. Merged with [defaultDescriptionTextStyle] by the resolver.
disabledDescriptionTextStyle CoreTextStyle? Description text style when disabled. Merged with [defaultDisabledDescriptionTextStyle] by the resolver.

빠른 오버라이드 (Chain)#

이미 만든 SwitchField 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다.

class SwitchFieldChainExample extends StatefulWidget {
  const SwitchFieldChainExample({super.key});

  @override
  State<SwitchFieldChainExample> createState() => _SwitchFieldChainExampleState();
}

class _SwitchFieldChainExampleState extends State<SwitchFieldChainExample> {
  bool _value = false;

  @override
  Widget build(BuildContext context) {
    return SwitchField(
      label: 'Enable notifications',
      description: 'Receive push notifications.',
      value: _value,
      onChanged: (v) => setState(() => _value = v),
    ).withStyle(
      const CoreSwitchFieldStyle(
        labelToggleGapStyle: CoreGapStyle(size: CoreSpace.space24),
        labelDescriptionGapStyle: CoreGapStyle(size: CoreSpace.space4),
        activeColor: CoreColor.token(CoreColors.success),
        labelTextStyle: CoreTextStyle.token(
          CoreTextStyles.titleSmall,
          color: CoreColor.token(CoreColors.onSurface),
        ),
      ),
    );
  }
}
class SwitchFieldChainExample extends StatefulComponent {
  const SwitchFieldChainExample({super.key});

  @override
  State<SwitchFieldChainExample> createState() => _SwitchFieldChainExampleState();
}

class _SwitchFieldChainExampleState extends State<SwitchFieldChainExample> {
  bool _value = false;

  @override
  Component build(BuildContext context) {
    return SwitchField(
      label: 'Enable notifications',
      description: 'Receive push notifications.',
      value: _value,
      onChanged: (v) => setState(() => _value = v),
    ).withStyle(
      const CoreSwitchFieldStyle(
        labelToggleGapStyle: CoreGapStyle(size: CoreSpace.space24),
        labelDescriptionGapStyle: CoreGapStyle(size: CoreSpace.space4),
        activeColor: CoreColor.token(CoreColors.success),
        labelTextStyle: CoreTextStyle.token(
          CoreTextStyles.titleSmall,
          color: CoreColor.token(CoreColors.onSurface),
        ),
      ),
    );
  }
}

변형 (Variants)#

기본#

SwitchField(
  value: emailMarketing,
  onChanged: handleEmailMarketingToggled,
  label: '마케팅 이메일 수신',
)

설명 포함#

SwitchField(
  value: biometricLogin,
  onChanged: handleBiometricToggled,
  label: '생체 인증 로그인',
  description: '지문 또는 얼굴 인식으로 로그인합니다',
)

동작 스펙 (Behavior)#

인터랙션#

  • 클릭/탭: 스위치 토글 또는 레이블/설명 영역 클릭으로 상태 전환
  • 호버: 포인터 오버 시 스위치에 시각적 강조 표시
  • 포커스: 포커스 링으로 현재 포커스 상태 표시

상태 전환#

  • offon (클릭 또는 Space/Enter 입력)
  • onoff (다시 클릭 또는 Space/Enter 입력)
  • disabled 상태에서는 모든 인터랙션 무시

즉각 반영 vs 폼 제출#

  • 설정 페이지에서 SwitchField는 일반적으로 클릭 즉시 효과 반영
  • 폼 내에서 사용 시 다른 필드와 함께 제출 시 반영

사용 가이드라인 (Usage Guidelines)#

✅ Do#

description으로 스위치 효과를 명확히 설명

SwitchField(
  value: locationTracking,
  onChanged: handleLocationTrackingToggled,
  label: '위치 정보 수집',
  description: '앱 사용 중 위치 데이터를 수집하여 맞춤 서비스를 제공합니다',
)

스위치의 효과와 영향을 설명 텍스트로 명확히 전달하면 사용자가 안심하고 선택할 수 있다.


❌ Don't#

다중 선택이 필요한 경우에 SwitchField 사용

// ❌ 여러 항목 중 다수를 선택해야 하는 경우 Checkbox가 적합
Column(
  children: [
    SwitchField(value: receiveEmail, onChanged: handleEmailToggled, label: '이메일'),
    SwitchField(value: receiveSms, onChanged: handleSmsToggled, label: 'SMS'),
    SwitchField(value: receivePush, onChanged: handlePushToggled, label: '푸시 알림'),
    // 이런 경우 CheckboxGroup이 더 적합
  ],
)

다수 항목의 선택/해제를 관리할 때는 CheckboxCheckboxGroup이 더 적합하다.

✅ Do#

설정 즉시 적용되는 스위치에 로딩 상태 처리

SwitchField(
  value: isFeatureEnabled,
  onChanged: (value) async {
    unawaited(settingsService.updateFeature(value));
    handleFeatureToggled(value);
  },
  label: '베타 기능 사용',
  enabled: !isLoading, // API 호출 중 비활성화
)

비동기 설정 변경 중 중복 클릭을 방지하여 일관된 상태를 유지한다.


❌ Don't#

부정적 표현의 라벨 사용

// ❌ 부정 표현은 ON/OFF 상태와 혼란을 야기함
SwitchField(
  value: isNotificationsDisabled, // 부정 상태를 저장
  onChanged: handleNotificationsDisabledToggled,
  label: '알림 끄기', // ON이 "알림 끄기"? 혼란스러움
)

스위치 라벨은 항상 긍정형으로 작성해야 ON 상태의 의미가 명확하다.

✅ Do#

레이블을 항상 Switch 옆에 배치하세요.

SwitchField(
  label: '마케팅 알림 수신',
  description: '이벤트 및 프로모션 알림을 받습니다.',
  value: marketingEnabled,
  onChanged: handleMarketingToggled,
)

레이블 없는 스위치는 무엇을 켜고 끄는지 알 수 없습니다. 레이블과 선택적 설명을 함께 제공하여 명확한 맥락을 만들어야 합니다.


❌ Don't#

Switch로 즉각 실행되는 위험한 동작을 제어하지 마세요.

// ❌ Switch 토글 즉시 계정 삭제 시작
SwitchField(
  label: '계정 비활성화',
  value: isDeactivated,
  onChanged: (value) {
    if (value) deactivateAccountImmediately();  // 위험한 즉시 실행
  },
)

Switch는 즉각적인 ON/OFF 상태 변경에 적합합니다. 계정 삭제, 데이터 초기화 등 위험한 동작은 Dialog로 확인을 받으세요.

접근성 (Accessibility)#

키보드 인터랙션#

동작
Space스위치 상태 토글
Enter스위치 상태 토글
Tab다음 포커스 가능 요소로 이동
Shift+Tab이전 포커스 가능 요소로 이동

스크린 리더#

  • Flutter: Semanticsswitch role, label, checked 상태 전달 (예: "다크 모드, 스위치, 켜짐")
  • Web: role="switch", aria-checked, aria-label(label 텍스트), aria-describedby(description) 자동 적용

터치 타겟#

  • 최소 터치 타겟 크기: 24×24 (WCAG 2.2 2.5.8). CoreTouchTarget.minimum 이 단일 출처이고, TouchTarget(Flutter) / coui-touch-target(Web)이 그리는 크기는 그대로 둔 채 닿는 범위만 넓힙니다. 플랫폼 가이드는 더 큰 값(iOS 44 · Android 48)을 권장하며, 컴포넌트가 그보다 크게 그리는 것은 자유입니다 — 24 는 그 아래로 내려가면 틀린 선입니다.
  • 스위치 + 레이블 전체 영역이 터치 가능 영역

크로스 플랫폼 차이점 (Platform Differences)#

named properties 표면(label · description · value · onChanged · enabled · switchFieldStyle)은 양 플랫폼이 동일합니다. 아래는 렌더 경로 차이입니다.

항목FlutterWeb
렌더링 Clickable + Row + Toggle Clickable + <div> + Toggle
chrome 적용resolver 가 굽는 Flutter 값resolver 가 내보내는 class / inline CSS
필수성 value / onChangedrequired 둘 다 optional (기본값 있음)
  • Toggle: 라벨 없이 단독으로 사용하는 최소한의 스위치
  • Checkbox: 폼에서 항목 선택 여부를 관리하는 경우
  • RadioGroup: 여러 옵션 중 하나를 배타적으로 선택하는 경우

조합 예제#

// SwitchField 목록 + Fieldset 조합: 알림 설정 섹션
Fieldset(
  legend: Text('알림 설정').titleSmall.onSurface,
  children: [
    SwitchField(
      value: pushEnabled,
      onChanged: handlePushToggled,
      label: '푸시 알림',
      description: '새 메시지와 업데이트를 알려드립니다',
    ),
    SwitchField(
      value: emailEnabled,
      onChanged: handleEmailToggled,
      label: '이메일 알림',
      description: '주간 요약 및 중요 공지를 이메일로 받습니다',
    ),
    SwitchField(
      value: marketingEnabled,
      onChanged: handleMarketingToggled,
      label: '마케팅 알림',
      description: '할인 혜택 및 이벤트 정보를 받습니다',
    ),
  ],
)