VisorVisor
FlutterWidgets

VisorButton

Flutter button widget with variants, sizes, width modes, loading state, and icon slots. Reads all styling from visor_core BuildContext extensions.

VisorButton is Visor's primary interactive button for Flutter. It wraps Material 3 button types with Visor's semantic color tokens and spacing scale.

Preview

When to Use

  • Triggering an action (submit, save, delete, confirm)
  • Primary and secondary CTAs on a page
  • Form submission
  • Destructive actions requiring visual emphasis

When Not to Use

  • Navigation to another page (use go_router or Navigator.pushNamed)
  • Toggling state on/off (use Switch or Toggle Group)
  • Selecting from options (use Select or Radio Group)

Installation

npx visor add button --target flutter

Or copy components/flutter/visor_button/visor_button.dart into your project.

Basic Usage

import 'package:ui/ui.dart';

VisorButton(
  label: 'Save',
  onPressed: _save,
)

Variants

// Primary — solid filled (default)
VisorButton(
  label: 'Primary',
  onPressed: () {},
  style: VisorButtonStyle.primary,
)

// Secondary — tonal filled
VisorButton(
  label: 'Secondary',
  onPressed: () {},
  style: VisorButtonStyle.secondary,
)

// Ghost — text-only
VisorButton(
  label: 'Ghost',
  onPressed: () {},
  style: VisorButtonStyle.ghost,
)

// Destructive — filled in error palette
VisorButton(
  label: 'Delete',
  onPressed: _delete,
  style: VisorButtonStyle.destructive,
)

Sizes

VisorButton(label: 'Small',  onPressed: () {}, size: VisorButtonSize.sm)
VisorButton(label: 'Medium', onPressed: () {}, size: VisorButtonSize.md)
VisorButton(label: 'Large',  onPressed: () {}, size: VisorButtonSize.lg)

Width

// Hug — wraps label content (default)
VisorButton(label: 'Hug', onPressed: () {}, width: VisorButtonWidth.hug)

// Full — expands to parent width
VisorButton(label: 'Full width', onPressed: () {}, width: VisorButtonWidth.full)

With Icons

import 'package:phosphor_flutter/phosphor_flutter.dart';

// Leading icon
VisorButton(
  label: 'Upload',
  onPressed: _upload,
  leadingIcon: const PhosphorIcon(PhosphorIcons.upload),
)

// Trailing icon
VisorButton(
  label: 'Next',
  onPressed: _next,
  trailingIcon: const PhosphorIcon(PhosphorIcons.arrowRight),
)

Loading State

Pass isLoading: true to show a spinner and disable interaction:

VisorButton(
  label: 'Saving...',
  onPressed: _save,
  isLoading: _isSaving,
)

Disabled State

Pass onPressed: null to disable the button:

VisorButton(
  label: 'Disabled',
  onPressed: null,
)

Accessibility

VisorButton wraps itself in a Semantics(button: true) container. Override the announced label via semanticLabel:

VisorButton(
  label: 'X',
  onPressed: _dismiss,
  semanticLabel: 'Dismiss notification',
)

API Reference

PropertyTypeDefaultDescription
labelStringrequiredButton label text
onPressedVoidCallback?requiredTap handler; null disables
styleVisorButtonStyle.primaryVisual variant
brandVisorButtonBrand.primaryColor palette source
sizeVisorButtonSize.mdPadding + text size preset
widthVisorButtonWidth.hug.hug or .full
leadingIconWidget?nullIcon before label
trailingIconWidget?nullIcon after label
isLoadingboolfalseShow spinner, disable tap
semanticLabelString?nullOverride accessibility label

Source

  • components/flutter/visor_button/visor_button.dart
  • Quality contract audit row: docs/flutter-widget-quality-contract.md (Rec8)