Button | CoUI

Button

다양한 변형과 크기를 지원하는 버튼 컴포넌트

Button#

사용자 인터랙션을 위한 기본 버튼 컴포넌트입니다. Primary, secondary, ghost, outline 변형을 지원합니다.

Live Preview#

사용 시기 (When to Use)#

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

  • 폼 제출, 저장, 삭제 등 명확한 액션을 트리거할 때
  • 다이얼로그의 확인/취소 버튼이 필요할 때
  • 네비게이션이 아닌 사용자 액션을 수행할 때

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

  • Toggle: 켜기/끄기 상태를 전환할 때
  • Link (Text): 페이지 이동이 목적일 때
  • FAB: 화면의 주요 플로팅 액션일 때
  • Menu: 여러 옵션 중 선택이 필요할 때

기본 사용법 (Basic Usage)#

// 기본 버튼 — variant 는 위젯 파라미터
CoButton(
  variant: CoreButtonVariant.primary,
  onPressed: handleSubmit,
  child: Text('제출'),
)

// 다른 변형
CoButton(
  variant: CoreButtonVariant.outline,
  onPressed: handleCancel,
  child: Text('취소'),
)

// 비활성화 (onPressed null)
CoButton(
  variant: CoreButtonVariant.primary,
  onPressed: null,
  child: Text('비활성화'),
)
// 기본 버튼 — variant 는 위젯 파라미터
CoButton(
  variant: CoreButtonVariant.primary,
  onPressed: handleSubmit,
  child: text('제출'),
)

// 다른 변형
CoButton(
  variant: CoreButtonVariant.outline,
  onPressed: handleCancel,
  child: text('취소'),
)

// 비활성화
CoButton(
  variant: CoreButtonVariant.primary,
  enabled: false,
  child: text('비활성화'),
)

