adaptive_sheet 1.0.1 copy "adaptive_sheet: ^1.0.1" to clipboard
adaptive_sheet: ^1.0.1 copied to clipboard

A zero-boilerplate, responsive Cupertino bottom sheet kit for Flutter. Clean header, drag physics, keyboard handling, and adaptive tablet presentation.

adaptive_sheet #

pub package license Flutter style: lints

A zero-boilerplate, responsive Cupertino bottom sheet kit for Flutter.

Every Flutter developer has re-implemented the same modal bottom sheet: a grabber, a title with a close button, drag-to-dismiss physics, keyboard avoidance, and that clean Apple look. adaptive_sheet wraps all of it in a single, readable call that returns a typed result — no boilerplate, no layout overflow, no tablet jank.

final note = await AdaptiveSheet.show<String>(
  context: context,
  header: const AdaptiveSheetHeaderData(
    title: 'New Note',
    leading: Icon(CupertinoIcons.plus_rectangle_on_rectangle),
  ),
  builder: (context, controller) => NoteComposer(
    onSave: (text) => controller.pop(text),
  ),
);

Demo #

The example app running on iOS and Android:

iOS Android
adaptive_sheet on iOS adaptive_sheet on Android

Tap any tile in the example to try the five sheets: media picker, password reset, Google Maps–style location card, live theme customizer, and a gated terms reader.

Highlights #

  • Zero boilerplate. AdaptiveSheet.show<T>(...) and context.showAdaptiveSheet<T>(...) return a Future<T?> that resolves with whatever your content hands to controller.pop(value).
  • Native drag physics. Sheets resize and dismiss with the familiar overscroll-drag interaction (pull up to expand, pull down to collapse, fling to dismiss), snapping between min / default / max stops.
  • Built-in header & closing controls. Grabber handle, leading icon, bold title, subtitle, and a rounded close button — all optional and fully customizable.
  • Keyboard aware. Sheets automatically rise above the software keyboard, so inputs are never hidden. No Padding(viewInsets) boilerplate.
  • Responsive by default. Full-width sheets on phones; width-constrained, iPad-style sheets (or an opt-in centered presentation) on tablets and wide displays.
  • Frosted barrier. Optional Gaussian blur (barrierBlur) for the genuine iOS scrim effect, or a classic dimmed barrier.
  • Data-loss safe. onWillPop guards intercept every dismissal path (close button, barrier tap, drag, back button) so a dirty form is protected.
  • Safe areas, for real. Notches, status bars, and home indicators are respected on both platforms.
  • Zero third-party runtime dependencies — only flutter and cupertino_icons (for the glyph).
  • Fully documented and tested. /// docs on every public member and a broad widget-test suite covering dismissal, guards, and responsive widths.

Installation #

Add the dependency to your pubspec.yaml:

dependencies:
  adaptive_sheet: ^1.0.0

Then run:

flutter pub get

Quick start #

import 'package:adaptive_sheet/adaptive_sheet.dart';

Future<void> pickFlavor(BuildContext context) async {
  final flavor = await context.showAdaptiveSheet<String>(
    header: const AdaptiveSheetHeaderData(
      title: 'Pick a flavor',
      leading: Icon(CupertinoIcons.fork_knife),
    ),
    builder: (context, controller) => Column(
      children: [
        for (final name in ['Vanilla', 'Chocolate', 'Strawberry'])
          ListTile(
            title: Text(name),
            onTap: () => controller.pop(name),
          ),
      ],
    ),
  );

  if (flavor != null) {
    // Do something with the chosen flavor.
  }
}

The sheet opens anchored to the bottom, drags like an iOS page sheet, and the controller.pop(name) closes it, returning name to the awaiting call.

Usage #

The header #

[AdaptiveSheetHeaderData] is a const-friendly description of everything on top of the sheet:

header: const AdaptiveSheetHeaderData(
  title: 'Share this place',
  subtitle: '3 attachments ready',
  leading: Icon(CupertinoIcons.share),
  onLeadingTap: _shareFast,
  leadingTooltip: 'Share options',   // screen-reader label
  closeButtonTooltip: 'Dismiss',
  // trailing: CustomWidget(), — replaces the close button entirely
),

The two controls intentionally read differently:

  • Leading (left) is a quiet, transparent → ghost control (leadingButtonColor / leadingButtonBackgroundColor to restyle) — a back/info affordance that never dismisses the sheet.
  • Close (right) is the prominent action: a filled, high-contrast (closeButtonColor / closeButtonBackgroundColor / closeButtonSize), with a padded 44×44 keyboard-focusable tap target and a screen-reader label.

The close button renders only when the sheet is dismissible and showCloseButton is true.

Returning a result #

builder: (context, controller) => Column(
  children: [
    ElevatedButton(
      onPressed: () => controller.pop('yes'),
      child: const Text('Confirm'),
    ),
    TextButton(
      onPressed: controller.close, // dismiss with null
      child: const Text('Cancel'),
    ),
  ],
),

pop(value) dismisses the sheet and resolves the awaited future with value. close() dismisses with whatever result is currently recorded (usually null).

Sticky footers #

Pass the footer builder to pin action buttons below the scrollable content — they stay visible no matter how far the user scrolls:

AdaptiveSheet.show<bool>(
  context: context,
  header: const AdaptiveSheetHeaderData(title: 'Terms of Service'),
  footer: (context, controller) => FilledButton(
    onPressed: () => controller.pop(true),
    child: const Text('I Agree'),
  ),
  builder: (context, controller) => _LongTermsBody(),
);

