FAB | CoUI
LogoCoUI

FAB

화면의 주요 액션을 강조하는 플로팅 액션 버튼 컴포넌트

FAB#

화면의 주요 액션을 강조하는 플로팅 액션 버튼(Floating Action Button)입니다. Fab은 단독 버튼으로도, 탭하면 액션 메뉴가 열리는 트리거로도 사용할 수 있습니다 — 열린 메뉴는 Popup 패널 안에 Button(variant: .menu) 행이 하나씩 놓인 형태입니다. Flutter와 Web이 동일한 Fab API를 공유합니다.

Live Preview#

사용 시기 (When to Use)#

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

  • 화면 내에서 가장 중요한 단일 액션을 명확히 강조하고 싶을 때 (새 항목 만들기, 작성, 공유)
  • 스크롤해도 항상 접근 가능해야 하는 주요 CTA(Call to Action)가 필요할 때
  • 하나의 트리거에서 여러 관련 액션을 메뉴로 열어 보여주고 싶을 때

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

  • Button: 여러 동등한 액션을 나란히 제공할 때, 또는 인라인 액션이 필요할 때
  • Tooltip: 아이콘 전용 FAB에 레이블 설명을 추가할 때 함께 사용

기본 사용법 (Basic Usage)#

// 기본 FAB
Fab(
  trigger: const Icon(LucideIcons.plus),
  onPressed: handleCreate,
)

// 소형 FAB
Fab(
  trigger: const Icon(LucideIcons.plus),
  size: CoreFabSize.mini,
  onPressed: handleCreate,
)

// 메뉴 FAB (탭하면 액션 메뉴가 열림)
Fab(
  trigger: const Icon(LucideIcons.plus),
  actions: [
    FabAction(
      icon: const Icon(LucideIcons.pencil),
      label: const Text('Edit'),
      onPressed: handleEdit,
    ),
    FabAction(
      icon: const Icon(LucideIcons.share2),
      label: const Text('Share'),
      onPressed: handleShare,
    ),
  ],
)
// 기본 FAB
Fab(
  trigger: const Icon(LucideIcons.plus),
  onPressed: handleCreate,
)

// 소형 FAB
Fab(
  trigger: const Icon(LucideIcons.plus),
  size: CoreFabSize.mini,
  onPressed: handleCreate,
)

// 메뉴 FAB
Fab(
  trigger: const Icon(LucideIcons.plus),
  actions: [
    FabAction(
      icon: const Icon(LucideIcons.pencil),
      label: const Text('Edit'),
      onPressed: handleEdit,
    ),
    FabAction(
      icon: const Icon(LucideIcons.share2),
      label: const Text('Share'),
      onPressed: handleShare,
    ),
  ],
)

빠른 오버라이드 (Chain)#

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

class FabChainExample extends StatelessWidget {
  const FabChainExample({super.key});

  @override
  Widget build(BuildContext context) {
    return Fab(
      trigger: const Icon(LucideIcons.plus),
      onPressed: () {},
      expandDirection: CoreFabExpandDirection.down,
      actions: [
        FabAction(
          icon: const Icon(LucideIcons.pencil),
          label: const Text('Edit'),
        ),
        FabAction(
          icon: const Icon(LucideIcons.share2),
          label: const Text('Share'),
        ),
      ],
    ).withStyle(
      const CoreFabStyle(
        diameter: CoreSize.size72,
        iconStyle: CoreIconStyle(
          size: CoreIconSize.size32,
          color: CoreColor.token(CoreColors.onPrimary),
        ),
        popupStyle: CorePopupStyle(
          backgroundColor: CoreColor.token(CoreColors.primaryContainer),
          borderRadius: CoreBorderRadius.all(CoreRadius.radius16),
        ),
        spacing: CoreSpace.space24,
      ),
    );
  }
}
class FabChainExample extends StatelessComponent {
  const FabChainExample({super.key});