시맨틱 vs 스타일 — 분리 패턴 (Epic #1302)#

CoButton 의 인스턴스 정체성을 결정하는 시맨틱 enum 은 위젯 파라미터로, chrome / dimensional 미세 조정은 buttonStyle 슬롯으로 분리됩니다.

분류어디로예시
시맨틱 enum (정체성) 위젯 파라미터 variant, size, shape
chrome / dimensional (자유 override) buttonStyle: CoreButtonStyle(...) width, paddingH, backgroundColor, labelStyle
인터랙션 / 콜백 / 포커스 위젯 파라미터 onPressed, enabled, focusNode

같은 의미의 필드를 두 군데에서 받지 않습니다 — Material / shadcn / Radix / MUI / CoCard 모두 동일 패턴입니다.

CoButton(
  variant: CoreButtonVariant.primary,         // ← 시맨틱 (위젯 파라미터)
  size: CoreComponentSize.lg,                  // ← 시맨틱 (위젯 파라미터)
  onPressed: handleSubmit,                     // ← 콜백 (위젯 파라미터)
  buttonStyle: CoreButtonStyle(                // ← chrome 미세 override
    paddingH: 24,
    width: 200,
    labelStyle: CoreTextStyle(
      fontWeight: CoreFontWeight.semiBold,
    ),
    leadingIconStyle: CoreIconStyle(size: 18),
  ),
  leading: Icon(Icons.check),
  child: Text('Submit'),
)

하위 시맨틱 변경 — asChild 패턴#

composite 컴포넌트 (CoSelect, CoDialog, CoPopover 등) 가 자기 trigger 의 시맨틱 (variant) 을 외부에서 조정해야 한다면, sub-component 의 Style 안에 packed 시키지 않고 위젯 자체를 슬롯으로 받습니다 (shadcn asChild 패턴).

// composite 의 sub-component variant 를 외부에서 결정
CoSelect(
  trigger: CoButton(variant: .outline, child: Text('Select…')),
  options: [...],
)

CoreSelectStyle.triggerStyle: CoreButtonStyle? 같은 nested 슬롯은 chrome 미세 조정용 입니다 — variant 는 들어가지 않습니다.

머지 체인 (Merge Chain)#

buttonStyle 은 다음 체인을 거쳐 최종 값으로 해석됩니다 (오른쪽이 이김):

design system default for variant
  → CoreButtonTheme.style                       // 프로젝트 공통
  → CoreButtonTheme.variantStyles[widget.variant]   // variant 별
  → 부모 컴포넌트 슬롯 오버라이드               // 예: CorePopoverStyle.triggerStyle
  → widget.buttonStyle                          // 인스턴스별

각 레이어는 CoreButtonStyle 의 어떤 필드든 부분적으로 채울 수 있습니다. nested slot styles (labelStyle / leadingIconStyle / trailingIconStyle) 은 null 이 아닌 경우 재귀 머지 됩니다 — labelStyle.fontWeight 만 override 해도 labelStyle.fontSize / labelStyle.color 는 이전 레이어 값 유지.

테마 적용#

ThemeData.fromCore(
  coreComponentTheme: CoreComponentTheme(
    button: CoreButtonTheme(
      style: CoreButtonStyle(paddingH: 16),                // 모든 버튼 공통
      variantStyles: {
        CoreButtonVariant.primary: CoreButtonStyle(        // primary 만 더 큰 padding
          paddingH: 24,
          labelStyle: CoreTextStyle(
            fontWeight: CoreFontWeight.semiBold,
          ),
        ),
      },
      animationDuration: AnimationConstants.short,
      disableHoverEffect: false,
    ),
  ),
)

Props / Parameters#

속성타입기본값설명
child Widget / Component required 버튼 본문 (label)
onPressed VoidCallback? null 클릭 핸들러. null 이면 비활성화
leading Widget? / Component? null 텍스트 앞 아이콘/위젯
trailing Widget? / Component? null 텍스트 뒤 아이콘/위젯
enabled bool? null 활성화 여부 (onPressed != null 과 결합)
expanded bool false 부모 너비 채우기
variant CoreButtonVariant primary 시맨틱 변형 (위젯 파라미터)
size CoreComponentSize md 크기 토큰 (위젯 파라미터)
shape CoreButtonShape rectangle 형태 (위젯 파라미터)
buttonStyle CoreButtonStyle? null chrome / dimensional / nested slot 묶음
alignment AlignmentGeometry? null 콘텐츠 정렬
marginAlignment AlignmentGeometry? null 마진 정렬
onTap* / onLongPress* callback null 인터랙션 콜백
onHover / onFocus ValueChanged<bool>? null 상태 변경 콜백
disableTransition bool false 상태 전환 애니메이션 끄기
disableHoverEffect bool false hover 시각 효과 끄기
disableFocusOutline bool false 포커스 아웃라인 끄기
enableFeedback bool? null 햅틱 피드백 (Web no-op)
dashedBorder bool false 대시 보더
dashedBorderColor Color? / String? null 대시 보더 색
focusNode FocusNode? null 포커스 노드
statesController WidgetStatesController? null 외부 상태 컨트롤러

CoreButtonStyle 필드 (chrome / dimensional / nested slot 만)#

필드타입설명
backgroundColorColor? / String?배경색
foregroundColor Color? / String? 텍스트/아이콘색 (foreground)
borderColorColor? / String?보더 색
borderWidthdouble?보더 두께 (logical px)
borderRadiusdouble?보더 반경 (logical px)
heightdouble?고정 높이
widthdouble?고정 너비
minWidthdouble?최소 너비
maxWidthdouble?최대 너비
paddingHdouble?수평 패딩
paddingVdouble?수직 패딩
gapdouble?leading/trailing ↔ label 간격
labelStyle CoreTextStyle? label 텍스트 스타일 (fontSize/fontWeight/color 등)
leadingIconStyle CoreIconStyle? leading 아이콘 스타일 (size/color)
trailingIconStyle CoreIconStyle? trailing 아이콘 스타일 (size/color)

variant / size / shape위젯 파라미터로 들어가며 CoreButtonStyle 안에는 들어가지 않습니다.

변형 (Variants)#

Primary#

가장 강조되는 주요 액션에 사용합니다.

CoButton(variant: CoreButtonVariant.primary, onPressed: handleAction, child: Text('Primary'))

Secondary#

보조 액션에 사용합니다.

CoButton(variant: CoreButtonVariant.secondary, onPressed: handleAction, child: Text('Secondary'))

Ghost#

배경 없이 텍스트만 표시합니다. 덜 중요한 액션에 적합합니다.

CoButton(variant: CoreButtonVariant.ghost, onPressed: handleAction, child: Text('Ghost'))

Outline#

테두리만 있는 버튼입니다.

CoButton(variant: CoreButtonVariant.outline, onPressed: handleAction, child: Text('Outline'))

Destructive#

위험한 작업(삭제 등)에 사용합니다.

CoButton(variant: CoreButtonVariant.destructive, onPressed: handleDelete, child: Text('삭제'))

동작 스펙 (Behavior)#

상태 전환#

  • defaulthoverpresseddefault
  • disabled 상태에서는 모든 인터랙션 무시, 시각적으로 흐리게 표시

포커스 관리#

  • Tab 키로 포커스 이동 시 포커스 아웃라인 표시
  • 포커스 상태에서 Enter/Space 로 활성화
  • disableFocusOutline: true 로 포커스 아웃라인 제거 가능

애니메이션#

  • hover / pressed 상태 전환: CoreButtonTheme.animationDuration (기본 150 ms)
  • disableTransition: true 로 트랜지션 비활성화 가능
  • CoreButtonTheme.disableHoverEffect: true 로 hover 시각 효과 끔 (전역)

leading / trailing#

  • leading: 버튼 텍스트 앞에 아이콘/위젯 배치
  • trailing: 버튼 텍스트 뒤에 아이콘/위젯 배치
  • buttonStyle.leadingIconStyle / trailingIconStyle 로 size/color 미세 조정

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

✅ Do#

한 화면에 Primary 버튼은 하나만 사용하세요.

CoButton(variant: CoreButtonVariant.primary, onPressed: handleSave, child: Text('저장'))
CoButton(variant: CoreButtonVariant.outline, onPressed: handleCancel, child: Text('취소'))

Primary 는 가장 중요한 액션 하나에만 사용하고, 보조 액션은 outline 이나 ghost 를 사용합니다.


❌ Don't#

여러 Primary 버튼을 나란히 배치하지 마세요.

CoButton(variant: CoreButtonVariant.primary, onPressed: handleSave, child: Text('저장'))
CoButton(variant: CoreButtonVariant.primary, onPressed: handleCancel, child: Text('취소'))

사용자가 주요 액션을 구분할 수 없어 혼란을 야기합니다.

✅ Do#

버튼 레이블은 동작을 명확히 설명하세요.

CoButton(variant: CoreButtonVariant.destructive, onPressed: handleDelete, child: Text('계정 삭제'))

사용자가 버튼을 누르기 전에 결과를 예측할 수 있어야 합니다.


❌ Don't#

모호한 레이블을 사용하지 마세요.

CoButton(variant: CoreButtonVariant.destructive, onPressed: handleDelete, child: Text('확인'))

위험한 작업에 "확인" 만 쓰면 사용자가 결과를 예측할 수 없습니다.

접근성 (Accessibility)#

키보드 인터랙션#

동작
Enter버튼 활성화 (클릭과 동일)
Space버튼 활성화 (클릭과 동일)
Tab다음 포커스 가능 요소로 이동
Shift+Tab이전 포커스 가능 요소로 이동

스크린 리더#

  • Flutter: Semantics 로 버튼 역할과 레이블 자동 전달. enabled: false 시 "비활성화" 상태 안내
  • Web: <button> 요소 사용으로 네이티브 접근성 자동 적용

터치 타겟#

  • 최소 터치 타겟 크기: 48×48 dp (Material 가이드라인)

마이그레이션 (Epic #1302)#

이전 버전에서 chrome / dimensional 필드는 위젯의 flat 파라미터였습니다. 이제 buttonStyle: CoreButtonStyle(...) 안으로 이동했습니다. variant / size / shape 시맨틱 enum 은 위젯 파라미터로 그대로 유지 됩니다.

// Before — flat 필드
CoButton(
  variant: CoreButtonVariant.primary,
  size: CoreComponentSize.lg,
  width: 200,
  paddingH: 24,
  textStyle: CoreTextStyle(fontWeight: CoreFontWeight.semiBold),
  iconSize: 18,
  onPressed: () {},
  leading: Icon(Icons.check),
  child: Text('Submit'),
)

// After — variant/size 위젯 파라미터, chrome 만 buttonStyle
CoButton(
  variant: CoreButtonVariant.primary,        // ← 위젯 파라미터 그대로
  size: CoreComponentSize.lg,                 // ← 위젯 파라미터 그대로
  onPressed: () {},
  buttonStyle: CoreButtonStyle(
    width: 200,
    paddingH: 24,
    labelStyle: CoreTextStyle(fontWeight: CoreFontWeight.semiBold),
    leadingIconStyle: CoreIconStyle(size: 18),
  ),
  leading: Icon(Icons.check),
  child: Text('Submit'),
)

기존 외부 프로젝트 호환을 위한 deprecated 라벨은 제공하지 않으며, 각 사용처를 직접 새 API 로 옮겨주세요.

  • FAB: 화면의 주요 플로팅 액션 버튼. 한 화면에 하나의 주요 액션을 강조할 때 사용
  • Toggle: 켜기/끄기 상태를 전환하는 컴포넌트. 버튼과 달리 상태를 유지
  • Menu: 여러 액션을 묶어 제공할 때. 버튼 클릭으로 메뉴를 열 수 있음