Action lists #

For iOS-style action sheets (camera / gallery / destructive), compose [AdaptiveSheetActionGroup] and [AdaptiveSheetAction]:

builder: (context, controller) => AdaptiveSheetActionGroup(
  children: [
    AdaptiveSheetAction(
      icon: CupertinoIcons.camera,
      label: 'Take Photo',
      onTap: () => controller.pop(Source.camera),
    ),
    AdaptiveSheetAction(
      icon: CupertinoIcons.trash,
      label: 'Remove Photo',
      destructive: true,
      onTap: () => controller.pop(Source.remove),
    ),
  ],
),

Guarding against data loss #

Pass onWillPop (synchronous or asynchronous) to veto accidental dismissals while a form is dirty — every path except a deliberate controller.pop() from a primary action:

  • close button
  • barrier tap
  • drag-to-dismiss fling
  • system back button / gesture
onWillPop: () async {
  if (!_isDirty) return true;
  final discard = await showCupertinoDialog<bool>(
    context: sheetContext, // captured inside `builder`
    builder: (_) => CupertinoAlertDialog(
      title: const Text('Discard changes?'),
      actions: [
        CupertinoDialogAction(
          child: const Text('Keep editing'),
          onPressed: () => Navigator.pop(_, false),
        ),
        CupertinoDialogAction(
          child: const Text('Discard'),
          onPressed: () => Navigator.pop(_, true),
        ),
      ],
    ),
  );
  return discard ?? false;
},

Because controller.pop(value) is an explicit act, it never consults the guard — a "Share", "Agree", or "Send" button that already validated is never vetoed.

Configuration #

Every behavior is tuned through [AdaptiveSheetConfig] — reuse one const AdaptiveSheetConfig() across sheets and branch with copyWith:

config: const AdaptiveSheetConfig(
  presentation: AdaptiveSheetPresentation.sheet, // or .centered
  initialHeightFraction: 0.62,   // default opening height
  minHeightFraction: 0.2,        // smallest draggable height
  maxHeightFraction: 0.95,       // largest draggable height
  enableDrag: true,
  barrierDismissible: true,
  barrierOpacity: 0.42,
  barrierBlur: 8.0,              // frosted-glass scrim
  showGrabber: true,
  tabletBreakpoint: 600,         // width threshold for "large" screens
  maxSheetWidth: 640,            // max width on tablets / desktop
  backgroundColor: CupertinoColors.systemBackground,
  avoidKeyboard: true,
  snapToStops: true,
),

expandContentToFit: true makes short confirmation/info sheets size themselves to their content instead of opening to a fixed height.

Responsive behavior #

Viewport sheet presentation centered presentation
Phone (< 600dp) Full-width bottom sheet Full-width bottom sheet
Tablet / desktop Width-capped (640) bottom sheet Centered, width-capped card

The width-constraining kicks in automatically off the [MediaQuery] size, so rotating a device or resplitting a window just works.

Examples #

The example app in the repository is a runnable reference covering five real-world use cases, all backed by widget tests. Run it locally:

cd example
flutter run
  1. Media & image picker — an iOS-style action sheet (Camera / Library / destructive Remove) built with AdaptiveSheetActionGroup.
  2. Password reset — a validating form with live strength-checklist chips, inline error feedback, keyboard avoidance, and a discard guard.
  3. Location details — a Google Maps–style place card: left-aligned title / rating / type / temporarily closed status, small share + close buttons on the right, tappable Save toggle, photo strip, and reviews.
  4. Live theme customizer — Light / Dark / System switcher plus an accent-color palette, restyling the whole app behind the transparent sheet live.
  5. Terms & conditions reader — long legal text that scrolls while a pinned footer stays visible, with an "I have read" checkbox gating the Agree button.

API overview #

  • AdaptiveSheet — static show<T> / dismiss helpers.
  • AdaptiveSheetContextExtensioncontext.showAdaptiveSheet<T>(...).
  • AdaptiveSheetConfig — sizing, physics, barrier, and style knobs.
  • AdaptiveSheetController<T>pop(value) / close() to dismiss.
  • AdaptiveSheetHeaderData / AdaptiveSheetHeader — the built-in header.
  • AdaptiveSheetAction / AdaptiveSheetActionGroup — iOS action-sheet rows.
  • AdaptiveSheetGrabber — the drag handle.
  • AdaptiveSheetLayout<T> — the embeddable layout widget.
  • AdaptiveSheetRoute<T> — the route used under the hood.
  • AdaptiveSheetPresentationsheet vs centered placement.

Author and Contact #

Developed and maintained by Ebrahim Joy.

Contributing #

Please report bugs and request features through the issue tracker. Pull requests are welcome.

License #

Released under the MIT License.

0
likes
160
points
88
downloads

Documentation

API reference

Publisher

verified publishereebrahimjoy.com

Weekly Downloads

A zero-boilerplate, responsive Cupertino bottom sheet kit for Flutter. Clean header, drag physics, keyboard handling, and adaptive tablet presentation.

Repository (GitHub)
View/report issues

Topics

#bottom-sheet #cupertino #modal #sheet #responsive

License

MIT (license)

Dependencies

cupertino_icons, flutter

More

Packages that depend on adaptive_sheet