elastic_sheet 0.1.2 copy "elastic_sheet: ^0.1.2" to clipboard
elastic_sheet: ^0.1.2 copied to clipboard

Elastic expand/collapse surface for Flutter with realistic showcase examples.

elastic_sheet #

pub version likes license

Care Desk showcase

elastic_sheet is a Flutter widget for building realistic expand/collapse surfaces with a liquid spring feel.

It is designed for inline actions, drawers, search affordances, composer bars, and other UI pieces that should feel like one surface stretching into another, not a modal abruptly appearing on top.

Showcase #

Feature Description Preview
Spring Presets Completely configurable spring physics, ranging from snappy drawer snaps to gentle floating surfaces. Presets
Anchor (Compact) 9-point anchor system allows components to expand naturally from corners and edges without disrupting layout. Anchor Compact
Anchor (Grid) Maintains layout integrity for multi-directional expansions, ideal for grids, cards, and complex responsive blocks. Anchor Grid

Features #

  • Declarative API with isExpanded
  • Controller-based API with ElasticSheetController
  • Configurable spring tuning via ElasticSheetConfig
  • Optional rebound profiles, including a sequential cross-axis stretch
  • 9-point anchors for fixed-size expansion, from topLeft to bottomRight
  • Content states for ready, pending, and unavailable
  • Legacy top, center, and bottom origins still supported
  • Fixed and measured dynamic expanded sizing
  • Runnable example app under example/

Installation #

dependencies:
  elastic_sheet: ^0.1.0

Usage #

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

class DemoCard extends StatefulWidget {
  const DemoCard({super.key});

  @override
  State<DemoCard> createState() => _DemoCardState();
}

class _DemoCardState extends State<DemoCard> {
  bool _isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () => setState(() => _isExpanded = !_isExpanded),
      child: ElasticSheet(
        isExpanded: _isExpanded,
        anchor: ElasticSheetAnchor.bottomCenter,
        config: const ElasticSheetConfig.gentle(),
        collapsedSize: const Size(220, 52),
        expandedSize: const Size(320, 320),
        expandedSizing: ElasticSheetExpandedSizing.dynamicHeight,
        collapsedChild: const Center(child: Text('Open')),
        expandedChild: const Padding(
          padding: EdgeInsets.all(16),
          child: Text('Expanded content'),
        ),
      ),
    );
  }
}

Controller API #

late final ElasticSheetController controller;

@override
void initState() {
  super.initState();
  controller = ElasticSheetController(
    vsync: this,
    config: const ElasticSheetConfig.snappy(),
  );
}

@override
void dispose() {
  controller.dispose();
  super.dispose();
}

Then use:

ElasticSheet.controlled(
  controller: controller,
  anchor: ElasticSheetAnchor.centerRight,
  collapsedSize: const Size(220, 52),
  expandedSize: const Size(320, 320),
  collapsedChild: const Text('Compose'),
  expandedChild: const Text('Composer panel'),
)

You can also opt into a more natural late-stage rebound:

const config = ElasticSheetConfig.natural();

Partial Triggers #

ElasticSheet.controlled injects ElasticSheetActions around its collapsed and expanded content. That lets any descendant open, close, toggle, or pulse the surface without turning the whole collapsedChild into one large button.

ElasticSheet.controlled(
  controller: controller,
  anchor: ElasticSheetAnchor.bottomCenter,
  collapsedSize: const Size(320, 56),
  expandedSize: const Size(320, 220),
  collapsedChild: Builder(
    builder: (context) => Row(
      children: [
        Expanded(child: TextField(controller: draftController)),
        IconButton(
          onPressed: () {
            FocusManager.instance.primaryFocus?.unfocus();
            ElasticSheetActions.of(context).expand();
          },
          icon: const Icon(Icons.add_rounded),
        ),
      ],
    ),
  ),
  expandedChild: const Text('Composer actions'),
)

Anchors #

Use anchor to keep a specific edge or corner fixed while the surface grows:

ElasticSheet(
  isExpanded: _open,
  anchor: ElasticSheetAnchor.topRight,
  collapsedSize: const Size(180, 48),
  expandedSize: const Size(320, 280),
  collapsedChild: const Text('Filters'),
  expandedChild: const Text('Expanded content'),
)

For ElasticSheetExpandedSizing.dynamicHeight, the horizontal part of the anchor is ignored so topLeft and topRight both behave like topCenter, and the same applies to the center and bottom rows.

Content States #

Use contentState when a collapsed surface does not always have expanded data.

ElasticSheet(
  isExpanded: false,
  contentState: ElasticSheetContentState.pending,
  collapsedSize: const Size(180, 48),
  collapsedChild: const Text('Waiting for slot data'),
  onPendingTap: _reloadSlots,
)
  • ready: normal expand/collapse behavior. expandedSize and expandedChild are required.
  • pending: no expanded content yet. The surface stays collapsed and plays a subtle pulse on tap.
  • unavailable: visible but disabled. No pulse and no expansion.

Example app #

The runnable showcase lives in example/. It contains the playground plus several realistic scenarios that import the package exactly as an external consumer would.

3
likes
160
points
36
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Elastic expand/collapse surface for Flutter with realistic showcase examples.

Repository (GitHub)
View/report issues

Topics

#animation #spring #expandable #bottom-sheet #surface

License

MIT (license)

Dependencies

flutter

More

Packages that depend on elastic_sheet