DEV Community

Nayden Gochev
Nayden Gochev

Posted on

Flutter Widgets Guide — Part 2: Every Other Widget

Flutter Widgets Guide — Part 2: Every Other Widget

This continues https://dev.to/gochev/flutter-core-widgets-part-1-4pdh (the 50 core widgets). This part covers the rest of the widget catalog: advanced layout, Slivers, advanced Material components, animation, gestures, painting, async builders, theming/media, and the full Cupertino (iOS-style) set.

**NOTE: if you are using "Open in DartPad" do not foreget to hit RUN and give it time to load, it takes about 10-15 seconds to load.


9. Advanced Layout & Slivers

LayoutBuilder

Rebuilds based on the parent's constraints — for responsive layouts.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: LayoutBuilder(
          builder: (context, constraints) {
            if (constraints.maxWidth > 400) {
              return Container(
                color: Colors.blue,
                child: const Center(child: Text('Wide layout (>400px)', style: TextStyle(color: Colors.white, fontSize: 20))),
              );
            }
            return Container(
              color: Colors.orange,
              child: const Center(child: Text('Narrow layout', style: TextStyle(color: Colors.white, fontSize: 20))),
            );
          },
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Not visible itself — it silently picks which child layout to draw based on available space, e.g. switching from a single column to a two-column layout.

ConstrainedBox

