genui_button

A GenUI-compatible button widget package, giving AI agents the ability to generate highly styled buttons connected to your app's interactions.

Features

  • Provides a GenUIButton widget defined as a GenUI CatalogItem.
  • Theme Support: Seamlessly falls back to your app's Material 3 ThemeData if properties are absent!
  • Deep UI Control: Supports full width layout, custom tooltips, shadowing, explicit border colors and widths.
  • Typographic Control: Explicit font weight declarations and letter spacing customizations.
  • Material Styles: Supports elevated, filled, outlined, and text visual button types.
  • Integrates flawlessly with genui properties to accept a label string and action ID payload.
  • Out-of-the-box UserActionEvent triggering when tapping.
  • Handled Accessibility out-of-the-box with Semantics.

Getting started

In your pubspec.yaml, add the dependency:

dependencies:
  flutter:
    sdk: flutter
  genui_button: ^0.0.6

Usage

Simply register genUiButton with your GenUI catalogues:

import 'package:genui_button/genui_button.dart';
import 'package:genui/genui.dart';

final a2uiMessageProcessor = A2uiMessageProcessor(
  catalogs: [
    CoreCatalogItems.asCatalog().copyWith([genUiButton])
  ],
);

Then your FirebaseAiContentGenerator (or similar generator) system instruction can tell the agent to use GenUIButton globally.

final contentGenerator = FirebaseAiContentGenerator(
  systemInstruction: 'Use GenUIButton to provide user actions with custom styling.',
  additionalTools: a2uiMessageProcessor.getTools(),
);

Whenever the agent generates a UI containing a button, it can trigger UserActionEvent actions back to the app logic that you configure.

The AI can also deeply customize the button's look dynamically using the schema:

  • type: elevated, filled, outlined, text
  • backgroundColor: #FF5733
  • foregroundColor: #FFFFFF
  • borderColor: #000000
  • borderWidth: 1.5
  • shadowColor: #FF0000
  • borderRadius: 8.0
  • elevation: 4.0
  • padding: 16.0
  • fontSize: 18.0
  • fontWeight: bold, normal, w700
  • letterSpacing: 1.2
  • gradientColors: "#FF0000, #00FF00" (Comma separated linear gradient)
  • fullWidth: true
  • isLoading: true (Disables button and shows spinner)
  • tooltip: "Click me to proceed!"
  • data: {"id": 123} (Static payload parameters)
  • contextMapping: {"user": "session/user_id"} (Dynamic context collection from DataContext)
  • debounceMs: 500 (Debounce duration in milliseconds)
  • onLongPressAction: "secondary_action" (Alternative action on long press)
  • onDoubleClickAction: "like_action" (Action on double click/tap)
  • onHoverEnterAction: "show_preview" (Action on mouse hover enter)
  • onHoverExitAction: "hide_preview" (Action on mouse hover exit)
  • confirmation: {"title": "Warning!", "message": "Delete?"} (Confirmation dialog setup)

Realistic Action Handling

GenUIButton supports advanced interaction patterns to build production-ready AI UIs:

Context & Data Mapping

You can now map button actions to specific keys in the GenUI DataContext. This allows the button to "collect" state from other components (like text fields or checkboxes) that have bound their values to the context.

{
  "action": "submit_form",
  "contextMapping": {
    "email": "form/email_field",
    "accept_terms": "form/terms_accepted"
  }
}

Native Confirmation Dialogs

For destructive or important actions, you can configure a native confirmation dialog without any extra logic on the app side:

{
  "action": "clear_data",
  "confirmation": {
    "isEnabled": true,
    "title": "Clear all data?",
    "message": "This action is permanent and cannot be reversed.",
    "confirmText": "Delete All",
    "cancelText": "Keep My Data",
    "onConfirmAction": "explicitly_confirmed_delete",
    "onCancelAction": "user_cancelled_delete"
  }
}

Key confirmation properties:

  • isEnabled: Set to false to conditionally disable the dialog.
  • onConfirmAction: Override the default action when the user confirms.
  • onCancelAction: Trigger a specific event if the user cancels.

### Handling Actions (Developer Guide)

When a developer uses this package, the button triggers a `UserActionEvent`. Here is the best way to handle these actions in your Flutter app using a `GenUiSurface`:

