adaptive_actions 0.2.2 copy "adaptive_actions: ^0.2.2" to clipboard
adaptive_actions: ^0.2.2 copied to clipboard

Adaptive action placement with Material and Cupertino renderers for Flutter.

adaptive_actions #

Package Likes Points

EN / 中文

Adaptive action bars for Flutter. Keep the important actions visible, move the rest into overflow, and share the same action model between Material and Cupertino UI.

Features #

  • Responsive primary actions that change between icon-and-label and icon-only presentations before moving into overflow.
  • Material and Cupertino renderers backed by the same action tree and command payloads.
  • Hierarchical menus that keep their declared nesting when a parent action is compressed into overflow.
  • Pinned, automatic, overflow-only, and hidden placement, plus retention priorities and independent display-order overrides.
  • Leaf, menu, and composite actions with enabled, destructive, tooltip, and semantic-label metadata.
  • Menu dividers rendered with the native Material and Cupertino components.
  • Custom primary-action and overflow-trigger builders without replacing menu ownership or layout resolution.
  • Animated layout changes, anchored menus, and LTR/RTL-aware affordances.
  • Platform-neutral resolver and renderer extension points for custom UI.

See it in action #

Material Apple (Cupertino)
Responsive Material actions
Material actions adapt to width
Responsive Apple actions
Apple actions adapt to width
Nested hierarchy in overflow
Material example actions across three available widths
Nested hierarchy in overflow
Apple example actions across three available widths
Custom button builders RTL-aware nested menus
Custom action and More buttons Apple nested menu following RTL

Getting started #

Add the package:

flutter pub add adaptive_actions

Start with one action list. Your app still owns the command handling, so an enum is often enough for a small feature:

import 'package:adaptive_actions/material.dart';
import 'package:flutter/material.dart';

enum DocumentCommand { save, share, delete }

final documentActions = ActionCollection<DocumentCommand>(
  roots: [
    AdaptiveAction.action(
      id: ActionId('save'),
      metadata: const ActionMetadata(label: 'Save', iconKey: 'save'),
      payload: DocumentCommand.save,
      placementPolicy: ActionPlacementPolicy(
        placement: ActionPlacement.pinned,
      ),
    ),
    AdaptiveAction.action(
      id: ActionId('share'),
      metadata: const ActionMetadata(label: 'Share', iconKey: 'share'),
      payload: DocumentCommand.share,
    ),
    AdaptiveAction.action(
      id: ActionId('delete'),
      metadata: const ActionMetadata(
        label: 'Delete',
        subtitle: 'This cannot be undone',
        iconKey: 'delete',
        isDestructive: true,
      ),
      payload: DocumentCommand.delete,
      placementPolicy: ActionPlacementPolicy(
        placement: ActionPlacement.overflowOnly,
      ),
    ),
  ],
);

Place MaterialAdaptiveActions in your Material action area:

MaterialAdaptiveActions<DocumentCommand>.moreAction(
  actions: documentActions,
  primaryCapacity: 320,
  onInvoke: (command) => handleDocumentCommand(command),
  overflowTooltip: AppLocalizations.of(context).moreActions,
  iconBuilder: (context, action) => switch (action.metadata.iconKey) {
    'save' => const Icon(Icons.save),
    'share' => const Icon(Icons.share),
    'delete' => const Icon(Icons.delete),
    _ => null,
  },
)

primaryCapacity is the width left for actions after the title, padding, and other controls have taken their space. As it shrinks, automatic actions use a smaller layout or move into overflow. Pinned and overflow-only actions keep the placement you requested.

Use the Apple UI #

Use the same documentActions and command handler with the Cupertino widget:

import 'package:adaptive_actions/cupertino.dart';
import 'package:flutter/cupertino.dart';

CupertinoAdaptiveActions<DocumentCommand>.moreAction(
  actions: documentActions,
  primaryCapacity: 320,
  onInvoke: (command) => handleDocumentCommand(command),
  overflowTooltip: AppLocalizations.of(context).moreActions,
  iconBuilder: (context, action) => switch (action.metadata.iconKey) {
    'save' => const Icon(CupertinoIcons.floppy_disk),
    'share' => const Icon(CupertinoIcons.share),
    'delete' => const Icon(CupertinoIcons.delete),
    _ => null,
  },
)

Only the widget and icon mapping change.

Action labels, optional menu subtitles, tooltips, and semantic labels come from your ActionMetadata values. A subtitle appears below its label in Material and Apple menus, but does not change primary action buttons or their layout. The generic constructors require an explicit overflowIcon and keep overflowTooltip empty. The .moreAction constructors add the conventional platform More icon and a visible, overridable More actions tooltip; pass your localized string as shown above when localization is required.

More examples and behavior #

Explore the example app

The example app runs on Android, iOS, Linux, macOS, Web, and Windows. Its controls let you:

  • switch between Material and Apple UI;
  • change available width and parent height;
  • try placement, retention, and ordering options;
  • toggle animations, actions, text direction, and theme;
  • open nested menus and inspect the result.

Run it locally:

cd example
fvm flutter run
Placement, menus, and display order

Placement #