  @override
  Component build(BuildContext context) {
    return Fab(
      trigger: const Icon(LucideIcons.plus),
      onPressed: () {},
      expandDirection: CoreFabExpandDirection.down,
      actions: [
        FabAction(
          icon: const Icon(LucideIcons.pencil),
          label: Text('Edit'),
        ),
        FabAction(
          icon: const Icon(LucideIcons.share2),
          label: Text('Share'),
        ),
      ],
    ).withStyle(
      const CoreFabStyle(
        diameter: CoreSize.size72,
        iconStyle: CoreIconStyle(
          size: CoreIconSize.size32,
          color: CoreColor.token(CoreColors.onPrimary),
        ),
        popupStyle: CorePopupStyle(
          backgroundColor: CoreColor.token(CoreColors.primaryContainer),
          borderRadius: CoreBorderRadius.all(CoreRadius.radius16),
        ),
        spacing: CoreSpace.space24,
      ),
    );
  }
}

Props / Parameters#

속성타입기본값설명
trigger Widget / Component 필수 메인 FAB 내부에 표시할 콘텐츠 (보통 아이콘)
onPressed VoidCallback? null 메인 FAB 클릭 핸들러. actions가 있으면 메뉴 토글로 동작
actions List<FabAction>? null 메뉴 행이 되는 액션 목록
open bool false 메뉴 열림 상태 (controlled 모드)
onOpenChanged void Function(bool)? null 메뉴 열림 상태 변경 콜백
size CoreFabSize CoreFabSize.regular FAB 크기 (mini / regular / large)
expandDirection CoreFabExpandDirection CoreFabExpandDirection.up 메뉴가 열리는 방향 ( up / down / left / right / fanfanup 과 같다)
closeIcon Widget? / Component? null 메뉴 열림 시 표시할 아이콘 (기본값: X 아이콘)
tooltip String? null 메인 FAB 툴팁 텍스트
fabStyle CoreFabStyle? null 지름 / 간격 / 애니메이션 + nested buttonStyle · actionButtonStyle · iconStyle · popupStyle · popoverStyle 등 모든 chrome 단일 진입점

FabAction#

속성타입기본값설명
icon Widget / Component 필수 메뉴 행의 leading 아이콘
label Widget? / Component? null 메뉴 행의 라벨
onPressed VoidCallback? null 액션 클릭 핸들러
tooltip String? null 액션 툴팁 텍스트

스타일 시스템 (Style System)#

CoreFabStyle 필드#

필드타입설명
buttonStyle CoreButtonStyle? Main FAB circle chrome override — raw-forwarded to the composed Button . Carries background / foreground / shadow / radius / focus ring. null (or any unset field) defers to [defaultButtonStyle].
actionButtonStyle CoreButtonStyle? Action-row chrome override — raw-forwarded to each composed Button(variant:.menu) row. null defers to [defaultActionButtonStyle].
actionLabelTextStyle CoreTextStyle? Action-row label text style override. Text colour is carried via [CoreTextStyle.color] inside this slot. Defaults to [defaultActionLabelTextStyle].
actionLabelBackgroundColor CoreColor? Retired — see [defaultActionLabelBackgroundColor]. Read by nothing.
actionLabelBoxShadow List<CoreShadowLayer>? Retired — see [defaultActionLabelBoxShadow]. Read by nothing.
actionLabelPadding CoreEdgeInsets? Retired — see [defaultActionLabelPadding]. Read by nothing.
actionLabelBorderRadius CoreBorderRadius? Retired — see [defaultActionLabelBorderRadius]. Read by nothing.
diameter double? Main FAB circular diameter override (logical px). null defers to [defaultsBySize] for the active size. Forwarded to the composed Button 's width / height .
iconStyle CoreIconStyle? Main FAB icon style override — sizes the icon glyph the FAB passes as the composed Button 's child. null defers to [defaultsBySize] for the active size.
actionDiameter double? Retired — see [defaultActionDiameter]. Read by nothing.
actionIconStyle CoreIconStyle? Action-row leading icon style override — baked into the row Button 's leadingIconStyle . null defers to [defaultActionIconStyle].
spacing double? Gap between the main FAB and the menu panel (logical px) override. Pure placement-offset scalar — never consumed as a flex gap by Gap / CSS gap .
actionGapStyle CoreGapStyle? Retired — see [defaultActionGapStyle]. Read by nothing.
duration Duration? Menu open / close animation duration override — the panel's own open / close motion, unless [popupStyle] pins one.
popoverStyle CorePopoverStyle? Overlay host chrome override. Nested [CorePopoverStyle] raw-forwarded to the composed Popover that hosts the menu (child-component-composition). null (or any unset field) defers to [defaultPopoverStyle] — chrome-less, because the visible box is the Popup in [popupStyle]. Override e.g. to tune the placement offset.
popupStyle CorePopupStyle? Menu panel chrome override. Nested [CorePopupStyle] raw-forwarded to the composed Popup that holds the action rows. null (or any unset field) defers to [defaultPopupStyle] — the same panel a Menu draws.