```dart
GenUiSurface(
  message: lastAiMessage,
  onEvent: (UiEvent event) {
    if (event is UserActionEvent) {
      // 1. Identify the action triggered by the AI
      final actionName = event.name;
      
      // 2. Access the data payload (static data + context mapping)
      final payload = event.context; 

      switch (actionName) {
        case 'submit_order':
          _processOrder(payload['order_id'], payload['user_email']);
          break;
        case 'user_confirmed_delete':
          _performDeletion(payload['id']);
          break;
        case 'analytics_cancel':
          _logCancellation(payload['reason']);
          break;
      }
    }
  },
)

Advanced Confirmation Logic

The confirmation object allows for interactive safety checks before an action is dispatched.

Property Description
isEnabled (Boolean) If false, the dialog is skipped and the action runs immediately.
onConfirmAction (String) Overrides the primary action specifically when the user confirms.
onCancelAction (String) A separate event to dispatch if the user clicks "Cancel".

Conditional Confirmation Example

The AI can decide to only show a confirmation if a certain condition is met (e.g., "Are you sure you want to exit without saving?"):

{
  "action": "exit_screen",
  "confirmation": {
    "isEnabled": true,
    "title": "Unsaved Changes",
    "message": "You have unsaved work. Exit anyway?",
    "confirmText": "Exit",
    "onConfirmAction": "FORCE_EXIT_CONFIRMED",
    "onCancelAction": "STAY_ON_PAGE"
  }
}

Handling Local Actions

This guide explains how to intercept and handle custom actions from AI-generated UI components (like buttons) using the GenUI framework. This is essential for executing local Flutter logic—such as navigation, showing SnackBars, or calling local APIs—without requiring a round-trip to the AI model.

Overview

When a user interacts with a component (e.g., clicking a GenUIButton), a UserActionEvent is dispatched. By default, this event is serialized to JSON and sent to the AI so it can generate the next turn of the conversation.

However, you often want to run Predefined Local Actions. There are two primary ways to do this.


Subclassing the A2uiMessageProcessor is the cleanest and most robust approach for "Local-Only" actions. It allows you to intercept specific events and prevent them from being sent to the AI.

1. Define your custom Processor

Create a class that extends A2uiMessageProcessor and override the handleUiEvent method.

import 'package:genui/genui.dart';

class MyLocalActionProcessor extends A2uiMessageProcessor {
  MyLocalActionProcessor({required super.catalogs});

  @override
  void handleUiEvent(UiEvent event) {
    if (event is UserActionEvent) {
      // Logic for specific local actions
      if (event.name == 'local:show_toast') {
        _handleShowToast(event.context['message']);
        
        // Return early to "consume" the event. 
        // It will NOT be sent to the AI.
        return; 
      }
      
      if (event.name == 'local:navigate') {
        _handleNavigation(event.context['route']);
        return;
      }
    }

    // Forward all other events to the AI
    super.handleUiEvent(event);
  }

  void _handleShowToast(dynamic message) {
    print('Toast: $message');
  }

  void _handleNavigation(dynamic route) {
    print('Navigating to: $route');
  }
}

2. Implementation in your State

Use your custom processor instead of the default one when initializing your GenUiConversation.

@override
void initState() {
  super.initState();
  final catalog = CoreCatalogItems.asCatalog().copyWith([genUiButton]);
  
  // Use your subclassed processor
  final processor = MyLocalActionProcessor(catalogs: [catalog]);

  _conversation = GenUiConversation(
    a2uiMessageProcessor: processor,
    contentGenerator: _generator,
    // ...
  );
}

Method 2: Listening to the onSubmit Stream

Use this method if you want the local action to occur, but you also want the AI to see the event so it can respond textually (e.g., "I've updated your settings as requested").

final processor = A2uiMessageProcessor(catalogs: [catalog]);

// Listen to the stream of UI events sent to the AI
processor.onSubmit.listen((message) {
  // The 'message.text' contains a JSON string of the UserActionEvent
  if (message.text.contains('my_predefined_action')) {
    print('Local logic executed, and the AI is processing this too!');
  }
});

Handling Data and Payloads

When the AI generates a button, it can pass data to your local action through the data and contextMapping properties of the GenUIButton.

Accessing Static Data

In your processor, access the event.context map:

if (event.name == 'local:add_item') {
  final itemId = event.context['id'];
  final quantity = event.context['qty'] ?? 1;
  // Run your logic: cartService.add(itemId, quantity);
}

Prompting the AI

To ensure the AI uses your local actions correctly, include them in your System Instructions:

PREDEFINED LOCAL ACTIONS:
- 'local:show_snackbar': Displays a notification. Requires 'message' in data.
- 'local:navigate_to_profile': Opens the user profile.

Best Practices

Tip

Namespace your actions: Use a prefix like local: (e.g., local:save_draft) to clearly distinguish between actions handled by Flutter and actions handled by the AI.

Important

Use Context Mapping: If you need real-time app state in your action (like a userID stored in a DataContext), tell the AI to use contextMapping. This ensures the event payload contains the current value of that variable.

Caution

Memory Management: If using Method 2, remember to cancel your stream subscription in the dispose() method to avoid memory leaks. Subclassing (Method 1) is generally safer as it follows the lifecycle of the processor itself.

Example Application

See the example/ folder for a more comprehensive Flutter project demonstrating how genui_button parses styling under the hood, including its interactions with parent themes!

Libraries

genui_button
A highly customizable, GenUI-compatible button widget package.