Placement Behavior
pinned Stays in the primary area. If it cannot fit, the result reports the problem instead of moving it.
automatic Uses available space and retention priority to choose primary, overflow, or hidden.
overflowOnly Always appears in overflow.
hidden Is not shown.

For automatic actions, PrimaryRetentionPriority.low, .normal, and .high decide which actions stay visible for longer. Use PrimaryRetentionPriority.custom(value) when you need your own scale. Retention does not override placement rules or display order.

Constructor Direct invocation Children
AdaptiveAction.action Required None
AdaptiveAction.menu None Required
AdaptiveAction.composite Required Required

The layout places root actions. When a menu or composite action moves into overflow, all of its children move with it and keep their declared order. A disabled branch cannot be invoked or opened.

Use AdaptiveMenuDivider between child actions to separate menu groups. It is rendered as PopupMenuDivider on Material and CupertinoMenuDivider on Cupertino. Both display targets are enabled by default and can be configured independently:

AdaptiveAction<DocumentCommand>.menu(
  id: ActionId('share'),
  metadata: const ActionMetadata(label: 'Share'),
  children: [
    shareLink,
    const AdaptiveMenuDivider<DocumentCommand>.menuOnly(),
    deleteShare,
  ],
)

Use ActionCollection.withEntries when the divider is between top-level actions:

final documentActions = ActionCollection<DocumentCommand>.withEntries(
  entries: [
    save,
    const AdaptiveMenuDivider<DocumentCommand>.menuOnly(),
    delete,
  ],
);

showInPrimary controls the vertical separator between directly rendered actions. showInMenu controls dividers in action and overflow menus. When both are false, the divider remains declared but is not rendered. Dividers never participate in action placement, and a boundary is omitted when either side has no adjacent declared action in that region. Use menuOnly(), primaryOnly(), or hidden() as shorthand for the corresponding visibility settings.

Display order overrides #

Use primaryOrderOverride and overflowOrderOverride when display order needs to differ from declaration order:

MaterialAdaptiveActions<DocumentCommand>.moreAction(
  actions: documentActions,
  primaryCapacity: 320,
  primaryOrderOverride: [ActionId('share'), ActionId('save')],
  overflowOrderOverride: [ActionId('delete'), ActionId('share')],
  onInvoke: (command) => handleDocumentCommand(command),
)

Overrides only reorder actions inside primary or overflow. They do not move an action between regions. Unlisted actions keep their slots, unknown IDs are ignored, and duplicate IDs are rejected.

Framework and Core #

Most apps can stay with MaterialAdaptiveActions or CupertinoAdaptiveActions. Reach for Core when you need custom placement rules or a custom renderer.

Framework and Core details

Use these public imports. Do not import package:adaptive_actions/src/...:

// Material widget plus the complete Core API.
import 'package:adaptive_actions/material.dart';

// Cupertino widget plus the complete Core API.
import 'package:adaptive_actions/cupertino.dart';

// Platform-neutral models and resolver only.
import 'package:adaptive_actions/core.dart';

Core places actions; widgets render the result:

ActionCollection + placement policy + available capacity
                           │
                           ▼
                ActionLayoutResolver
                           │
                           ▼
       ordered primary │ overflow │ hidden │ diagnostics
                           │
                           ▼
        Material │ Cupertino │ your own renderer

The renderer reports the layouts it can draw and their costs, then renders the ordered ActionLayoutResult. Core does not measure widgets, choose icons, run commands, or depend on Material or Cupertino.

Custom placement #

Implement ActionPlacementDelegate to change placement while keeping the standard validation and ordering:

final resolver = ActionLayoutResolver(
  placementDelegate: MyActionPlacementDelegate(),
);

MaterialAdaptiveActions<DocumentCommand>.moreAction(
  actions: documentActions,
  resolver: resolver,
  primaryCapacity: 320,
  onInvoke: (command) => handleDocumentCommand(command),
)

The built-in widgets can use that resolver directly.

Custom rendering #

A custom renderer imports package:adaptive_actions/core.dart, provides ActionLayoutProfile and ActionLayoutOption values, creates an ActionLayoutRequest, and renders the returned ActionLayoutResult in order. It does not need a shared UI base class or Core changes. The final result owns placement and display order; renderers only translate that result into UI. Custom renderers can use primaryDividerBeforeActionIds and overflowDividerBeforeActionIds to render resolved top-level group boundaries.

Development #

Local checks
# Format, analyze, and test the package and example application.
make check

"Buy Me A Coffee" Alipay WechatPay

ETH BTC

License #

This project is licensed under the MIT License. See LICENSE for the full license text.

MIT License

Copyright (c) 2026 Fries_I23

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
1
likes
160
points
0
downloads
screenshot

Documentation

API reference

Publisher

verified publisherfriesi23.icu

Weekly Downloads

Adaptive action placement with Material and Cupertino renderers for Flutter.

Repository (GitHub)
View/report issues

Topics

#flutter #adaptive-ui #material-design #cupertino #responsive-ui

License

MIT (license)

Dependencies

collection, cupertino_icons, flutter, meta

More

Packages that depend on adaptive_actions