크기 (Sizes)#

크기지름
MiniCoreFabSize.mini40px
RegularCoreFabSize.regular56px
LargeCoreFabSize.large72px

동작 스펙 (Behavior)#

인터랙션#

  • 클릭/탭: actions가 없으면 onPressed 콜백 실행. actions가 있으면 speed-dial을 펼치거나 접음
  • speed-dial 액션 클릭: 해당 액션의 onPressed 실행 후 speed-dial 자동 닫힘
  • 호버: 커서가 포인터로 전환
  • 포커스: 포커스 링 표시로 키보드 탐색 가능 상태 표시

Controlled / Uncontrolled#

  • onOpenChanged를 제공하지 않으면 Fab이 내부 상태로 speed-dial 열림/닫힘을 관리합니다 (uncontrolled)
  • onOpenChanged를 제공하면 open 값으로 외부에서 상태를 제어합니다 (controlled)

애니메이션#

  • speed-dial 펼침/접힘: 200ms ease-in-out, 액션별 stagger 적용
  • 펼침 방향(expandDirection)에 따라 액션이 동일한 계산식으로 배치됨

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

✅ Do#

한 화면에 하나의 FAB만 사용

Fab(
  trigger: const Icon(LucideIcons.plus),
  tooltip: '새 노트 작성',
  onPressed: handleCreateNote,
)

FAB은 화면에서 가장 중요한 단일 액션을 나타내야 합니다. 여러 개를 사용하면 사용자가 어느 것이 주요 액션인지 혼란스러워집니다.


❌ Don't#

파괴적인 액션에 FAB 사용 금지

// ❌ 삭제(파괴적) 액션에 FAB 사용
Fab(
  trigger: const Icon(LucideIcons.trash2),
  onPressed: handleDeleteAll,
)

FAB은 눈에 잘 띄기 때문에 실수로 클릭하기 쉽습니다. 삭제 같은 파괴적인 액션에는 확인 다이얼로그가 있는 일반 버튼을 사용하세요.

✅ Do#

speed-dial로 관련 액션을 묶어 제공

Fab(
  trigger: const Icon(LucideIcons.plus),
  actions: [
    FabAction(
      icon: const Icon(LucideIcons.pencil),
      label: const Text('Edit'),
      onPressed: handleEdit,
    ),
    FabAction(
      icon: const Icon(LucideIcons.share2),
      label: const Text('Share'),
      onPressed: handleShare,
    ),
  ],
)

하나의 트리거에서 관련된 보조 액션을 펼쳐 보여주면 화면을 깔끔하게 유지할 수 있습니다.

접근성 (Accessibility)#

키보드 인터랙션#

동작
Enter / SpaceFAB 액션 실행 또는 speed-dial 토글
Tab다음 포커스 가능 요소로 이동

스크린 리더#

  • Flutter: Tooltip 위젯으로 버튼 레이블 전달. tooltip 속성 설정 권장
  • Web: role="button", aria-label, aria-expanded가 자동 적용

터치 타겟#

  • Mini: 40px (중요도 낮은 보조 액션에만 사용)
  • Regular: 56px (기본, 권장)
  • Large: 72px (특별히 강조가 필요한 경우)

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

항목FlutterWeb
클래스명FabFab
클릭 핸들러onPressedonPressed
elevation CoreShadow 토큰 → BoxShadow CoreShadow 토큰 → CSS box-shadow
speed-dial 토글 controlled open 상태 + 애니메이션 controlled open 상태 + CSS transition
  • Button: 인라인 또는 덜 중요한 액션에 사용
  • Tooltip: 아이콘 전용 FAB에 레이블 설명 추가 시 함께 사용