Applies extra min/max size constraints to its child.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: ConstrainedBox(
            constraints: const BoxConstraints(minHeight: 100, minWidth: 200),
            child: Container(
              color: Colors.teal,
              child: const Text('At least 100px tall', style: TextStyle(color: Colors.white)),
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: The child text sits inside an invisible box that's at least 100px tall, even though the text itself is much shorter — you'd notice the extra space around/below it if the parent highlights bounds.

UnconstrainedBox

Removes constraints imposed on a child, letting it size to its natural size (can overflow).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: UnconstrainedBox(
            child: Container(width: 500, height: 50, color: Colors.red,
              child: const Center(child: Text('500px wide — overflows!', style: TextStyle(color: Colors.white)))),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A wide red bar that ignores the parent's width limit and can visually overflow/clip beyond the screen edge.

OverflowBox

Lets a child be larger than its parent without triggering overflow warnings/clipping.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: SizedBox(
            width: 100, height: 50,
            child: OverflowBox(
              maxWidth: 300,
              child: Container(width: 300, height: 50, color: Colors.blue,
                child: const Center(child: Text('300px inside 100px box', style: TextStyle(color: Colors.white)))),
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A blue bar that visually extends past its parent's boundary as if the parent weren't there.

IntrinsicHeight / IntrinsicWidth

Sizes a child to match the intrinsic (natural) height/width of its siblings.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: IntrinsicHeight(
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                Container(width: 4, color: Colors.blue),
                const SizedBox(width: 8),
                const Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text('Line one', style: TextStyle(fontSize: 18)),
                    Text('Line two is longer', style: TextStyle(fontSize: 14)),
                    Text('Line three', style: TextStyle(fontSize: 22)),
                  ],
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Two dividers that stretch to exactly match the tallest text/content beside them, rather than being an arbitrary fixed height.

Baseline

Aligns a child based on its text baseline rather than its box edges.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Row(
            crossAxisAlignment: CrossAxisAlignment.baseline,
            textBaseline: TextBaseline.alphabetic,
            children: const [
              Text('Small', style: TextStyle(fontSize: 14)),
              SizedBox(width: 8),
              Text('BIG', style: TextStyle(fontSize: 48, color: Colors.blue)),
              SizedBox(width: 8),
              Text('Medium', style: TextStyle(fontSize: 24)),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Text that sits so its letters' baseline lines up with neighboring text of a different font size, rather than lining up by box top/bottom (subtle — noticeable when mixing font sizes in a row).

Spacer

Fills empty space in a Row/Column, like an invisible Expanded.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: "const Text('Spacer Demo')),"
        body: Column(
          children: [
            Row(children: const [Text('Left'), Spacer(), Text('Right')]),
            const Divider(),
            Row(children: const [Text('A'), Spacer(flex: 1), Text('B'), Spacer(flex: 2), Text('C')]),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: "Left" pinned to the left edge and "Right" pinned to the right edge, with all empty space between them.

Table

Lays out children in a grid with rows/columns, supporting column-width strategies.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Table(
            border: TableBorder.all(),
            columnWidths: const {0: FlexColumnWidth(), 1: FlexColumnWidth()},
            children: const [
              TableRow(children: [
                Padding(padding: EdgeInsets.all(8), child: Text('Name', style: TextStyle(fontWeight: FontWeight.bold))),
                Padding(padding: EdgeInsets.all(8), child: Text('Score', style: TextStyle(fontWeight: FontWeight.bold))),
              ]),
              TableRow(children: [
                Padding(padding: EdgeInsets.all(8), child: Text('Alice')),
                Padding(padding: EdgeInsets.all(8), child: Text('95')),
              ]),
              TableRow(children: [
                Padding(padding: EdgeInsets.all(8), child: Text('Bob')),
                Padding(padding: EdgeInsets.all(8), child: Text('87')),
              ]),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A classic spreadsheet-style grid with thin border lines separating each cell, columns auto-sized to their widest content.

DataTable

A Material-styled data grid with sortable column headers and row selection.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: SingleChildScrollView(
          scrollDirection: Axis.horizontal,
          child: DataTable(
            columns: const [
              DataColumn(label: Text('Name')),
              DataColumn(label: Text('Age')),
              DataColumn(label: Text('City')),
            ],
            rows: const [
              DataRow(cells: [DataCell(Text('Ann')), DataCell(Text('29')), DataCell(Text('London'))]),
              DataRow(cells: [DataCell(Text('Bob')), DataCell(Text('34')), DataCell(Text('Paris'))]),
              DataRow(cells: [DataCell(Text('Cal')), DataCell(Text('22')), DataCell(Text('Tokyo'))]),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A clean white table with bold column headers ("Name," "Age"), light divider lines between rows, and checkboxes on the left if selection is enabled — like a simplified spreadsheet UI.

IndexedStack

Like Stack, but only the child at the given index is actually visible/hit-testable (others stay laid out but hidden).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: IndexedStackDemo());
}

class IndexedStackDemo extends StatefulWidget {
  const IndexedStackDemo({super.key});
  @override
  State<IndexedStackDemo> createState() => _IndexedStackDemoState();
}

class _IndexedStackDemoState extends State<IndexedStackDemo> {
  int _index = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: _index,
        children: const [
          Center(child: Icon(Icons.home, size: 80, color: Colors.blue)),
          Center(child: Icon(Icons.search, size: 80, color: Colors.green)),
          Center(child: Icon(Icons.settings, size: 80, color: Colors.orange)),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _index,
        onTap: (i) => setState(() => _index = i),
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
          BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Search'),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: 'Settings'),
        ],
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Only the search icon is visible; the other two are present in the tree (keeping their state alive) but invisible — commonly used for tab content that shouldn't rebuild on switch.

CustomScrollView + Slivers

A scroll container composed of "sliver" pieces for advanced scroll effects (collapsing headers, mixed lists/grids).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: CustomScrollView(
          slivers: [
            const SliverAppBar(
              title: "Text('Collapsing Header'),"
              expandedHeight: 200,
              pinned: true,
              flexibleSpace: FlexibleSpaceBar(
                background: ColoredBox(color: Colors.indigo),
              ),
            ),
            SliverList(
              delegate: SliverChildBuilderDelegate(
                (context, i) => ListTile(title: "Text('Row \$i')),"
                childCount: 30,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A tall header image/title area that shrinks and sticks as a compact app bar once you scroll down past it, with an ordinary list continuing underneath — the classic "collapsing toolbar" effect.

SliverGrid / SliverList / SliverToBoxAdapter / SliverFillRemaining / SliverPadding

The grid/list/single-widget/padding building blocks used inside a CustomScrollView.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: CustomScrollView(
          slivers: [
            const SliverToBoxAdapter(
              child: Padding(
                padding: EdgeInsets.all(16),
                child: Text('Grid section', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
              ),
            ),
            SliverPadding(
              padding: const EdgeInsets.all(8),
              sliver: SliverGrid.count(
                crossAxisCount: 3,
                children: List.generate(6, (i) => Container(
                  margin: const EdgeInsets.all(4),
                  color: Colors.teal[100 * ((i % 8) + 1)],
                  child: Center(child: Text('\$i')),
                )),
              ),
            ),
            SliverList(
              delegate: SliverChildBuilderDelegate(
                (context, i) => ListTile(title: "Text('List item \$i')),"
                childCount: 10,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Functionally identical to GridView/ListView/Padding, but as a scrollable "slice" that composes with other slivers in one continuous scroll view instead of being its own independent scrollable.


10. Advanced Input & Forms

TextFormField

A TextField wired for use inside a Form, with built-in validation support.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: TextFormFieldDemo());
}

class TextFormFieldDemo extends StatefulWidget {
  const TextFormFieldDemo({super.key});
  @override
  State<TextFormFieldDemo> createState() => _TextFormFieldDemoState();
}

class _TextFormFieldDemoState extends State<TextFormFieldDemo> {
  final _formKey = GlobalKey<FormState>();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Form(
          key: _formKey,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              TextFormField(
                decoration: const InputDecoration(labelText: 'Name', border: OutlineInputBorder()),
                validator: (v) => v!.isEmpty ? 'Required' : null,
              ),
              const SizedBox(height: 12),
              TextFormField(
                decoration: const InputDecoration(labelText: 'Email', border: OutlineInputBorder()),
                validator: (v) => v!.contains('@') ? null : 'Invalid email',
              ),
              const SizedBox(height: 16),
              ElevatedButton(
                onPressed: () => _formKey.currentState!.validate(),
                child: const Text('Validate'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Identical to TextField visually, but shows a red error line and message beneath the box when validation fails.

Autocomplete

A text field that suggests matching options as the user types.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Padding(
          padding: const EdgeInsets.all(16),
          child: Autocomplete<String>(
            optionsBuilder: (v) => ['Apple', 'Apricot', 'Banana', 'Blueberry', 'Cherry', 'Grape']
                .where((o) => o.toLowerCase().startsWith(v.text.toLowerCase())),
            onSelected: (s) {},
            fieldViewBuilder: (ctx, ctrl, fn, onSubmit) => TextField(
              controller: ctrl,
              focusNode: fn,
              decoration: const InputDecoration(labelText: 'Type a fruit…', border: OutlineInputBorder()),
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A normal text box; as you type "Ap," a small dropdown card appears beneath it listing "Apple" as a tappable suggestion.

RangeSlider

Like Slider, but selects a min/max range with two thumbs.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: RangeSliderDemo());
}

class RangeSliderDemo extends StatefulWidget {
  const RangeSliderDemo({super.key});
  @override
  State<RangeSliderDemo> createState() => _RangeSliderDemoState();
}

class _RangeSliderDemoState extends State<RangeSliderDemo> {
  RangeValues _values = const RangeValues(20, 80);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            RangeSlider(
              values: _values,
              min: 0, max: 100,
              labels: RangeLabels('\${_values.start.round()}', '\${_values.end.round()}'),
              onChanged: (v) => setState(() => _values = v),
            ),
            Text('\${_values.start.round()}\${_values.end.round()}'),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A horizontal track with two circular thumbs; the segment between them is filled in the theme color, while the segments outside are grey.

showDatePicker / CalendarDatePicker

A modal calendar for picking a date.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: DatePickerDemo());
}

class DatePickerDemo extends StatefulWidget {
  const DatePickerDemo({super.key});
  @override
  State<DatePickerDemo> createState() => _DatePickerDemoState();
}

class _DatePickerDemoState extends State<DatePickerDemo> {
  DateTime? _picked;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(_picked == null ? 'No date selected' : 'Picked: \${_picked!.toLocal()}'.split(' ')[0],
                style: const TextStyle(fontSize: 18)),
            const SizedBox(height: 16),
            ElevatedButton(
              child: const Text('Pick a date'),
              onPressed: () async {
                final date = await showDatePicker(
                  context: context,
                  initialDate: DateTime.now(),
                  firstDate: DateTime(2020),
                  lastDate: DateTime(2030),
                );
                if (date != null) setState(() => _picked = date);
              },
            ),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A card-style calendar month grid pops up centered on screen, with the current day highlighted and a header showing month/year plus "Cancel"/"OK" buttons at the bottom.

showTimePicker

A modal clock-face (or dial input) for picking a time.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: TimePickerDemo());
}

class TimePickerDemo extends StatefulWidget {
  const TimePickerDemo({super.key});
  @override
  State<TimePickerDemo> createState() => _TimePickerDemoState();
}

class _TimePickerDemoState extends State<TimePickerDemo> {
  TimeOfDay? _picked;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(_picked == null ? 'No time selected' : _picked!.format(context),
                style: const TextStyle(fontSize: 24)),
            const SizedBox(height: 16),
            ElevatedButton(
              child: const Text('Pick a time'),
              onPressed: () async {
                final time = await showTimePicker(
                  context: context,
                  initialTime: TimeOfDay.now(),
                );
                if (time != null) setState(() => _picked = time);
              },
            ),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A circular analog clock face pops up with draggable hour/minute hands, and large digital hour:minute numerals above it that you can also tap to type directly.

SegmentedButton (Material 3)

A row of mutually-exclusive selectable segments (replaces the older CupertinoSegmentedControl look in Material).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => MaterialApp(
    theme: ThemeData(useMaterial3: true),
    home: const SegmentedButtonDemo(),
  );
}

class SegmentedButtonDemo extends StatefulWidget {
  const SegmentedButtonDemo({super.key});
  @override
  State<SegmentedButtonDemo> createState() => _SegmentedButtonDemoState();
}

class _SegmentedButtonDemoState extends State<SegmentedButtonDemo> {
  Set<int> _selected = {0};
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SegmentedButton<int>(
          segments: const [
            ButtonSegment(value: 0, label: Text('Day')),
            ButtonSegment(value: 1, label: Text('Week')),
            ButtonSegment(value: 2, label: Text('Month')),
          ],
          selected: _selected,
          onSelectionChanged: (s) => setState(() => _selected = s),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A single pill-shaped bar divided into "Day" | "Week" sections, with the selected segment shown filled in the theme color and the other left outlined/plain.

Chip / ActionChip / ChoiceChip / FilterChip / InputChip

Small pill-shaped elements representing an input, attribute, or action.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: ChipsDemo());
}

class ChipsDemo extends StatefulWidget {
  const ChipsDemo({super.key});
  @override
  State<ChipsDemo> createState() => _ChipsDemoState();
}

class _ChipsDemoState extends State<ChipsDemo> {
  bool _choice = true;
  bool _filter = false;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Wrap(
          spacing: 8, runSpacing: 8,
          children: [
            const Chip(label: Text('Plain Chip')),
            ActionChip(label: const Text('ActionChip'), onPressed: () {}),
            ChoiceChip(label: const Text('ChoiceChip'), selected: _choice,
                onSelected: (v) => setState(() => _choice = v)),
            FilterChip(label: const Text('FilterChip'), selected: _filter,
                onSelected: (v) => setState(() => _filter = v)),
            InputChip(label: const Text('InputChip'), onDeleted: () {}, onPressed: () {}),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Rounded-pill shaped tags in a row — plain grey for Chip, tappable with a ripple for ActionChip, and highlighted in the theme color when selected: true for ChoiceChip/FilterChip. InputChip additionally shows a small "x" to delete it.

ExpansionTile

A ListTile that expands/collapses to reveal nested children when tapped.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: ListView(
          children: const [
            ExpansionTile(
              title: Text('Settings'),
              children: [
                ListTile(title: Text('Notifications')),
                ListTile(title: Text('Privacy')),
                ListTile(title: Text('Storage')),
              ],
            ),
            ExpansionTile(
              title: Text('Account'),
              children: [
                ListTile(title: Text('Profile')),
                ListTile(title: Text('Billing')),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A row with a downward chevron on the right; tapping it rotates the chevron and smoothly slides open additional rows beneath it.

ExpansionPanelList

Like ExpansionTile but for a whole list of collapsible panels with elevation.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: ExpansionPanelDemo());
}

class ExpansionPanelDemo extends StatefulWidget {
  const ExpansionPanelDemo({super.key});
  @override
  State<ExpansionPanelDemo> createState() => _ExpansionPanelDemoState();
}

class _ExpansionPanelDemoState extends State<ExpansionPanelDemo> {
  final _expanded = [false, true, false];
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SingleChildScrollView(
        child: ExpansionPanelList(
          expansionCallback: (i, open) => setState(() => _expanded[i] = !open),
          children: List.generate(3, (i) => ExpansionPanel(
            headerBuilder: (c, e) => ListTile(title: Text('Panel \${i + 1}')),
            body: ListTile(title: Text('Details for panel \${i + 1}')),
            isExpanded: _expanded[i],
          )),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A stack of card-like panels, each with a header row and a chevron; expanded ones show extra content beneath with a subtle shadow separating panels.

Stepper

A multi-step, numbered wizard/form flow.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: StepperDemo());
}

class StepperDemo extends StatefulWidget {
  const StepperDemo({super.key});
  @override
  State<StepperDemo> createState() => _StepperDemoState();
}

class _StepperDemoState extends State<StepperDemo> {
  int _step = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stepper(
        currentStep: _step,
        onStepContinue: () => setState(() => _step = (_step + 1).clamp(0, 2)),
        onStepCancel: () => setState(() => _step = (_step - 1).clamp(0, 2)),
        steps: const [
          Step(title: Text('Account'), content: Text('Enter your account info.')),
          Step(title: Text('Profile'), content: Text('Set up your profile.')),
          Step(title: Text('Review'), content: Text('Confirm and submit.')),
        ],
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A vertical (or horizontal) sequence of numbered circles connected by lines, each with a title; the active step's content is expanded below it, with "Continue"/"Cancel" buttons at the bottom.

RadioListTile / CheckboxListTile / SwitchListTile

Combine a ListTile row with a Radio/Checkbox/Switch control baked in.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: ListTileControlsDemo());
}

class ListTileControlsDemo extends StatefulWidget {
  const ListTileControlsDemo({super.key});
  @override
  State<ListTileControlsDemo> createState() => _ListTileControlsDemoState();
}

class _ListTileControlsDemoState extends State<ListTileControlsDemo> {
  int _radio = 1;
  bool _check = true;
  bool _sw = false;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          RadioListTile<int>(title: const Text('Option A'), value: 1, groupValue: _radio,
              onChanged: (v) => setState(() => _radio = v!)),
          RadioListTile<int>(title: const Text('Option B'), value: 2, groupValue: _radio,
              onChanged: (v) => setState(() => _radio = v!)),
          const Divider(),
          CheckboxListTile(title: const Text('Accept terms'), value: _check,
              onChanged: (v) => setState(() => _check = v!)),
          SwitchListTile(title: const Text('Enable notifications'), value: _sw,
              onChanged: (v) => setState(() => _sw = v)),
        ],
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A standard list row with the label on the left and the switch/checkbox/radio control aligned to the right edge — the whole row is tappable, not just the control.


11. Advanced Material Components

Tooltip

Shows a short text hint on long-press/hover.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: const [
              Tooltip(message: 'Delete this item', child: Icon(Icons.delete, size: 48)),
              SizedBox(width: 24),
              Tooltip(message: 'Share with others', child: Icon(Icons.share, size: 48)),
              SizedBox(width: 24),
              Tooltip(message: 'Add to favourites', child: Icon(Icons.favorite_border, size: 48)),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Normally invisible; long-pressing or hovering over the icon pops up a small dark rounded rectangle with white text just above it, disappearing after a moment.

Badge

A small dot or count overlay, usually on an icon (e.g., notification counts).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(useMaterial3: true),
      home: Scaffold(
        body: Center(
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: const [
              Badge(label: Text('3'), child: Icon(Icons.notifications, size: 40)),
              SizedBox(width: 32),
              Badge(label: Text('99+'), child: Icon(Icons.mail, size: 40)),
              SizedBox(width: 32),
              Badge(child: Icon(Icons.shopping_cart, size: 40)),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A bell icon with a small red circle in its top-right corner containing the white numeral "3."

Divider / VerticalDivider

A thin line separating content.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Column(
          children: [
            const ListTile(title: Text('Above the divider')),
            const Divider(thickness: 2),
            const ListTile(title: Text('Below the divider')),
            Expanded(
              child: Row(
                children: const [
                  Center(child: Text('Left')),
                  VerticalDivider(thickness: 2),
                  Center(child: Text('Right')),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A thin horizontal grey line running the width of its container, with a small margin, visually separating "Above" from "Below."

MaterialBanner

A persistent, non-modal message bar anchored to the top of the content area.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: MaterialBannerDemo());
}

class MaterialBannerDemo extends StatefulWidget {
  const MaterialBannerDemo({super.key});
  @override
  State<MaterialBannerDemo> createState() => _MaterialBannerDemoState();
}

class _MaterialBannerDemoState extends State<MaterialBannerDemo> {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      ScaffoldMessenger.of(context).showMaterialBanner(MaterialBanner(
        content: const Text('Your session is expiring soon'),
        leading: const Icon(Icons.warning, color: Colors.orange),
        actions: [
          TextButton(
            onPressed: () => ScaffoldMessenger.of(context).hideCurrentMaterialBanner(),
            child: const Text('Dismiss'),
          ),
        ],
      ));
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('MaterialBanner')),
      body: const Center(child: Text('Banner appears at the top')),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A pale-colored horizontal bar just under the app bar spanning the full width, with text on the left and one or more text buttons on the right — stays on screen until dismissed (unlike a SnackBar).

LinearProgressIndicator

A horizontal loading/progress bar.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              const Text('Determinate (40%)'),
              const SizedBox(height: 8),
              const LinearProgressIndicator(value: 0.4),
              const SizedBox(height: 24),
              const Text('Indeterminate (loading)'),
              const SizedBox(height: 8),
              const LinearProgressIndicator(),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A thin horizontal track, 40% filled from the left in the theme color, the rest a lighter grey.

RefreshIndicator

Wraps a scrollable to add pull-to-refresh behavior.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: RefreshDemo());
}

class RefreshDemo extends StatefulWidget {
  const RefreshDemo({super.key});
  @override
  State<RefreshDemo> createState() => _RefreshDemoState();
}

class _RefreshDemoState extends State<RefreshDemo> {
  int _count = 5;
  Future<void> _refresh() async {
    await Future.delayed(const Duration(seconds: 1));
    setState(() => _count += 5);
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: RefreshIndicator(
        onRefresh: _refresh,
        child: ListView.builder(
          itemCount: _count,
          itemBuilder: (c, i) => ListTile(title: Text('Item \$i')),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Pulling down past the top of the list reveals a small circular spinner that stretches/snaps into place, spins while refreshing, then retracts.

NavigationRail

A vertical, icon-based navigation strip for tablet/desktop layouts.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: NavRailDemo());
}

class NavRailDemo extends StatefulWidget {
  const NavRailDemo({super.key});
  @override
  State<NavRailDemo> createState() => _NavRailDemoState();
}

class _NavRailDemoState extends State<NavRailDemo> {
  int _index = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Row(
        children: [
          NavigationRail(
            selectedIndex: _index,
            onDestinationSelected: (i) => setState(() => _index = i),
            labelType: NavigationRailLabelType.all,
            destinations: const [
              NavigationRailDestination(icon: Icon(Icons.home), label: Text('Home')),
              NavigationRailDestination(icon: Icon(Icons.search), label: Text('Search')),
              NavigationRailDestination(icon: Icon(Icons.settings), label: Text('Settings')),
            ],
          ),
          const VerticalDivider(thickness: 1, width: 1),
          Expanded(child: Center(child: Text(['Home', 'Search', 'Settings'][_index], style: const TextStyle(fontSize: 24)))),
        ],
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A narrow vertical bar on the left edge of the screen with stacked icon+label destinations; the selected one has a colored background "pill" behind its icon.

NavigationBar (Material 3)

The modern replacement for BottomNavigationBar, with a pill-style selection indicator.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => MaterialApp(
    theme: ThemeData(useMaterial3: true),
    home: const NavigationBarDemo(),
  );
}

class NavigationBarDemo extends StatefulWidget {
  const NavigationBarDemo({super.key});
  @override
  State<NavigationBarDemo> createState() => _NavigationBarDemoState();
}

class _NavigationBarDemoState extends State<NavigationBarDemo> {
  int _index = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(child: Text(['Home', 'Explore', 'Profile'][_index], style: const TextStyle(fontSize: 24))),
      bottomNavigationBar: NavigationBar(
        selectedIndex: _index,
        onDestinationSelected: (i) => setState(() => _index = i),
        destinations: const [
          NavigationDestination(icon: Icon(Icons.home), label: 'Home'),
          NavigationDestination(icon: Icon(Icons.explore), label: 'Explore'),
          NavigationDestination(icon: Icon(Icons.person), label: 'Profile'),
        ],
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Same bottom bar position as BottomNavigationBar, but the selected icon sits inside a rounded pill-shaped highlight instead of just changing color.

NavigationDrawer (Material 3)

The M3-styled version of Drawer, with rounded selection indicators.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => MaterialApp(
    theme: ThemeData(useMaterial3: true),
    home: const NavigationDrawerDemo(),
  );
}

class NavigationDrawerDemo extends StatefulWidget {
  const NavigationDrawerDemo({super.key});
  @override
  State<NavigationDrawerDemo> createState() => _NavigationDrawerDemoState();
}

class _NavigationDrawerDemoState extends State<NavigationDrawerDemo> {
  int _index = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('NavigationDrawer')),
      drawer: NavigationDrawer(
        selectedIndex: _index,
        onDestinationSelected: (i) { setState(() => _index = i); Navigator.pop(context); },
        children: const [
          Padding(padding: EdgeInsets.fromLTRB(28, 16, 16, 10), child: Text('Menu', style: TextStyle(fontSize: 14))),
          NavigationDrawerDestination(icon: Icon(Icons.home), label: Text('Home')),
          NavigationDrawerDestination(icon: Icon(Icons.bookmark), label: Text('Bookmarks')),
          NavigationDrawerDestination(icon: Icon(Icons.settings), label: Text('Settings')),
        ],
      ),
      body: Center(child: Text(['Home', 'Bookmarks', 'Settings'][_index], style: const TextStyle(fontSize: 24))),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Same slide-in side panel as Drawer, but list items use pill-shaped highlight backgrounds for the selected destination, matching Material 3's rounder aesthetic.

SearchBar / SearchAnchor

A Material 3 search input that can anchor a suggestions view.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => MaterialApp(
    theme: ThemeData(useMaterial3: true),
    home: const SearchBarDemo(),
  );
}

class SearchBarDemo extends StatelessWidget {
  const SearchBarDemo({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            SearchBar(
              hintText: 'Search...',
              leading: const Icon(Icons.search),
              padding: const MaterialStatePropertyAll(EdgeInsets.symmetric(horizontal: 16)),
            ),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A tall, fully rounded (pill-shaped) input box with a search icon on the left and placeholder text "Search..." — taller and rounder than a standard TextField.

CircleAvatar

A circular image or initials, commonly for profile pictures.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: const [
              CircleAvatar(
                radius: 36,
                backgroundImage: NetworkImage('https://picsum.photos/100'),
              ),
              SizedBox(width: 16),
              CircleAvatar(radius: 36, backgroundColor: Colors.indigo, child: Text('AB', style: TextStyle(color: Colors.white, fontSize: 20))),
              SizedBox(width: 16),
              CircleAvatar(radius: 36, backgroundColor: Colors.orange, child: Icon(Icons.person, size: 36, color: Colors.white)),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A perfectly circular cropped photo, 60px across, often used at the leading edge of a ListTile or app bar.


12. Animation & Transitions

AnimatedContainer

A Container that automatically animates changes to its properties (size, color, padding, etc.).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: AnimatedContainerDemo());
}

class AnimatedContainerDemo extends StatefulWidget {
  const AnimatedContainerDemo({super.key});
  @override
  State<AnimatedContainerDemo> createState() => _AnimatedContainerDemoState();
}

class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
  bool _expanded = false;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GestureDetector(
          onTap: () => setState(() => _expanded = !_expanded),
          child: AnimatedContainer(
            duration: const Duration(milliseconds: 400),
            curve: Curves.easeInOut,
            width: _expanded ? 250 : 100,
            height: _expanded ? 250 : 100,
            color: _expanded ? Colors.blue : Colors.red,
            child: Center(child: Text(_expanded ? 'Tap to shrink' : 'Tap', style: const TextStyle(color: Colors.white))),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A colored box that smoothly grows/shrinks and fades between colors over 300ms whenever expanded toggles, rather than jumping instantly.

AnimatedOpacity

Smoothly fades a child in/out.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: AnimatedOpacityDemo());
}

class AnimatedOpacityDemo extends StatefulWidget {
  const AnimatedOpacityDemo({super.key});
  @override
  State<AnimatedOpacityDemo> createState() => _AnimatedOpacityDemoState();
}

class _AnimatedOpacityDemoState extends State<AnimatedOpacityDemo> {
  bool _visible = true;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            AnimatedOpacity(
              opacity: _visible ? 1.0 : 0.0,
              duration: const Duration(milliseconds: 500),
              child: const FlutterLogo(size: 120),
            ),
            const SizedBox(height: 24),
            ElevatedButton(
              onPressed: () => setState(() => _visible = !_visible),
              child: Text(_visible ? 'Fade Out' : 'Fade In'),
            ),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: The Flutter logo gradually fades to fully transparent (and back) over half a second instead of blinking on/off instantly.

AnimatedAlign / AnimatedPadding / AnimatedPositioned

Animate alignment, padding, or Stack position changes respectively.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: AnimatedAlignDemo());
}

class AnimatedAlignDemo extends StatefulWidget {
  const AnimatedAlignDemo({super.key});
  @override
  State<AnimatedAlignDemo> createState() => _AnimatedAlignDemoState();
}

class _AnimatedAlignDemoState extends State<AnimatedAlignDemo> {
  Alignment _align = Alignment.topLeft;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          AnimatedAlign(
            alignment: _align,
            duration: const Duration(milliseconds: 500),
            curve: Curves.easeInOut,
            child: const FlutterLogo(size: 80),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => setState(() {
          final options = [Alignment.topLeft, Alignment.topRight, Alignment.bottomLeft, Alignment.bottomRight, Alignment.center];
          _align = options[(options.indexOf(_align) + 1) % options.length];
        }),
        child: const Icon(Icons.swap_horiz),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: The logo glides smoothly downward to a new vertical position over 400ms rather than teleporting there.

AnimatedDefaultTextStyle

Animates changes to a text's style (size, color, weight).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: AnimatedTextStyleDemo());
}

class AnimatedTextStyleDemo extends StatefulWidget {
  const AnimatedTextStyleDemo({super.key});
  @override
  State<AnimatedTextStyleDemo> createState() => _AnimatedTextStyleDemoState();
}

class _AnimatedTextStyleDemoState extends State<AnimatedTextStyleDemo> {
  bool _big = false;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            AnimatedDefaultTextStyle(
              duration: const Duration(milliseconds: 400),
              style: TextStyle(
                fontSize: _big ? 48 : 16,
                color: _big ? Colors.blue : Colors.black,
                fontWeight: _big ? FontWeight.bold : FontWeight.normal,
              ),
              child: const Text('Grow me'),
            ),
            const SizedBox(height: 24),
            ElevatedButton(onPressed: () => setState(() => _big = !_big), child: const Text('Toggle')),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: The words "Grow me" smoothly scale up/down in size over 300ms rather than snapping between sizes.

AnimatedCrossFade

Cross-fades between two children, animating the size difference too.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: AnimatedCrossFadeDemo());
}

class AnimatedCrossFadeDemo extends StatefulWidget {
  const AnimatedCrossFadeDemo({super.key});
  @override
  State<AnimatedCrossFadeDemo> createState() => _AnimatedCrossFadeDemoState();
}

class _AnimatedCrossFadeDemoState extends State<AnimatedCrossFadeDemo> {
  bool _showFirst = true;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            AnimatedCrossFade(
              firstChild: const Icon(Icons.add_circle, size: 100, color: Colors.green),
              secondChild: const Icon(Icons.remove_circle, size: 100, color: Colors.red),
              crossFadeState: _showFirst ? CrossFadeState.showFirst : CrossFadeState.showSecond,
              duration: const Duration(milliseconds: 400),
            ),
            const SizedBox(height: 24),
            ElevatedButton(onPressed: () => setState(() => _showFirst = !_showFirst), child: const Text('Switch')),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A "+" icon smoothly dissolves into a "−" icon (and the surrounding box resizes to fit) rather than an abrupt swap.

AnimatedSwitcher

Animates the transition whenever its child widget is swapped for a different one.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: AnimatedSwitcherDemo());
}

class AnimatedSwitcherDemo extends StatefulWidget {
  const AnimatedSwitcherDemo({super.key});
  @override
  State<AnimatedSwitcherDemo> createState() => _AnimatedSwitcherDemoState();
}

class _AnimatedSwitcherDemoState extends State<AnimatedSwitcherDemo> {
  int _count = 0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            AnimatedSwitcher(
              duration: const Duration(milliseconds: 300),
              child: Text('\$_count', key: ValueKey(_count), style: const TextStyle(fontSize: 80, fontWeight: FontWeight.bold)),
            ),
            const SizedBox(height: 24),
            ElevatedButton(onPressed: () => setState(() => _count++), child: const Text('Increment')),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Each time the number changes, the old digit fades/slides out while the new one fades/slides in, rather than the text just changing instantly.

AnimatedBuilder / TweenAnimationBuilder

Rebuild a widget tree on every tick of an Animation or Tween, for custom animations.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: TweenBuilderDemo());
}

class TweenBuilderDemo extends StatefulWidget {
  const TweenBuilderDemo({super.key});
  @override
  State<TweenBuilderDemo> createState() => _TweenBuilderDemoState();
}

class _TweenBuilderDemoState extends State<TweenBuilderDemo> {
  double _target = 1.0;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TweenAnimationBuilder<double>(
              tween: Tween(begin: 0, end: _target),
              duration: const Duration(milliseconds: 600),
              curve: Curves.easeInOut,
              builder: (c, v, child) => Transform.scale(scale: v, child: child),
              child: const FlutterLogo(size: 120),
            ),
            const SizedBox(height: 24),
            ElevatedButton(
              onPressed: () => setState(() => _target = _target == 1.0 ? 0.2 : 1.0),
              child: const Text('Animate'),
            ),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: The Flutter logo fades in from fully invisible to fully visible over one second when the widget first builds.

Hero

Animates a shared element seamlessly flying between two screens during navigation.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: HeroListPage());
}

class HeroListPage extends StatelessWidget {
  const HeroListPage({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Tap to expand')),
      body: Center(
        child: GestureDetector(
          onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const HeroDetailPage())),
          child: Hero(
            tag: 'flutter-logo',
            child: const FlutterLogo(size: 80),
          ),
        ),
      ),
    );
  }
}

class HeroDetailPage extends StatelessWidget {
  const HeroDetailPage({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Detail')),
      body: Center(
        child: Hero(
          tag: 'flutter-logo',
          child: const FlutterLogo(size: 250),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: The logo visually "flies" from its position on screen A to its (possibly different size/position) placement on screen B during the page transition, rather than just disappearing and reappearing.

FadeTransition / ScaleTransition / SlideTransition / RotationTransition / SizeTransition

Lower-level, explicitly-controlled animation widgets typically driven by an AnimationController.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: TransitionsDemo());
}

class TransitionsDemo extends StatefulWidget {
  const TransitionsDemo({super.key});
  @override
  State<TransitionsDemo> createState() => _TransitionsDemoState();
}

class _TransitionsDemoState extends State<TransitionsDemo> with SingleTickerProviderStateMixin {
  late final AnimationController _ctrl = AnimationController(vsync: this, duration: const Duration(seconds: 2))..repeat(reverse: true);

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            Column(mainAxisSize: MainAxisSize.min, children: [
              FadeTransition(opacity: _ctrl, child: const FlutterLogo(size: 60)),
              const Text('Fade'),
            ]),
            Column(mainAxisSize: MainAxisSize.min, children: [
              ScaleTransition(scale: _ctrl, child: const FlutterLogo(size: 60)),
              const Text('Scale'),
            ]),
            Column(mainAxisSize: MainAxisSize.min, children: [
              RotationTransition(turns: _ctrl, child: const FlutterLogo(size: 60)),
              const Text('Rotate'),
            ]),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Same visual effects as their "Animated*" counterparts above, but driven manually frame-by-frame by your own controller instead of automatically on property change — gives finer control (e.g., pause, reverse, repeat).

AnimatedList

A ListView that animates item insertion/removal.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: AnimatedListDemo());
}

class AnimatedListDemo extends StatefulWidget {
  const AnimatedListDemo({super.key});
  @override
  State<AnimatedListDemo> createState() => _AnimatedListDemoState();
}

class _AnimatedListDemoState extends State<AnimatedListDemo> {
  final _key = GlobalKey<AnimatedListState>();
  final _items = <String>['Item 0', 'Item 1', 'Item 2'];

  void _add() {
    final index = _items.length;
    _items.add('Item \$index');
    _key.currentState!.insertItem(index);
  }

  void _remove() {
    if (_items.isEmpty) return;
    final index = _items.length - 1;
    final removed = _items.removeAt(index);
    _key.currentState!.removeItem(index,
        (c, anim) => SizeTransition(sizeFactor: anim, child: ListTile(title: Text(removed))));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: AnimatedList(
        key: _key,
        initialItemCount: _items.length,
        itemBuilder: (c, i, anim) => SizeTransition(
          sizeFactor: anim,
          child: ListTile(title: Text(_items[i])),
        ),
      ),
      floatingActionButton: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          FloatingActionButton(heroTag: 'add', onPressed: _add, child: const Icon(Icons.add)),
          const SizedBox(width: 8),
          FloatingActionButton(heroTag: 'remove', onPressed: _remove, child: const Icon(Icons.remove)),
        ],
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: When an item is added/removed, it smoothly grows in from zero height or shrinks away, pushing/pulling neighboring rows rather than popping in/out abruptly.

AnimatedIcon

Morphs between two related icon shapes (e.g., menu ↔ arrow-back).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: AnimatedIconDemo());
}

class AnimatedIconDemo extends StatefulWidget {
  const AnimatedIconDemo({super.key});
  @override
  State<AnimatedIconDemo> createState() => _AnimatedIconDemoState();
}

class _AnimatedIconDemoState extends State<AnimatedIconDemo> with SingleTickerProviderStateMixin {
  late final AnimationController _ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 400));
  bool _isMenu = true;

  void _toggle() {
    setState(() => _isMenu = !_isMenu);
    _isMenu ? _ctrl.reverse() : _ctrl.forward();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: IconButton(
          iconSize: 80,
          icon: AnimatedIcon(icon: AnimatedIcons.menu_arrow, progress: _ctrl),
          onPressed: _toggle,
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A hamburger-menu icon whose three lines smoothly morph into a back-arrow shape as the animation progresses, instead of an instant icon swap.


13. Gestures & Interaction

GestureDetector

Detects taps, drags, long-presses, and other raw pointer gestures on any widget.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: GestureDetectorDemo());
}

class GestureDetectorDemo extends StatefulWidget {
  const GestureDetectorDemo({super.key});
  @override
  State<GestureDetectorDemo> createState() => _GestureDetectorDemoState();
}

class _GestureDetectorDemoState extends State<GestureDetectorDemo> {
  String _last = 'Tap, double-tap, or long-press the box';
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            GestureDetector(
              onTap: () => setState(() => _last = 'Single tap'),
              onDoubleTap: () => setState(() => _last = 'Double tap'),
              onLongPress: () => setState(() => _last = 'Long press'),
              child: Container(width: 160, height: 160, color: Colors.purple,
                child: const Center(child: Text('Tap me', style: TextStyle(color: Colors.white, fontSize: 18)))),
            ),
            const SizedBox(height: 24),
            Text(_last, style: const TextStyle(fontSize: 16)),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Visually just a purple square — the widget itself adds no visual feedback; it's purely a wrapper for detecting touch gestures on whatever's inside it.

InkWell

Like GestureDetector but adds Material's ripple/splash visual feedback on tap.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Material(
            color: Colors.transparent,
            child: InkWell(
              onTap: () {},
              splashColor: Colors.blue.withOpacity(0.4),
              borderRadius: BorderRadius.circular(12),
              child: Ink(
                decoration: BoxDecoration(
                  color: Colors.blue.shade50,
                  borderRadius: BorderRadius.circular(12),
                  border: Border.all(color: Colors.blue),
                ),
                child: const Padding(
                  padding: EdgeInsets.symmetric(horizontal: 32, vertical: 16),
                  child: Text('Tap for ripple', style: TextStyle(fontSize: 18)),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: "Tap me" text with a circular ripple of color spreading out from the tap point and fading away, confined to this widget's bounds.

Draggable / DragTarget

Lets a widget be dragged, and defines drop zones that respond when something is dragged onto them.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: DragDropDemo());
}

class DragDropDemo extends StatefulWidget {
  const DragDropDemo({super.key});
  @override
  State<DragDropDemo> createState() => _DragDropDemoState();
}

class _DragDropDemoState extends State<DragDropDemo> {
  Color _targetColor = Colors.grey.shade200;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          const Center(
            child: Draggable<Color>(
              data: Colors.blue,
              feedback: Icon(Icons.circle, size: 60, color: Colors.blue),
              childWhenDragging: Icon(Icons.circle_outlined, size: 60, color: Colors.blue),
              child: Icon(Icons.circle, size: 60, color: Colors.blue),
            ),
          ),
          Center(
            child: DragTarget<Color>(
              onAccept: (c) => setState(() => _targetColor = c),
              builder: (c, candidates, rejected) => AnimatedContainer(
                duration: const Duration(milliseconds: 200),
                width: 140, height: 140,
                color: candidates.isNotEmpty ? Colors.blue.shade100 : _targetColor,
                child: const Center(child: Text('Drop here')),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: An outlined circle icon that, when picked up and dragged, shows a solid circle "ghost" following your finger/cursor, leaving the original in place until dropped.

Dismissible

A widget that can be swiped away (e.g., to delete a list item).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: DismissibleDemo());
}

class DismissibleDemo extends StatefulWidget {
  const DismissibleDemo({super.key});
  @override
  State<DismissibleDemo> createState() => _DismissibleDemoState();
}

class _DismissibleDemoState extends State<DismissibleDemo> {
  final _items = List.generate(8, (i) => 'Item \$i');
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Swipe to dismiss')),
      body: ListView.builder(
        itemCount: _items.length,
        itemBuilder: (c, i) => Dismissible(
          key: Key(_items[i]),
          background: Container(color: Colors.red, alignment: Alignment.centerLeft,
              padding: const EdgeInsets.only(left: 16), child: const Icon(Icons.delete, color: Colors.white)),
          secondaryBackground: Container(color: Colors.red, alignment: Alignment.centerRight,
              padding: const EdgeInsets.only(right: 16), child: const Icon(Icons.delete, color: Colors.white)),
          onDismissed: (_) => setState(() => _items.removeAt(i)),
          child: ListTile(title: Text(_items[i])),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A list row that slides fully off-screen when swiped left or right, revealing a colored (often red) background underneath it as it moves, then collapses to zero height.

InteractiveViewer

Adds pan/zoom/pinch gesture support to its child (e.g., for images/maps).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Pinch to zoom')),
        body: InteractiveViewer(
          minScale: 0.5,
          maxScale: 5,
          child: Image.network('https://picsum.photos/800/600', fit: BoxFit.contain),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A normal photo that you can pinch to zoom into and drag to pan around, up to 4x magnification, with the content following your fingers smoothly.


14. Painting & Custom Drawing

CustomPaint

Draws arbitrary vector graphics via a custom CustomPainter (canvas API).

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

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: CustomPaint(
            size: const Size(200, 200),
            painter: StarPainter(),
          ),
        ),
      ),
    );
  }
}

class StarPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.amber..style = PaintingStyle.fill;
    final cx = size.width / 2, cy = size.height / 2;
    final path = Path();
    for (var i = 0; i < 10; i++) {
      final r = i.isEven ? 80.0 : 36.0;
      final a = (i * pi / 5) - pi / 2;
      i == 0 ? path.moveTo(cx + r * cos(a), cy + r * sin(a))
              : path.lineTo(cx + r * cos(a), cy + r * sin(a));
    }
    path.close();
    canvas.drawPath(path, paint);
  }
  @override
  bool shouldRepaint(covariant CustomPainter old) => false;
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Entirely dependent on the painter's code — commonly a hand-drawn circle, chart, or custom shape rendered pixel-precisely, not achievable with stock widgets.

ClipRect / ClipRRect / ClipOval / ClipPath

Clip a child to a rectangle, rounded rectangle, oval, or arbitrary path shape.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Wrap(
            spacing: 16, runSpacing: 16,
            alignment: WrapAlignment.center,
            children: [
              Column(mainAxisSize: MainAxisSize.min, children: [
                ClipRect(child: SizedBox(width: 80, height: 80, child: Image.network('https://picsum.photos/200', fit: BoxFit.cover))),
                const Text('ClipRect'),
              ]),
              Column(mainAxisSize: MainAxisSize.min, children: [
                ClipRRect(borderRadius: BorderRadius.circular(20),
                  child: Image.network('https://picsum.photos/200', width: 80, height: 80, fit: BoxFit.cover)),
                const Text('ClipRRect'),
              ]),
              Column(mainAxisSize: MainAxisSize.min, children: [
                ClipOval(child: Image.network('https://picsum.photos/200', width: 80, height: 80, fit: BoxFit.cover)),
                const Text('ClipOval'),
              ]),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A square photo with all four corners neatly rounded off, as if cut with a rounded-corner stencil.

BackdropFilter

Applies an image filter (commonly blur) to everything behind a widget.

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

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Stack(
          fit: StackFit.expand,
          children: [
            Image.network('https://picsum.photos/600/800', fit: BoxFit.cover),
            Center(
              child: ClipRRect(
                borderRadius: BorderRadius.circular(16),
                child: BackdropFilter(
                  filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
                  child: Container(
                    width: 200, height: 100,
                    color: Colors.white.withOpacity(0.2),
                    child: const Center(child: Text('Frosted glass', style: TextStyle(color: Colors.white, fontSize: 18))),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A frosted-glass effect — whatever is visually behind this widget appears softly blurred, with a faint white tint over it (the classic iOS "frosted panel" look).

ShaderMask

Applies a gradient or shader as a mask over a child.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              ShaderMask(
                shaderCallback: (bounds) => const LinearGradient(
                  colors: [Colors.purple, Colors.blue, Colors.transparent],
                  end: Alignment.bottomCenter,
                ).createShader(bounds),
                child: const FlutterLogo(size: 150),
              ),
              const SizedBox(height: 16),
              ShaderMask(
                shaderCallback: (bounds) => const LinearGradient(
                  colors: [Colors.red, Colors.orange, Colors.yellow],
                ).createShader(bounds),
                blendMode: BlendMode.srcIn,
                child: const Text('Gradient Text', style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Colors.white)),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: The Flutter logo gradually fades from full color/opacity on one side to fully transparent on the other, following the gradient direction.

Opacity

Makes a child partially or fully transparent (non-animated; use AnimatedOpacity for transitions).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Stack(
            alignment: Alignment.center,
            children: [
              Container(width: 200, height: 200, color: Colors.blue),
              Opacity(
                opacity: 0.5,
                child: Container(width: 200, height: 200, color: Colors.red),
              ),
              const Text('50% opacity', style: TextStyle(color: Colors.white, fontSize: 16)),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A green square rendered at half strength — the background shows through it faintly.

Transform

Applies a matrix transform (rotate, scale, translate, skew) to a child.

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

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Wrap(
            spacing: 16, runSpacing: 16,
            alignment: WrapAlignment.center,
            children: [
              Column(mainAxisSize: MainAxisSize.min, children: [
                Transform.rotate(angle: pi / 6, child: Container(width: 80, height: 80, color: Colors.orange)),
                const Text('Rotate'),
              ]),
              Column(mainAxisSize: MainAxisSize.min, children: [
                Transform.scale(scale: 1.5, child: Container(width: 60, height: 60, color: Colors.blue)),
                const SizedBox(height: 16),
                const Text('Scale'),
              ]),
              Column(mainAxisSize: MainAxisSize.min, children: [
                Transform.translate(offset: const Offset(20, 0), child: Container(width: 80, height: 80, color: Colors.green)),
                const Text('Translate'),
              ]),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: An orange square tilted slightly clockwise, as if rotated ~11 degrees, without affecting the layout space it occupies.

FittedBox

Scales/positions a child to fit within available space (like BoxFit for arbitrary widgets).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Container(
                width: 150, height: 60,
                color: Colors.amber.shade100,
                child: const FittedBox(
                  fit: BoxFit.contain,
                  child: Text('BIG TEXT', style: TextStyle(fontSize: 80)),
                ),
              ),
              const SizedBox(height: 8),
              const Text('FittedBox: text shrinks to fit'),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Enormous 100pt text shrunk down proportionally to fit neatly inside whatever smaller box contains it, rather than overflowing or clipping.


15. Async & State Builders

FutureBuilder

Rebuilds based on the state of a Future (loading / data / error).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

Future<String> fetchData() => Future.delayed(
  const Duration(seconds: 2),
  () => 'Hello from the future!',
);

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: FutureBuilder<String>(
            future: fetchData(),
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return const Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [CircularProgressIndicator(), SizedBox(height: 16), Text('Loading...')],
                );
              }
              if (snapshot.hasError) return Text('Error: \${snapshot.error}');
              return Text(snapshot.data!, style: const TextStyle(fontSize: 24));
            },
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A spinning loading indicator that's replaced by the actual text content the moment the underlying async call completes.

StreamBuilder

Like FutureBuilder, but rebuilds on every new event from a Stream.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

Stream<int> counterStream = Stream.periodic(const Duration(seconds: 1), (i) => i).take(60);

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: StreamBuilder<int>(
            stream: counterStream,
            builder: (context, snapshot) {
              if (!snapshot.hasData) return const CircularProgressIndicator();
              return Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text('\${snapshot.data}', style: const TextStyle(fontSize: 80, fontWeight: FontWeight.bold)),
                  const Text('seconds elapsed', style: TextStyle(fontSize: 16)),
                ],
              );
            },
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A number on screen that updates live, ticking up each time the underlying stream emits a new value — no manual refresh needed.

ValueListenableBuilder

Rebuilds only the part of the tree that depends on a ValueNotifier, for lightweight local state.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

final counter = ValueNotifier<int>(0);

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              ValueListenableBuilder<int>(
                valueListenable: counter,
                builder: (c, value, child) => Text('\$value',
                    style: const TextStyle(fontSize: 80, fontWeight: FontWeight.bold)),
              ),
              const SizedBox(height: 16),
              ElevatedButton(onPressed: () => counter.value++, child: const Text('Increment')),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Visually just a number that updates instantly whenever the underlying notifier's value changes, without rebuilding surrounding widgets.

Builder

A widget that gives you a fresh BuildContext inline, useful for accessing an ancestor Theme/Scaffold from deeper in the tree without extracting a new widget class.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(colorSchemeSeed: Colors.indigo),
      home: Scaffold(
        body: Center(
          child: Builder(
            builder: (context) {
              final color = Theme.of(context).colorScheme.primary;
              return Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Container(width: 120, height: 120, color: color),
                  const SizedBox(height: 8),
                  Text('Theme primary: \$color'),
                  const SizedBox(height: 16),
                  Builder(
                    builder: (ctx) => ElevatedButton(
                      onPressed: () => ScaffoldMessenger.of(ctx).showSnackBar(const SnackBar(content: Text('Builder provides context!'))),
                      child: const Text('Show Snackbar'),
                    ),
                  ),
                ],
              );
            },
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: No visual identity of its own — it renders exactly whatever its child produces, purely providing context plumbing.


16. Theming, Media & App Structure

MaterialApp / CupertinoApp

The root widget setting up theming, navigation, localization, and Material/Cupertino conventions for the whole app.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'My App',
      theme: ThemeData(colorSchemeSeed: Colors.teal, useMaterial3: true),
      darkTheme: ThemeData.dark(useMaterial3: true),
      themeMode: ThemeMode.system,
      home: Scaffold(
        appBar: AppBar(title: const Text('MaterialApp demo')),
        body: const Center(
          child: Text('MaterialApp sets up theme, navigation,\nand Material conventions for the whole app.',
            textAlign: TextAlign.center),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Not visible itself, but it determines the color palette, default fonts, and platform-appropriate transition/scroll behaviors used everywhere else in the app.

Theme

Provides Material design tokens (colors, text styles) down the widget tree; can override locally.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Column(
          children: [
            const Padding(
              padding: EdgeInsets.all(16),
              child: Card(child: Padding(padding: EdgeInsets.all(16), child: Text('Light-themed card'))),
            ),
            Theme(
              data: ThemeData.dark(),
              child: Builder(
                builder: (context) => Padding(
                  padding: const EdgeInsets.all(16),
                  child: Card(
                    child: const Padding(
                      padding: EdgeInsets.all(16),
                      child: Text('Dark-themed card (Theme override)'),
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A card and text rendered in dark-mode colors (dark background, light text) even if the rest of the app around it is light-themed.

MediaQuery

Exposes device/screen info (size, orientation, padding) to descendants.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Builder(
          builder: (context) {
            final mq = MediaQuery.of(context);
            return Center(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('Screen size: \${mq.size.width.toInt()} × \${mq.size.height.toInt()}'),
                  Text('Device pixel ratio: \${mq.devicePixelRatio}'),
                  Text('Text scale factor: \${mq.textScaleFactor}'),
                  Text('Platform brightness: \${mq.platformBrightness.name}'),
                  Text('Top padding (status bar): \${mq.padding.top}'),
                  Text('Bottom padding (home bar): \${mq.padding.bottom}'),
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Not visible itself — used to make layout decisions (e.g., "show 2 columns if width > 600") that then manifest visually elsewhere.

SafeArea

Insets its child to avoid system UI intrusions (notches, status bar, home indicator).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Stack(
          children: [
            Container(color: Colors.red),
            SafeArea(
              child: Container(
                color: Colors.white,
                child: const Center(
                  child: Text(
                    'This content is inside SafeArea\n(protected from notch & system UI)',
                    textAlign: TextAlign.center,
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Content that starts a bit further down/inward from the very edge of the screen, avoiding being obscured by a phone's notch, camera cutout, or gesture bar.

Directionality

Sets text/layout direction (LTR or RTL) for its subtree.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Column(
          children: [
            const Padding(
              padding: EdgeInsets.all(16),
              child: Text('LTR (default): Left to Right', style: TextStyle(fontSize: 16)),
            ),
            const Padding(padding: EdgeInsets.all(8), child: Row(children: [Text('A'), SizedBox(width: 8), Text('B'), SizedBox(width: 8), Text('C')])),
            const Divider(),
            Directionality(
              textDirection: TextDirection.rtl,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: const [
                  Padding(padding: EdgeInsets.all(16), child: Text('RTL: Right to Left (e.g. Arabic/Hebrew)', style: TextStyle(fontSize: 16))),
                  Padding(padding: EdgeInsets.all(8), child: Row(children: [Text('A'), SizedBox(width: 8), Text('B'), SizedBox(width: 8), Text('C')])),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: The Row's children lay out mirrored — starting from the right edge instead of the left — matching languages like Hebrew or Arabic.

DefaultTextStyle

Sets a fallback text style for all Text descendants that don't specify their own.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: DefaultTextStyle(
            style: const TextStyle(fontSize: 20, color: Colors.indigo, fontStyle: FontStyle.italic),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: const [
                Text('Inherits the default style'),
                Text('This too — italic indigo 20pt'),
                Text('But this is overridden', style: TextStyle(fontSize: 14, color: Colors.red, fontStyle: FontStyle.normal)),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Grey, 20pt text — even though no style was set directly on the Text widget itself.


17. Cupertino (iOS-style) Widgets

CupertinoPageScaffold

The iOS-style equivalent of Scaffold.

import 'package:flutter/cupertino.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      home: CupertinoPageScaffold(
        navigationBar: CupertinoNavigationBar(middle: Text('Settings')),
        child: Center(child: Text('iOS-style page content')),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A plain white/light background page with a translucent, centered-title nav bar at the top — flatter and less "elevated" than Material's Scaffold.

CupertinoNavigationBar

The iOS-style top bar with a centered title and often a "< Back" leading label.

import 'package:flutter/cupertino.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      home: CupertinoPageScaffold(
        navigationBar: CupertinoNavigationBar(
          leading: const CupertinoButton(padding: EdgeInsets.zero, onPressed: null, child: Text('‹ Home')),
          middle: const Text('Details'),
          trailing: CupertinoButton(padding: EdgeInsets.zero, onPressed: () {}, child: const Text('Edit')),
        ),
        child: const Center(child: Text('iOS navigation bar demo')),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A slim, semi-transparent bar with the title centered and a "‹ Home" text-link back button on the left, instead of Material's icon-based back arrow.

CupertinoButton

An iOS-style tappable button, typically text-only with a fade-on-press effect (no ripple).

import 'package:flutter/cupertino.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      home: CupertinoPageScaffold(
        navigationBar: const CupertinoNavigationBar(middle: Text('CupertinoButton')),
        child: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              CupertinoButton(onPressed: () {}, child: const Text('Text button (fades on press)')),
              const SizedBox(height: 16),
              CupertinoButton.filled(onPressed: () {}, child: const Text('Filled button')),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Blue text that dims/fades briefly when pressed, with no border, background, or Material ripple.

CupertinoSwitch

The iOS-style toggle switch.

import 'package:flutter/cupertino.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const CupertinoApp(home: CupertinoSwitchDemo());
}

class CupertinoSwitchDemo extends StatefulWidget {
  const CupertinoSwitchDemo({super.key});
  @override
  State<CupertinoSwitchDemo> createState() => _CupertinoSwitchDemoState();
}

class _CupertinoSwitchDemoState extends State<CupertinoSwitchDemo> {
  bool _on = true;
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(middle: Text('CupertinoSwitch')),
      child: Center(
        child: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            CupertinoSwitch(value: _on, onChanged: (v) => setState(() => _on = v)),
            const SizedBox(width: 12),
            Text(_on ? 'ON' : 'OFF'),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Nearly identical to Material's Switch but with iOS's signature green-when-on color and slightly different curvature/proportions.

CupertinoSlider

The iOS-style slider.

import 'package:flutter/cupertino.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const CupertinoApp(home: CupertinoSliderDemo());
}

class CupertinoSliderDemo extends StatefulWidget {
  const CupertinoSliderDemo({super.key});
  @override
  State<CupertinoSliderDemo> createState() => _CupertinoSliderDemoState();
}

class _CupertinoSliderDemoState extends State<CupertinoSliderDemo> {
  double _value = 0.5;
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(middle: Text('CupertinoSlider')),
      child: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            CupertinoSlider(value: _value, onChanged: (v) => setState(() => _value = v)),
            Text('\${(_value * 100).round()}%'),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A thin track with a plain white circular thumb — thinner and flatter than Material's Slider.

CupertinoTextField

The iOS-style text input.

import 'package:flutter/cupertino.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      home: CupertinoPageScaffold(
        navigationBar: const CupertinoNavigationBar(middle: Text('CupertinoTextField')),
        child: Center(
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: const [
                CupertinoTextField(placeholder: 'Enter name'),
                SizedBox(height: 12),
                CupertinoTextField(placeholder: 'Enter email', prefix: Padding(padding: EdgeInsets.only(left: 8), child: Text('Email'))),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A light-grey rounded rectangle box with placeholder text inside — no floating label or outline border like Material's TextField.

CupertinoAlertDialog

The iOS-style modal alert.

import 'package:flutter/cupertino.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const CupertinoApp(home: CupertinoAlertDemo());
}

class CupertinoAlertDemo extends StatelessWidget {
  const CupertinoAlertDemo({super.key});
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(middle: Text('CupertinoAlertDialog')),
      child: Center(
        child: CupertinoButton.filled(
          child: const Text('Show Alert'),
          onPressed: () => showCupertinoDialog(
            context: context,
            builder: (ctx) => CupertinoAlertDialog(
              title: const Text('Delete?'),
              content: const Text('This cannot be undone.'),
              actions: [
                CupertinoDialogAction(onPressed: () => Navigator.pop(ctx), child: const Text('Cancel')),
                CupertinoDialogAction(isDestructiveAction: true, onPressed: () => Navigator.pop(ctx), child: const Text('Delete')),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A small, tightly-rounded white card centered on a blurred/dimmed background, with the action buttons stacked as full-width rows at the bottom (destructive ones shown in red) — distinct from Material's left-aligned text buttons.

CupertinoActionSheet

An iOS-style bottom sheet with a stack of action options.

import 'package:flutter/cupertino.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const CupertinoApp(home: CupertinoActionSheetDemo());
}

class CupertinoActionSheetDemo extends StatelessWidget {
  const CupertinoActionSheetDemo({super.key});
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(middle: Text('CupertinoActionSheet')),
      child: Center(
        child: CupertinoButton.filled(
          child: const Text('Show Action Sheet'),
          onPressed: () => showCupertinoModalPopup(
            context: context,
            builder: (ctx) => CupertinoActionSheet(
              title: const Text('Options'),
              message: const Text('Choose an action'),
              actions: [
                CupertinoActionSheetAction(onPressed: () => Navigator.pop(ctx), child: const Text('Share')),
                CupertinoActionSheetAction(onPressed: () => Navigator.pop(ctx), child: const Text('Copy')),
                CupertinoActionSheetAction(isDestructiveAction: true, onPressed: () => Navigator.pop(ctx), child: const Text('Delete')),
              ],
              cancelButton: CupertinoActionSheetAction(onPressed: () => Navigator.pop(ctx), child: const Text('Cancel')),
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A rounded-rectangle panel sliding up from the bottom with stacked full-width option rows, and a visually separated "Cancel" row below it in its own rounded card.

CupertinoPicker

An iOS-style scrollable wheel/spinner picker.

import 'package:flutter/cupertino.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const CupertinoApp(home: CupertinoPickerDemo());
}

class CupertinoPickerDemo extends StatefulWidget {
  const CupertinoPickerDemo({super.key});
  @override
  State<CupertinoPickerDemo> createState() => _CupertinoPickerDemoState();
}

class _CupertinoPickerDemoState extends State<CupertinoPickerDemo> {
  final _options = ['Option A', 'Option B', 'Option C', 'Option D', 'Option E'];
  int _selected = 0;
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(middle: Text('CupertinoPicker')),
      child: Column(
        children: [
          Expanded(
            child: CupertinoPicker(
              itemExtent: 36,
              onSelectedItemChanged: (i) => setState(() => _selected = i),
              children: _options.map((o) => Center(child: Text(o))).toList(),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(16),
            child: Text('Selected: \${_options[_selected]}', style: const TextStyle(fontSize: 18)),
          ),
        ],
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A vertically scrolling wheel of options, where the item centered in the middle row (flanked by fading, curved-looking neighbors above/below) is the selected value — like a slot machine reel.

CupertinoDatePicker

An iOS-style date/time wheel picker.

import 'package:flutter/cupertino.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const CupertinoApp(home: CupertinoDatePickerDemo());
}

class CupertinoDatePickerDemo extends StatefulWidget {
  const CupertinoDatePickerDemo({super.key});
  @override
  State<CupertinoDatePickerDemo> createState() => _CupertinoDatePickerDemoState();
}

class _CupertinoDatePickerDemoState extends State<CupertinoDatePickerDemo> {
  DateTime _date = DateTime.now();
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(middle: Text('CupertinoDatePicker')),
      child: Column(
        children: [
          Expanded(
            child: CupertinoDatePicker(
              mode: CupertinoDatePickerMode.date,
              initialDateTime: _date,
              onDateTimeChanged: (dt) => setState(() => _date = dt),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(16),
            child: Text('Selected: \${_date.toLocal()}'.split(' ')[0], style: const TextStyle(fontSize: 18)),
          ),
        ],
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Three side-by-side spinning wheels (month/day/year), each behaving like CupertinoPicker, letting you dial in a date.

CupertinoActivityIndicator

The iOS-style loading spinner.

import 'package:flutter/cupertino.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      home: CupertinoPageScaffold(
        navigationBar: const CupertinoNavigationBar(middle: Text('CupertinoActivityIndicator')),
        child: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: const [
              CupertinoActivityIndicator(),
              SizedBox(height: 16),
              CupertinoActivityIndicator(radius: 20),
              SizedBox(height: 16),
              CupertinoActivityIndicator(radius: 32),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A circular ring of thin grey radiating tick marks that fade in sequence, spinning — distinctly different from Material's solid circular arc spinner.

CupertinoSlidingSegmentedControl

The iOS-style segmented control with a sliding highlight.

import 'package:flutter/cupertino.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const CupertinoApp(home: SegmentedControlDemo());
}

class SegmentedControlDemo extends StatefulWidget {
  const SegmentedControlDemo({super.key});
  @override
  State<SegmentedControlDemo> createState() => _SegmentedControlDemoState();
}

class _SegmentedControlDemoState extends State<SegmentedControlDemo> {
  int _selected = 0;
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(middle: Text('Segmented Control')),
      child: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            CupertinoSlidingSegmentedControl<int>(
              groupValue: _selected,
              children: const {0: Text('Day'), 1: Text('Week'), 2: Text('Month')},
              onValueChanged: (v) => setState(() => _selected = v!),
            ),
            const SizedBox(height: 24),
            Text(['Day view', 'Week view', 'Month view'][_selected], style: const TextStyle(fontSize: 20)),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A rounded grey capsule containing "Day" and "Week" labels, with a white rounded highlight that slides smoothly to sit behind whichever option is selected.

CupertinoTabScaffold / CupertinoTabBar

The iOS-style tabbed app structure with a bottom tab bar.

import 'package:flutter/cupertino.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      home: CupertinoTabScaffold(
        tabBar: CupertinoTabBar(
          items: const [
            BottomNavigationBarItem(icon: Icon(CupertinoIcons.home), label: 'Home'),
            BottomNavigationBarItem(icon: Icon(CupertinoIcons.search), label: 'Search'),
            BottomNavigationBarItem(icon: Icon(CupertinoIcons.person), label: 'Profile'),
          ],
        ),
        tabBuilder: (context, i) => CupertinoPageScaffold(
          navigationBar: CupertinoNavigationBar(middle: Text(['Home', 'Search', 'Profile'][i])),
          child: Center(child: Text(['Home', 'Search', 'Profile'][i], style: const TextStyle(fontSize: 24))),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Visually similar to BottomNavigationBar, but using iOS-style outlined icons and translucent white bar background, with instant tab switches (no page-slide transition).

CupertinoListTile

The iOS-style list row, typically used with CupertinoListSection.

import 'package:flutter/cupertino.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      home: CupertinoPageScaffold(
        navigationBar: const CupertinoNavigationBar(middle: Text('Settings')),
        child: CupertinoListSection(
          header: const Text('General'),
          children: const [
            CupertinoListTile(
              title: Text('Notifications'),
              leading: Icon(CupertinoIcons.bell),
              trailing: CupertinoListTileChevron(),
            ),
            CupertinoListTile(
              title: Text('Privacy'),
              leading: Icon(CupertinoIcons.lock),
              trailing: CupertinoListTileChevron(),
            ),
            CupertinoListTile(
              title: Text('Display'),
              leading: Icon(CupertinoIcons.sun_max),
              trailing: CupertinoListTileChevron(),
            ),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A row with the label on the left and a small grey "›" chevron on the right — flatter and without the ripple/highlight feedback of Material's ListTile.

CupertinoContextMenu

A long-press-triggered preview and action menu (like iOS's 3D-touch/long-press menus).

import 'package:flutter/cupertino.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      home: CupertinoPageScaffold(
        navigationBar: const CupertinoNavigationBar(middle: Text('Long-press the logo')),
        child: Center(
          child: CupertinoContextMenu(
            actions: [
              CupertinoContextMenuAction(
                trailingIcon: CupertinoIcons.doc_on_clipboard,
                onPressed: () => Navigator.pop(context),
                child: const Text('Copy'),
              ),
              CupertinoContextMenuAction(
                trailingIcon: CupertinoIcons.share,
                onPressed: () => Navigator.pop(context),
                child: const Text('Share'),
              ),
            ],
            child: const FlutterLogo(size: 100),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Long-pressing the logo blurs the background, enlarges the logo slightly with a subtle bounce, and pops up a small menu of actions ("Copy") right beside it.


What's Covered Now

Between Part 1 (Core, ~50 widgets) and Part 2 (this doc, ~75 more), this guide now spans roughly 120+ widgets across layout, input, Material, Cupertino, animation, gestures, painting, async, and app-structure categories — effectively the full practical Flutter widget catalog you'll encounter in real projects (excluding deep internals like render objects and low-level painting primitives, which aren't "widgets" you use directly).

If you want any specific widget from Part 2 expanded to the fuller Part-1-style writeup (more detail, bigger snippet), just point to it and I'll flesh it out.


Continues flutter_core_widgets_guide.md (Part 1) and flutter_all_widgets_guide_part2.md (Part 2). Confirming: yes, everything you listed — ExpansionTile, Chip variants, DataTable, Stepper, Tooltip, RefreshIndicator, NavigationRail, SegmentedButton, Autocomplete, and the Cupertino set — is already in Part 2. This Part 3 covers what's still missing: menus/toggles, dialogs/sheets extras, advanced scrolling, visibility & pointer control, focus/keyboard, accessibility, more animation primitives, and low-level layout/painting widgets.


18. Menus & Toggle Controls

FilledButton / FilledButton.tonal (Material 3)

The M3 filled button styles, sitting between ElevatedButton (more shadow) and TextButton (no fill).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              FilledButton(onPressed: () {}, child: const Text('Confirm')),
              const SizedBox(height: 16),
              FilledButton.tonal(onPressed: () {}, child: const Text('Maybe later')),
              const SizedBox(height: 16),
              FilledButton.icon(
                onPressed: () {},
                icon: const Icon(Icons.send),
                label: const Text('Send'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A solid-colored rounded rectangle, flatter (no shadow) than ElevatedButton. The .tonal variant uses a softer, muted background/text color pairing instead of the vivid primary color.

ToggleButtons

A row of buttons where one or more can be toggled on/off, sharing a connected border.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: ToggleButtonsDemo());
}

class ToggleButtonsDemo extends StatefulWidget {
  const ToggleButtonsDemo({super.key});
  @override
  State<ToggleButtonsDemo> createState() => _ToggleButtonsDemoState();
}

class _ToggleButtonsDemoState extends State<ToggleButtonsDemo> {
  List<bool> _selected = [true, false, false];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            ToggleButtons(
              isSelected: _selected,
              onPressed: (i) => setState(() {
                _selected = List.generate(_selected.length, (j) => j == i);
              }),
              children: const [
                Icon(Icons.format_align_left),
                Icon(Icons.format_align_center),
                Icon(Icons.format_align_right),
              ],
            ),
            const SizedBox(height: 24),
            ToggleButtons(
              isSelected: [_selected[0], _selected[1], _selected[2]],
              onPressed: (i) => setState(() => _selected[i] = !_selected[i]),
              children: const [
                Icon(Icons.format_bold),
                Icon(Icons.format_italic),
                Icon(Icons.format_underline),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Three icon buttons joined edge-to-edge inside one outlined pill, where selected ones show a filled/tinted background and the rest stay plain.

DropdownMenu (Material 3)

The M3 replacement for DropdownButton, combining a text field look with a dropdown menu.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => MaterialApp(
    theme: ThemeData(useMaterial3: true),
    home: const DropdownMenuDemo(),
  );
}

class DropdownMenuDemo extends StatefulWidget {
  const DropdownMenuDemo({super.key});
  @override
  State<DropdownMenuDemo> createState() => _DropdownMenuDemoState();
}

class _DropdownMenuDemoState extends State<DropdownMenuDemo> {
  String? _selected;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            DropdownMenu<String>(
              label: const Text('Colour'),
              onSelected: (v) => setState(() => _selected = v),
              dropdownMenuEntries: const [
                DropdownMenuEntry(value: 'red', label: 'Red'),
                DropdownMenuEntry(value: 'green', label: 'Green'),
                DropdownMenuEntry(value: 'blue', label: 'Blue'),
              ],
            ),
            const SizedBox(height: 16),
            Text(_selected == null ? 'Nothing selected' : 'Selected: \$_selected'),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: An outlined text-field-style box with a trailing dropdown arrow; tapping it opens a card-style menu list beneath, and selected text also becomes editable/filterable like a search box.

MenuAnchor / MenuBar / MenuItemButton / SubmenuButton

Desktop-style menu bar and anchored context/dropdown menus (File/Edit/View-style menus, right-click menus).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => MaterialApp(
    theme: ThemeData(useMaterial3: true),
    home: const MenuAnchorDemo(),
  );
}

class MenuAnchorDemo extends StatefulWidget {
  const MenuAnchorDemo({super.key});
  @override
  State<MenuAnchorDemo> createState() => _MenuAnchorDemoState();
}

class _MenuAnchorDemoState extends State<MenuAnchorDemo> {
  String _last = 'Tap the menu button';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('MenuAnchor'),
        actions: [
          MenuAnchor(
            menuChildren: [
              MenuItemButton(
                leadingIcon: const Icon(Icons.content_copy),
                child: const Text('Copy'),
                onPressed: () => setState(() => _last = 'Copy tapped'),
              ),
              MenuItemButton(
                leadingIcon: const Icon(Icons.content_paste),
                child: const Text('Paste'),
                onPressed: () => setState(() => _last = 'Paste tapped'),
              ),
              SubmenuButton(
                child: const Text('More'),
                menuChildren: [
                  MenuItemButton(child: const Text('Select all'),
                      onPressed: () => setState(() => _last = 'Select all')),
                  MenuItemButton(child: const Text('Find'),
                      onPressed: () => setState(() => _last = 'Find')),
                ],
              ),
            ],
            builder: (c, controller, child) => IconButton(
              icon: const Icon(Icons.more_vert),
              onPressed: () => controller.isOpen ? controller.close() : controller.open(),
            ),
          ),
        ],
      ),
      body: Center(child: Text(_last, style: const TextStyle(fontSize: 18))),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A three-dot icon that, when clicked, opens a small rectangular white menu card listing "Copy" and "Paste" as rows directly beneath/beside it — the desktop equivalent of PopupMenuButton, with support for full nested submenus that flyout sideways.

ButtonBar

A horizontal row of buttons that lays out with standard spacing, typically for dialog/card actions.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Card(
            margin: const EdgeInsets.all(24),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                const ListTile(
                  title: Text('Confirm action'),
                  subtitle: Text('Are you sure you want to proceed?'),
                ),
                const Divider(),
                ButtonBar(
                  children: [
                    TextButton(onPressed: () {}, child: const Text('Cancel')),
                    TextButton(onPressed: () {}, child: const Text('OK')),
                  ],
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: "Cancel" and "OK" buttons aligned to the right edge of their container with consistent spacing between them — the standard bottom row of a dialog.


19. Dialogs & Sheets Extras

SimpleDialog / SimpleDialogOption

A lightweight dialog listing several selectable options, no separate content/action split.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: SimpleDialogDemo());
}

class SimpleDialogDemo extends StatefulWidget {
  const SimpleDialogDemo({super.key});
  @override
  State<SimpleDialogDemo> createState() => _SimpleDialogDemoState();
}

class _SimpleDialogDemoState extends State<SimpleDialogDemo> {
  String _picked = 'Nothing selected';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Text(_picked, style: const TextStyle(fontSize: 18)),
            const SizedBox(height: 24),
            ElevatedButton(
              child: const Text('Choose a colour'),
              onPressed: () async {
                final result = await showDialog<String>(
                  context: context,
                  builder: (_) => SimpleDialog(
                    title: const Text('Choose one'),
                    children: [
                      SimpleDialogOption(
                          onPressed: () => Navigator.pop(context, 'Red'),
                          child: const Text('Red')),
                      SimpleDialogOption(
                          onPressed: () => Navigator.pop(context, 'Green'),
                          child: const Text('Green')),
                      SimpleDialogOption(
                          onPressed: () => Navigator.pop(context, 'Blue'),
                          child: const Text('Blue')),
                    ],
                  ),
                );
                if (result != null) setState(() => _picked = result);
              },
            ),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A small centered white card with a bold title and a plain vertical list of tappable text rows beneath it — simpler than AlertDialog, with no explicit action buttons since each row IS the action.

AboutDialog

A pre-built "About this app" dialog showing name, version, icon, and legal text.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: AboutDialogDemo());
}

class AboutDialogDemo extends StatelessWidget {
  const AboutDialogDemo({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          child: const Text('Show About Dialog'),
          onPressed: () => showDialog(
            context: context,
            builder: (_) => AboutDialog(
              applicationName: 'My Flutter App',
              applicationVersion: '1.0.0',
              applicationIcon: const FlutterLogo(size: 48),
              children: const [
                Text('\n© 2026 My Company. All rights reserved.'),
                SizedBox(height: 8),
                Text('A demo app showing the AboutDialog widget.'),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A centered dialog with the app's icon at the top, bold app name and version beneath it, then any extra text/legal content, plus a "View licenses" link at the bottom.

DraggableScrollableSheet

A bottom sheet that can be dragged to resize between a min/initial/max height fraction of the screen.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: DraggableSheetDemo());
}

class DraggableSheetDemo extends StatelessWidget {
  const DraggableSheetDemo({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: const Center(child: Text('Tap the button to open the draggable sheet')),
      floatingActionButton: FloatingActionButton.extended(
        label: const Text('Open Sheet'),
        icon: const Icon(Icons.expand_less),
        onPressed: () => showModalBottomSheet(
          context: context,
          isScrollControlled: true,
          builder: (_) => DraggableScrollableSheet(
            expand: false,
            initialChildSize: 0.4,
            minChildSize: 0.2,
            maxChildSize: 0.9,
            builder: (c, scrollController) => Column(
              children: [
                Container(
                  width: 40, height: 4,
                  margin: const EdgeInsets.symmetric(vertical: 8),
                  decoration: BoxDecoration(color: Colors.grey[400], borderRadius: BorderRadius.circular(2)),
                ),
                Expanded(
                  child: ListView.builder(
                    controller: scrollController,
                    itemCount: 20,
                    itemBuilder: (c, i) => ListTile(title: Text('Item \$i')),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A panel covering half the screen with a small grab-handle bar at its top edge; dragging that handle up/down resizes the panel between a quarter and nearly full screen height, its content scrolling once it's maxed out.

showBottomSheet (persistent)

Shows a non-modal bottom sheet that stays alongside interactive page content (as opposed to the modal showModalBottomSheet).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: PersistentSheetDemo());
}

class PersistentSheetDemo extends StatefulWidget {
  const PersistentSheetDemo({super.key});
  @override
  State<PersistentSheetDemo> createState() => _PersistentSheetDemoState();
}

class _PersistentSheetDemoState extends State<PersistentSheetDemo> {
  PersistentBottomSheetController? _sheet;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Persistent Bottom Sheet')),
      body: Center(
        child: Builder(
          builder: (ctx) => ElevatedButton(
            child: Text(_sheet != null ? 'Close sheet' : 'Open persistent sheet'),
            onPressed: () {
              if (_sheet != null) {
                _sheet!.close();
                setState(() => _sheet = null);
              } else {
                final s = Scaffold.of(ctx).showBottomSheet(
                  (c) => Container(
                    height: 140,
                    color: Colors.blue.shade50,
                    child: const Center(child: Text('Persistent sheet — page still interactive above')),
                  ),
                );
                setState(() => _sheet = s);
                s.closed.then((_) => setState(() => _sheet = null));
              }
            },
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A panel sliding up from the bottom, similar to the modal version, but the rest of the screen stays fully interactive and undimmed behind it.


20. Advanced Scrolling

ReorderableListView

A ListView whose items can be long-pressed and dragged to reorder.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: ReorderableDemo());
}

class ReorderableDemo extends StatefulWidget {
  const ReorderableDemo({super.key});
  @override
  State<ReorderableDemo> createState() => _ReorderableDemoState();
}

class _ReorderableDemoState extends State<ReorderableDemo> {
  final _items = ['Eggs', 'Milk', 'Butter', 'Bread', 'Cheese'];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Long-press to reorder')),
      body: ReorderableListView(
        onReorder: (oldIndex, newIndex) {
          setState(() {
            if (newIndex > oldIndex) newIndex--;
            _items.insert(newIndex, _items.removeAt(oldIndex));
          });
        },
        children: _items
            .map((item) => ListTile(
                  key: ValueKey(item),
                  title: Text(item),
                  trailing: const Icon(Icons.drag_handle),
                ))
            .toList(),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Long-pressing a row lifts it slightly with a shadow, letting you drag it up/down past other rows (which slide out of the way) until you release it into its new position.

NestedScrollView

Coordinates an outer scrollable (e.g., collapsing header) with an inner scrollable (e.g., tab content), so they scroll together correctly.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: NestedScrollView(
          headerSliverBuilder: (c, innerBoxIsScrolled) => [
            SliverAppBar(
              title: const Text('Profile'),
              expandedHeight: 200,
              floating: false,
              pinned: true,
              flexibleSpace: FlexibleSpaceBar(
                background: Container(
                  color: Colors.indigo,
                  child: const Center(
                    child: CircleAvatar(radius: 48, child: Icon(Icons.person, size: 48)),
                  ),
                ),
              ),
            ),
          ],
          body: ListView.builder(
            itemCount: 30,
            itemBuilder: (c, i) => ListTile(title: Text('Post \$i')),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A large header (photo/title) that scrolls away and collapses as you scroll down through the inner list, exactly like scrolling a profile page with a big cover photo up top.

ListWheelScrollView

A vertically scrolling wheel of items with a 3D curved perspective (like CupertinoPicker's engine, usable directly).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: ListWheelDemo());
}

class ListWheelDemo extends StatefulWidget {
  const ListWheelDemo({super.key});
  @override
  State<ListWheelDemo> createState() => _ListWheelDemoState();
}

class _ListWheelDemoState extends State<ListWheelDemo> {
  int _selected = 0;

  @override
  Widget build(BuildContext context) {
    final items = List.generate(20, (i) => 'Item \$i');
    return Scaffold(
      body: Column(
        children: [
          Expanded(
            child: ListWheelScrollView(
              itemExtent: 50,
              perspective: 0.004,
              onSelectedItemChanged: (i) => setState(() => _selected = i),
              children: items
                  .map((s) => Center(
                      child: Text(s, style: const TextStyle(fontSize: 22))))
                  .toList(),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(16),
            child: Text('Selected: \${items[_selected]}',
                style: const TextStyle(fontSize: 18)),
          ),
        ],
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Items appear to curve away in perspective above and below the centered, undistorted middle item — a slot-machine reel effect.

ScrollConfiguration

Overrides default scroll physics/behavior (overscroll glow, scrollbar visibility) for a subtree.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('ScrollConfiguration')),
        body: Row(
          children: [
            Expanded(
              child: Column(
                children: [
                  const Padding(padding: EdgeInsets.all(8), child: Text('Default (with scrollbar)')),
                  Expanded(
                    child: ListView.builder(
                      itemCount: 20,
                      itemBuilder: (c, i) => ListTile(title: Text('Row \$i')),
                    ),
                  ),
                ],
              ),
            ),
            const VerticalDivider(),
            Expanded(
              child: Column(
                children: [
                  const Padding(padding: EdgeInsets.all(8), child: Text('No scrollbar / no overscroll')),
                  Expanded(
                    child: ScrollConfiguration(
                      behavior: const ScrollBehavior().copyWith(
                        scrollbars: false,
                        overscroll: false,
                      ),
                      child: ListView.builder(
                        itemCount: 20,
                        itemBuilder: (c, i) => ListTile(title: Text('Row \$i')),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Not visible itself, but it can remove the blue Android-style "glow" that normally appears when over-scrolling, or hide the scrollbar track that would otherwise be drawn.


21. Visibility & Pointer Control

Visibility

Shows or completely removes a child from layout/painting/hit-testing (cheaper than conditionally building it, but similar visual effect).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: VisibilityDemo());
}

class VisibilityDemo extends StatefulWidget {
  const VisibilityDemo({super.key});
  @override
  State<VisibilityDemo> createState() => _VisibilityDemoState();
}

class _VisibilityDemoState extends State<VisibilityDemo> {
  bool _visible = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Text('Row of boxes — middle one toggled:'),
          const SizedBox(height: 12),
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Container(width: 60, height: 60, color: Colors.blue),
              Visibility(
                visible: _visible,
                child: Container(width: 60, height: 60, margin: const EdgeInsets.symmetric(horizontal: 8), color: Colors.red),
              ),
              Container(width: 60, height: 60, color: Colors.green),
            ],
          ),
          const SizedBox(height: 24),
          ElevatedButton(
            onPressed: () => setState(() => _visible = !_visible),
            child: Text(_visible ? 'Hide (collapses space)' : 'Show'),
          ),
        ],
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A yellow bar that's either fully present or completely gone (collapsing the space it would have used), with no fade transition — an instant on/off toggle.

Offstage

Lays a child out (so its size is known) but doesn't paint or hit-test it.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: OffstageDemo());
}

class OffstageDemo extends StatefulWidget {
  const OffstageDemo({super.key});
  @override
  State<OffstageDemo> createState() => _OffstageDemoState();
}

class _OffstageDemoState extends State<OffstageDemo> {
  bool _offstage = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Text('Below: an Offstage widget'),
          const SizedBox(height: 12),
          Offstage(
            offstage: _offstage,
            child: Container(width: 200, height: 80, color: Colors.orange,
                child: const Center(child: Text('I am Offstage=false now!'))),
          ),
          const SizedBox(height: 24),
          Text('offstage = \$_offstage  → box is \${_offstage ? "invisible (still measured)" : "visible"}'),
          const SizedBox(height: 8),
          ElevatedButton(
            onPressed: () => setState(() => _offstage = !_offstage),
            child: Text(_offstage ? 'Make visible' : 'Put offstage'),
          ),
        ],
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Nothing visible at all — the text is fully invisible and untappable, though Flutter still computes its size internally for other purposes.

AbsorbPointer / IgnorePointer

Block touch/gesture input from reaching a subtree — AbsorbPointer swallows the touch itself, IgnorePointer lets it pass through to whatever's behind.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: PointerBlockDemo());
}

class PointerBlockDemo extends StatefulWidget {
  const PointerBlockDemo({super.key});
  @override
  State<PointerBlockDemo> createState() => _PointerBlockDemoState();
}

class _PointerBlockDemoState extends State<PointerBlockDemo> {
  int _taps = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const Text('AbsorbPointer: button looks enabled but is inert'),
            const SizedBox(height: 8),
            AbsorbPointer(
              child: ElevatedButton(
                onPressed: () => setState(() => _taps++),
                child: const Text('Tap me (absorbed)'),
              ),
            ),
            const SizedBox(height: 24),
            const Text('IgnorePointer: taps pass through to widget below'),
            const SizedBox(height: 8),
            Stack(
              children: [
                ElevatedButton(
                  onPressed: () => setState(() => _taps++),
                  child: const Text('Underlying button (count: \$_taps)'),
                ),
                IgnorePointer(
                  child: Container(width: 200, height: 42, color: Colors.red.withOpacity(0.3),
                      child: const Center(child: Text('Overlay (ignored)'))),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: The button renders normally (full color, not greyed out) but taps on it do nothing — visually identical to an enabled button, functionally inert.

MouseRegion

Detects mouse hover/enter/exit events (desktop/web) and can change the cursor icon.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: MouseRegionDemo());
}

class MouseRegionDemo extends StatefulWidget {
  const MouseRegionDemo({super.key});
  @override
  State<MouseRegionDemo> createState() => _MouseRegionDemoState();
}

class _MouseRegionDemoState extends State<MouseRegionDemo> {
  bool _hovering = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: MouseRegion(
          cursor: SystemMouseCursors.click,
          onEnter: (_) => setState(() => _hovering = true),
          onExit: (_) => setState(() => _hovering = false),
          child: AnimatedContainer(
            duration: const Duration(milliseconds: 200),
            width: 200, height: 80,
            color: _hovering ? Colors.blue : Colors.grey.shade300,
            child: Center(
              child: Text(
                _hovering ? 'Hovering!' : 'Hover over me',
                style: TextStyle(color: _hovering ? Colors.white : Colors.black),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A plain grey box that shows no visible change on its own, but the mouse cursor switches to a pointing hand when hovering over it.

Listener

A lower-level pointer event listener (down/move/up/cancel) beneath GestureDetector, useful for custom gesture handling.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: ListenerDemo());
}

class ListenerDemo extends StatefulWidget {
  const ListenerDemo({super.key});
  @override
  State<ListenerDemo> createState() => _ListenerDemoState();
}

class _ListenerDemoState extends State<ListenerDemo> {
  String _event = 'No pointer event yet';
  Offset _pos = Offset.zero;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Listener(
              onPointerDown: (e) => setState(() { _event = 'DOWN'; _pos = e.localPosition; }),
              onPointerMove: (e) => setState(() { _event = 'MOVE'; _pos = e.localPosition; }),
              onPointerUp: (e) => setState(() { _event = 'UP'; _pos = e.localPosition; }),
              child: Container(
                width: 220, height: 220,
                color: Colors.indigo,
                child: const Center(child: Text('Tap / drag here', style: TextStyle(color: Colors.white))),
              ),
            ),
            const SizedBox(height: 16),
            Text('\$_event @ (\${_pos.dx.toInt()}, \${_pos.dy.toInt()})'),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Just an indigo square — purely a wrapper for raw pointer-event plumbing, with no built-in visual feedback.


22. Focus & Keyboard

Focus / FocusScope

Manages keyboard focus for a widget or a group of widgets (which one currently "has focus" for typing/tabbing).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: FocusDemo());
}

class FocusDemo extends StatefulWidget {
  const FocusDemo({super.key});
  @override
  State<FocusDemo> createState() => _FocusDemoState();
}

class _FocusDemoState extends State<FocusDemo> {
  bool _field1Focused = false;
  bool _field2Focused = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: FocusScope(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Focus(
                onFocusChange: (v) => setState(() => _field1Focused = v),
                child: AnimatedContainer(
                  duration: const Duration(milliseconds: 200),
                  decoration: BoxDecoration(
                    border: Border.all(color: _field1Focused ? Colors.blue : Colors.grey, width: _field1Focused ? 2 : 1),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: const Padding(padding: EdgeInsets.symmetric(horizontal: 8), child: TextField(decoration: InputDecoration(labelText: 'Field 1', border: InputBorder.none))),
                ),
              ),
              const SizedBox(height: 16),
              Focus(
                onFocusChange: (v) => setState(() => _field2Focused = v),
                child: AnimatedContainer(
                  duration: const Duration(milliseconds: 200),
                  decoration: BoxDecoration(
                    border: Border.all(color: _field2Focused ? Colors.blue : Colors.grey, width: _field2Focused ? 2 : 1),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: const Padding(padding: EdgeInsets.symmetric(horizontal: 8), child: TextField(decoration: InputDecoration(labelText: 'Field 2', border: InputBorder.none))),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: No visible difference from the underlying TextField itself — but the app can react to focus changes, e.g., highlighting a border or scrolling a field into view when it gains focus.

FocusTraversalGroup

Controls the order in which Tab/Shift-Tab or D-pad navigation moves between focusable widgets.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Tab between fields')),
        body: Padding(
          padding: const EdgeInsets.all(24),
          child: FocusTraversalGroup(
            policy: WidgetOrderTraversalPolicy(),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: const [
                TextField(decoration: InputDecoration(labelText: 'First name', border: OutlineInputBorder())),
                SizedBox(height: 16),
                TextField(decoration: InputDecoration(labelText: 'Last name', border: OutlineInputBorder())),
                SizedBox(height: 16),
                TextField(decoration: InputDecoration(labelText: 'Email', border: OutlineInputBorder())),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Not visible — but pressing Tab moves the blinking cursor between the two text fields in the exact order you defined, rather than an arbitrary default order.

Shortcuts / Actions / CallbackShortcuts

Bind keyboard key combinations to app-level actions/intents (e.g., Ctrl+S to save).

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

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: ShortcutsDemo());
}

class ShortcutsDemo extends StatefulWidget {
  const ShortcutsDemo({super.key});
  @override
  State<ShortcutsDemo> createState() => _ShortcutsDemoState();
}

class _ShortcutsDemoState extends State<ShortcutsDemo> {
  String _msg = 'Try Ctrl+S or Ctrl+Z';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CallbackShortcuts(
        bindings: {
          const SingleActivator(LogicalKeyboardKey.keyS, control: true): () => setState(() => _msg = 'Ctrl+S pressed — saved!'),
          const SingleActivator(LogicalKeyboardKey.keyZ, control: true): () => setState(() => _msg = 'Ctrl+Z pressed — undone!'),
        },
        child: Focus(
          autofocus: true,
          child: Center(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                const Icon(Icons.keyboard, size: 64),
                const SizedBox(height: 16),
                Text(_msg, style: const TextStyle(fontSize: 18)),
                const SizedBox(height: 8),
                const Text('(click here first, then use keyboard)', style: TextStyle(color: Colors.grey)),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Ordinary text on screen; pressing Ctrl+S triggers your save logic invisibly, with no on-screen indication other than whatever effect the action itself has.

KeyboardListener

Listens for raw physical key press/release events.

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

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: KeyboardListenerDemo());
}

class KeyboardListenerDemo extends StatefulWidget {
  const KeyboardListenerDemo({super.key});
  @override
  State<KeyboardListenerDemo> createState() => _KeyboardListenerDemoState();
}

class _KeyboardListenerDemoState extends State<KeyboardListenerDemo> {
  final _focusNode = FocusNode();
  String _last = 'Press any key (click box first)';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: KeyboardListener(
          focusNode: _focusNode,
          autofocus: true,
          onKeyEvent: (e) {
            if (e is KeyDownEvent) setState(() => _last = 'Key: \${e.logicalKey.keyLabel}');
          },
          child: GestureDetector(
            onTap: () => _focusNode.requestFocus(),
            child: Container(
              width: 280, height: 120,
              color: Colors.grey.shade200,
              child: Center(child: Text(_last, textAlign: TextAlign.center)),
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A plain grey box with no visual feedback of its own — purely a hook for reacting to keystrokes while this widget is focused.


23. Accessibility (Semantics)

Semantics

Attaches accessibility metadata (labels, roles, hints) to a widget for screen readers.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: const [
              Semantics(
                label: 'Submit the form',
                button: true,
                child: Icon(Icons.check_circle, size: 80, color: Colors.green),
              ),
              SizedBox(height: 8),
              Text('Icon above is announced as:\n"Submit the form, button"\nby screen readers',
                  textAlign: TextAlign.center),
              SizedBox(height: 32),
              Semantics(
                label: 'Product price: twenty-nine dollars and ninety-nine cents',
                child: Text('\$29.99', style: TextStyle(fontSize: 36, fontWeight: FontWeight.bold)),
              ),
              Text('Price above is announced in full words', style: TextStyle(color: Colors.grey)),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Visually just a checkmark icon — the difference only shows up in a screen reader, which will announce "Submit the form, button" when focus lands on it.

ExcludeSemantics / MergeSemantics

Hide a subtree from the accessibility tree, or merge several nodes' semantics into a single announced unit.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              const Text('MergeSemantics — announced as one unit:', style: TextStyle(fontWeight: FontWeight.bold)),
              const SizedBox(height: 8),
              MergeSemantics(
                child: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: const [
                    Icon(Icons.star, color: Colors.amber),
                    SizedBox(width: 4),
                    Text('4.5 rating', style: TextStyle(fontSize: 18)),
                  ],
                ),
              ),
              const SizedBox(height: 32),
              const Text('ExcludeSemantics — hidden from a11y tree:', style: TextStyle(fontWeight: FontWeight.bold)),
              const SizedBox(height: 8),
              Row(
                mainAxisSize: MainAxisSize.min,
                children: const [
                  ExcludeSemantics(child: Icon(Icons.info_outline, color: Colors.grey)),
                  SizedBox(width: 8),
                  Text('Decorative icon excluded from screen reader'),
                ],
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A star icon next to "4.5 rating" text — visually unchanged, but a screen reader announces them together as one phrase ("4.5 rating") instead of two separate stops.


24. More Animation Primitives

AnimatedTheme

Smoothly animates a Theme change (e.g., switching light/dark mode) across all themed descendants.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: AnimatedThemeDemo());
}

class AnimatedThemeDemo extends StatefulWidget {
  const AnimatedThemeDemo({super.key});
  @override
  State<AnimatedThemeDemo> createState() => _AnimatedThemeDemoState();
}

class _AnimatedThemeDemoState extends State<AnimatedThemeDemo> {
  bool _dark = false;

  @override
  Widget build(BuildContext context) {
    return AnimatedTheme(
      data: _dark ? ThemeData.dark() : ThemeData.light(),
      duration: const Duration(milliseconds: 500),
      child: Builder(
        builder: (ctx) => Scaffold(
          appBar: AppBar(title: const Text('AnimatedTheme')),
          body: Center(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Card(
                  child: Padding(
                    padding: const EdgeInsets.all(24),
                    child: Text('Theme animates smoothly', style: Theme.of(ctx).textTheme.headlineSmall),
                  ),
                ),
                const SizedBox(height: 24),
                ElevatedButton(
                  onPressed: () => setState(() => _dark = !_dark),
                  child: Text(_dark ? 'Switch to Light' : 'Switch to Dark'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Background and text colors throughout the subtree smoothly cross-fade between light and dark palettes over 400ms, rather than flipping instantly.

AnimatedPhysicalModel

Animates elevation, shape, and color changes with Material's shadow-casting physical model.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: AnimatedPhysicalModelDemo());
}

class AnimatedPhysicalModelDemo extends StatefulWidget {
  const AnimatedPhysicalModelDemo({super.key});
  @override
  State<AnimatedPhysicalModelDemo> createState() => _AnimatedPhysicalModelDemoState();
}

class _AnimatedPhysicalModelDemoState extends State<AnimatedPhysicalModelDemo> {
  bool _raised = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            GestureDetector(
              onTap: () => setState(() => _raised = !_raised),
              child: AnimatedPhysicalModel(
                duration: const Duration(milliseconds: 400),
                curve: Curves.easeInOut,
                shape: BoxShape.rectangle,
                elevation: _raised ? 16 : 2,
                color: _raised ? Colors.indigo : Colors.white,
                shadowColor: Colors.black,
                borderRadius: BorderRadius.circular(12),
                child: SizedBox(
                  width: 140, height: 80,
                  child: Center(
                    child: Text(
                      _raised ? 'Lifted' : 'Tap to lift',
                      style: TextStyle(color: _raised ? Colors.white : Colors.black),
                    ),
                  ),
                ),
              ),
            ),
            const SizedBox(height: 8),
            Text('Elevation: \${_raised ? 16 : 2}'),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A white square whose drop shadow smoothly grows deeper/softer as it "lifts" higher off the page, rather than jumping between shadow depths.

PositionedTransition / AlignTransition / RelativePositionedTransition / DecoratedBoxTransition

Explicit, controller-driven counterparts to AnimatedPositioned/AnimatedAlign/etc. for fine-grained animation control.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: PositionedTransitionDemo());
}

class PositionedTransitionDemo extends StatefulWidget {
  const PositionedTransitionDemo({super.key});
  @override
  State<PositionedTransitionDemo> createState() => _PositionedTransitionDemoState();
}

class _PositionedTransitionDemoState extends State<PositionedTransitionDemo>
    with SingleTickerProviderStateMixin {
  late final AnimationController _ctrl = AnimationController(
    vsync: this,
    duration: const Duration(milliseconds: 800),
  )..repeat(reverse: true);

  late final Animation<RelativeRect> _rect = RelativeRectTween(
    begin: const RelativeRect.fromLTRB(16, 16, 16, 16),
    end: const RelativeRect.fromLTRB(80, 80, 80, 80),
  ).animate(CurvedAnimation(parent: _ctrl, curve: Curves.easeInOut));

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SizedBox(
          width: 260, height: 260,
          child: Stack(
            children: [
              Container(decoration: BoxDecoration(border: Border.all(color: Colors.grey.shade300))),
              PositionedTransition(
                rect: _rect,
                child: const FlutterLogo(),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Same visual result as their "Animated*" siblings (a logo sliding to a new spot), but driven manually by an AnimationController you can pause, reverse, or chain with other animations.


25. Low-Level Layout & Custom Painting

DecoratedBox

Paints a decoration (color, border, gradient, shadow) behind/around a child — what Container uses internally.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              DecoratedBox(
                decoration: BoxDecoration(
                  color: Colors.indigo.shade50,
                  border: Border.all(color: Colors.indigo, width: 2),
                  borderRadius: BorderRadius.circular(12),
                  boxShadow: const [BoxShadow(color: Colors.black26, blurRadius: 8, offset: Offset(2, 4))],
                ),
                child: const Padding(padding: EdgeInsets.all(20), child: Text('DecoratedBox', style: TextStyle(fontSize: 18))),
              ),
              const SizedBox(height: 24),
              DecoratedBox(
                decoration: const BoxDecoration(
                  gradient: LinearGradient(colors: [Colors.purple, Colors.blue]),
                  borderRadius: BorderRadius.all(Radius.circular(8)),
                ),
                child: const Padding(padding: EdgeInsets.symmetric(horizontal: 32, vertical: 12),
                    child: Text('Gradient box', style: TextStyle(color: Colors.white, fontSize: 18))),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A thin black rounded-corner outline around the word "Boxed" with some inner padding — functionally the border/background half of what Container gives you.

LimitedBox

Constrains a child's size only when the parent gives unbounded constraints (e.g., inside a scrolling list).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Column(
          children: [
            const Padding(padding: EdgeInsets.all(8), child: Text('Without LimitedBox — throws layout error in Column:')),
            Padding(
              padding: const EdgeInsets.all(8),
              child: LimitedBox(
                maxHeight: 120,
                child: ListView.builder(
                  itemCount: 20,
                  itemBuilder: (c, i) => ListTile(title: Text('Row \$i')),
                ),
              ),
            ),
            const Padding(padding: EdgeInsets.all(8),
                child: Text('With LimitedBox(maxHeight: 120) — list capped at 120px in Column', style: TextStyle(color: Colors.green))),
          ],
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A list that caps at 100px tall when placed somewhere with no inherent height limit (like inside a Column), rather than throwing a layout error or expanding infinitely.

SizedOverflowBox

Reports one size to its parent for layout purposes, while painting a child of a possibly different actual size.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Container(
                color: Colors.grey.shade200,
                child: SizedOverflowBox(
                  size: const Size(100, 100),
                  alignment: Alignment.center,
                  child: Container(width: 160, height: 160, color: Colors.pink.withOpacity(0.6),
                      child: const Center(child: Text('160px box
claims 100px', textAlign: TextAlign.center, style: TextStyle(color: Colors.white)))),
                ),
              ),
              const SizedBox(height: 8),
              const Text('Grey area = 100×100 claimed size
Pink box = 160×160 actual paint', textAlign: TextAlign.center),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A pink square that visually spills 25px past each edge of the 100×100 space it claims to occupy, since layout math and painted size are deliberately mismatched.

Flow

A highly custom, performance-oriented layout widget where you manually position each child via transform matrices.

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

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: FlowDemo());
}

class FlowDemo extends StatefulWidget {
  const FlowDemo({super.key});
  @override
  State<FlowDemo> createState() => _FlowDemoState();
}

class _FlowDemoState extends State<FlowDemo> with SingleTickerProviderStateMixin {
  late final AnimationController _ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 600));
  bool _open = false;

  void _toggle() {
    setState(() => _open = !_open);
    _open ? _ctrl.forward() : _ctrl.reverse();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: SizedBox(
          width: 200, height: 200,
          child: Flow(
            delegate: FanOutDelegate(_ctrl),
            children: [
              ...[Colors.red, Colors.green, Colors.blue, Colors.orange].map((c) =>
                FloatingActionButton(mini: true, backgroundColor: c, onPressed: _toggle, child: const Icon(Icons.star))),
              FloatingActionButton(onPressed: _toggle, child: Icon(_open ? Icons.close : Icons.add)),
            ],
          ),
        ),
      ),
    );
  }
}

class FanOutDelegate extends FlowDelegate {
  final Animation<double> _anim;
  FanOutDelegate(this._anim) : super(repaint: _anim);

  @override
  void paintChildren(FlowPaintingContext c) {
    final n = c.childCount - 1;
    for (var i = 0; i < n; i++) {
      final progress = _anim.value;
      final angle = (pi / 2) / (n - 1) * i;
      final dist = 80.0 * progress;
      c.paintChild(i, transform: Matrix4.identity()
        ..translate(80 + dist * cos(pi + angle), 80 + dist * sin(pi / 2 + angle)));
    }
    c.paintChild(n, transform: Matrix4.identity()..translate(80.0 - 20, 80.0 - 20));
  }

  @override
  bool shouldRepaint(FanOutDelegate old) => false;
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Entirely dependent on the custom FlowDelegate — commonly used for things like a fan-out menu of icons or a custom carousel where you need precise per-frame control over each child's position/rotation/scale.

CustomMultiChildLayout / CustomSingleChildLayout

Lower-level layout widgets where you implement a LayoutDelegate to size/position children by custom logic, rather than using Row/Column/Stack.

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

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: SizedBox(
            width: 300, height: 300,
            child: CustomMultiChildLayout(
              delegate: DiagonalDelegate(),
              children: [
                LayoutId(id: 0, child: Container(width: 80, height: 80, color: Colors.red)),
                LayoutId(id: 1, child: Container(width: 80, height: 80, color: Colors.green)),
                LayoutId(id: 2, child: Container(width: 80, height: 80, color: Colors.blue)),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class DiagonalDelegate extends MultiChildLayoutDelegate {
  @override
  void performLayout(Size size) {
    const childSize = Size(80, 80);
    final loose = BoxConstraints.loose(childSize);
    for (var i = 0; i < 3; i++) {
      layoutChild(i, loose);
      positionChild(i, Offset(i * 80.0, i * 80.0));
    }
  }
  @override
  bool shouldRelayout(covariant MultiChildLayoutDelegate old) => false;
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Whatever custom arrangement the delegate specifies — used when no built-in layout widget (Row, Stack, Align, etc.) can express the positioning logic you need.

Banner

Draws a diagonal ribbon/label across a corner (e.g., Flutter's own "DEBUG" banner in dev builds).

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Wrap(
            spacing: 16, runSpacing: 16,
            children: [
              Banner(
                message: 'NEW',
                location: BannerLocation.topEnd,
                color: Colors.red,
                child: Card(child: const SizedBox(width: 130, height: 90, child: Center(child: Text('Product A')))),
              ),
              Banner(
                message: 'SALE',
                location: BannerLocation.topStart,
                color: Colors.green,
                child: Card(child: const SizedBox(width: 130, height: 90, child: Center(child: Text('Product B')))),
              ),
              Banner(
                message: 'DEBUG',
                location: BannerLocation.bottomEnd,
                child: Card(child: const SizedBox(width: 130, height: 90, child: Center(child: Text('Product C')))),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A diagonal red ribbon reading "NEW" stretched across the top-right corner of the card, overlapping its edge at a 45° angle.

RepaintBoundary

Isolates a subtree into its own compositing layer so it repaints independently — a performance tool, not a visual one.

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

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: RepaintBoundaryDemo());
}

class RepaintBoundaryDemo extends StatefulWidget {
  const RepaintBoundaryDemo({super.key});
  @override
  State<RepaintBoundaryDemo> createState() => _RepaintBoundaryDemoState();
}

class _RepaintBoundaryDemoState extends State<RepaintBoundaryDemo> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Text('Expensive painting isolated by RepaintBoundary'),
          const SizedBox(height: 16),
          RepaintBoundary(
            child: CustomPaint(
              size: const Size(200, 200),
              painter: CirclePainter(),
            ),
          ),
          const SizedBox(height: 16),
          Text('Tap counter: \$_count  (only counter area repaints, not the circle)'),
          ElevatedButton(
            onPressed: () => setState(() => _count++),
            child: const Text('Increment (does NOT repaint circle above)'),
          ),
        ],
      ),
    );
  }
}

class CirclePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final cx = size.width / 2, cy = size.height / 2;
    for (var i = 1; i <= 5; i++) {
      canvas.drawCircle(Offset(cx, cy), i * 18.0,
          Paint()..color = Colors.primaries[i * 2 % Colors.primaries.length].withOpacity(0.6)..style = PaintingStyle.stroke..strokeWidth = 4);
    }
  }
  @override
  bool shouldRepaint(covariant CustomPainter old) => false;
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: No visual difference at all — its effect is purely on rendering performance, preventing an expensive-to-paint child from forcing repaints of unrelated siblings.

PhysicalModel / PhysicalShape

Clips a child to a shape while also casting a Material-style shadow — the primitive Card and Material build on.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              PhysicalModel(
                color: Colors.white,
                elevation: 8,
                borderRadius: BorderRadius.circular(16),
                child: const SizedBox(width: 160, height: 80,
                    child: Center(child: Text('PhysicalModel', style: TextStyle(fontSize: 16)))),
              ),
              const SizedBox(height: 32),
              PhysicalShape(
                color: Colors.indigo.shade50,
                elevation: 6,
                clipper: const ShapeBorderClipper(shape: StadiumBorder()),
                child: const SizedBox(width: 160, height: 56,
                    child: Center(child: Text('PhysicalShape (stadium)', style: TextStyle(fontSize: 14)))),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A white rounded rectangle with a soft, correctly-angled drop shadow beneath it — visually the same as a plain Card, since that's essentially what Card wraps.


26. Text, Selection & Images (extras)

SelectableText

Like Text, but lets the user long-press/drag to select and copy the text.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: const Center(
          child: Padding(
            padding: EdgeInsets.all(24),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text('Long-press either paragraph to select & copy:', style: TextStyle(fontWeight: FontWeight.bold)),
                SizedBox(height: 16),
                SelectableText(
                  'SelectableText lets users highlight, copy, and share this content — identical to plain Text visually, but interactive.',
                  style: TextStyle(fontSize: 16),
                ),
                SizedBox(height: 16),
                SelectableText(
                  'The quick brown fox jumps over the lazy dog.',
                  style: TextStyle(fontSize: 16, fontStyle: FontStyle.italic),
                  cursorColor: Colors.blue,
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Identical to plain Text until you long-press it — then it shows the standard text-selection handles and a copy/share popup toolbar, just like selecting text in a browser.

Text.rich / WidgetSpan

Lets a block of text embed non-text widgets (like a small icon) inline within the flow of a sentence.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Padding(
            padding: const EdgeInsets.all(24),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: const [
                Text.rich(
                  TextSpan(text: 'Rated ', children: [
                    WidgetSpan(child: Icon(Icons.star, size: 20, color: Colors.amber)),
                    TextSpan(text: ' 4.5 out of 5'),
                  ]),
                  style: TextStyle(fontSize: 20),
                ),
                SizedBox(height: 24),
                Text.rich(
                  TextSpan(
                    text: 'Download the ',
                    children: [
                      WidgetSpan(child: Icon(Icons.android, size: 18, color: Colors.green)),
                      TextSpan(text: ' Android or '),
                      WidgetSpan(child: Icon(Icons.apple, size: 18, color: Colors.grey)),
                      TextSpan(text: ' iOS app today!'),
                    ],
                  ),
                  style: TextStyle(fontSize: 18),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: The sentence "Rated ★ 4.5" where the star is a real inline icon sitting at the same baseline as the surrounding letters, not just an emoji character.

FadeInImage

Shows a placeholder (often a blurred/low-res thumbnail) that fades into the real image once it finishes loading.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

final _transparent = Uint8List.fromList([
  0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,
  0x44,0x52,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x08,0x06,0x00,0x00,
  0x00,0x1f,0x15,0xc4,0x89,0x00,0x00,0x00,0x0b,0x49,0x44,0x41,0x54,0x78,
  0x9c,0x62,0x00,0x01,0x00,0x00,0x05,0x00,0x01,0x0d,0x0a,0x2d,0xb4,0x00,
  0x00,0x00,0x00,0x49,0x45,0x4e,0x44,0xae,0x42,0x60,0x82,
]);

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('FadeInImage')),
        body: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              const Text('Image fades in from transparent placeholder:'),
              const SizedBox(height: 12),
              FadeInImage.memoryNetwork(
                placeholder: _transparent,
                image: 'https://picsum.photos/300/200',
                width: 300, height: 200,
                fit: BoxFit.cover,
                fadeInDuration: const Duration(milliseconds: 800),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A blank/placeholder box that smoothly crossfades into the full photo once it's downloaded, rather than the image just popping in abruptly.


27. Overlay & Root Structure

Overlay / OverlayEntry

A stack of visual layers above the main app content, used for things like dropdown menus, dragged-item previews, and tooltips under the hood.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) => const MaterialApp(home: OverlayDemo());
}

class OverlayDemo extends StatefulWidget {
  const OverlayDemo({super.key});
  @override
  State<OverlayDemo> createState() => _OverlayDemoState();
}

class _OverlayDemoState extends State<OverlayDemo> {
  OverlayEntry? _entry;

  void _showOverlay(BuildContext ctx) {
    _entry = OverlayEntry(
      builder: (c) => Positioned(
        top: 120, right: 24,
        child: Material(
          color: Colors.transparent,
          child: GestureDetector(
            onTap: _removeOverlay,
            child: Container(
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
              decoration: BoxDecoration(
                color: Colors.indigo,
                borderRadius: BorderRadius.circular(8),
                boxShadow: const [BoxShadow(color: Colors.black38, blurRadius: 6)],
              ),
              child: const Text('Floating overlay!\nTap to dismiss',
                  style: TextStyle(color: Colors.white)),
            ),
          ),
        ),
      ),
    );
    Overlay.of(ctx).insert(_entry!);
  }

  void _removeOverlay() {
    _entry?.remove();
    _entry = null;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: ElevatedButton(
          child: const Text('Show Overlay'),
          onPressed: () => _showOverlay(context),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: Text that appears to float above everything else currently on screen, unaffected by scrolling or the layout of the page beneath it — the mechanism many popups/tooltips are built from.

WidgetsApp

The bare, non-Material/non-Cupertino root app widget that MaterialApp and CupertinoApp are themselves built on top of.

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return WidgetsApp(
      color: Colors.white,
      builder: (context, child) => Directionality(
        textDirection: TextDirection.ltr,
        child: Container(
          color: Colors.grey.shade100,
          child: Center(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: const [
                Icon(Icons.widgets, size: 64, color: Colors.indigo),
                SizedBox(height: 16),
                Text(
                  'WidgetsApp — bare app root\nno Material or Cupertino styling',
                  textAlign: TextAlign.center,
                  style: TextStyle(fontSize: 18, color: Colors.black87),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

▶ Open in DartPad

Looks like: A completely unstyled app shell — no default fonts, colors, or platform conventions — just your own builder content, useful for apps that want zero Material/Cupertino styling baked in.


Top comments (0)