Button | CoUI

Button

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

Button#

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

Live Preview#

사용 시기 (When to Use)#

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

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

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

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

기본 사용법 (Basic Usage)#

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

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

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

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

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

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

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

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

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

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

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

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

// composite 의 sub-component variant 를 외부에서 결정
Select(
  trigger: Button(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(                              // 모든 버튼 공통
        padding: CoreEdgeInsets.symmetric(horizontal: 16),
      ),
      variantStyles: {
        CoreButtonVariant.primary: CoreButtonStyle(        // primary 만 더 큰 padding
          padding: CoreEdgeInsets.symmetric(horizontal: 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 시각 효과 끄기 (widget — buttonStyle.disableHoverEffect 와 OR)
disableFocusOutline bool false 포커스 아웃라인 끄기
enableFeedback bool? null 햅틱 피드백 (Web no-op)
focusNode FocusNode? null 포커스 노드
statesController WidgetStatesController? null 외부 상태 컨트롤러

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

필드타입설명
backgroundColorColor? / String?배경색
foregroundColor Color? / String? 텍스트/아이콘색 (foreground)
borderColorColor? / String?보더 색
borderWidthdouble?보더 두께 (logical px)
borderRadiusCoreBorderRadius?보더 반경
dashedBorderbool?대시 보더 사용 여부
dashedBorderColor Color? / String? 대시 보더 색 (borderColor 로 fallback)
heightdouble?고정 높이
widthdouble?고정 너비
minWidthdouble?최소 너비
maxWidthdouble?최대 너비
paddingCoreEdgeInsets?패딩
iconLabelGapStyle CoreGapStyle? leading/trailing ↔ label 간격 (CoreGapStyle.size 슬롯)
animationDuration Duration? hover/focus/pressed chrome 전환 duration
disableHoverEffect bool? hover 시각 효과 끄기 (widget disableHoverEffect 와 OR)
labelStyle CoreTextStyle? label 텍스트 스타일 (fontSize/fontWeight/color 등)
leadingIconStyle CoreIconStyle? leading 아이콘 스타일 (size/color)
trailingIconStyle CoreIconStyle? trailing 아이콘 스타일 (size/color)

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

변형 (Variants)#

Primary#

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

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

Secondary#

보조 액션에 사용합니다.

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

Ghost#

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

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

Outline#

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

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

Destructive#

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

Button(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 버튼은 하나만 사용하세요.

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

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


❌ Don't#

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

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

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

✅ Do#

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

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

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


❌ Don't#

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

Button(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 필드
Button(
  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
Button(
  variant: CoreButtonVariant.primary,        // ← 위젯 파라미터 그대로
  size: CoreComponentSize.lg,                 // ← 위젯 파라미터 그대로
  onPressed: () {},
  buttonStyle: CoreButtonStyle(
    width: 200,
    padding: CoreEdgeInsets.symmetric(horizontal: 24),
    labelStyle: CoreTextStyle(fontWeight: CoreFontWeight.semiBold),
    leadingIconStyle: CoreIconStyle(size: 18),
  ),
  leading: Icon(Icons.check),
  child: Text('Submit'),
)

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

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