Home
Dart
OOP
Flutter
Contact

Flutter Widget Cookbook

A hands-on collection of production-ready UI patterns every Flutter developer should know.

Every Flutter project eventually hits the same wall: you need a search bar, a loading skeleton, a swipeable list, or a responsive sidebar — and the widget catalog alone doesn't hand you the finished recipe. This cookbook bridges that gap. Each section solves a concrete, real-world problem with a complete, copy-paste Dart example you can drop straight into your app.

Think of this page as your personal recipe book. Whether you're building a social feed, an e-commerce dashboard, or a settings screen, the patterns below will save you hours of googling and stack-overflowing. Every recipe includes the full widget tree, key technique explanations, and practical tips to help you adapt the code to your own design system.

How to use this cookbook Each recipe is self-contained. You can copy the entire class into a new file, run it on a device, and see it work instantly. Modify colors, spacing, and data to fit your app's look and feel.

1. Custom AppBar with Search

Problem: You need a visually distinctive top bar that integrates a search field and a notification bell — common in e-commerce, messaging, and content apps. The default AppBar widget is too plain for branded experiences.

custom_appbar.dart
class CustomSearchAppBar extends StatelessWidget {
  const CustomSearchAppBar({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: const BoxDecoration(
        gradient: LinearGradient(
          colors: [Colors.indigo, Colors.purple],
        ),
      ),
      child: AppBar(
        backgroundColor: Colors.transparent,
        elevation: 0,
        title: TextField(
          decoration: InputDecoration(
            hintText: 'Search products...',
            prefixIcon: const Icon(Icons.search, color: Colors.white),
            filled: true,
            fillColor: Colors.white.withOpacity(0.2),
            border: OutlineInputBorder(
              borderRadius: BorderRadius.circular(30),
              borderSide: BorderSide.none,
            ),
            hintStyle: const TextStyle(color: Colors.white70),
          ),
          style: const TextStyle(color: Colors.white),
        ),
        actions: [
          Stack(children: [
            IconButton(
              icon: const Icon(Icons.notifications_outlined),
              onPressed: () {},
            ),
            Positioned(
              right: 8, top: 8,
              child: CircleAvatar(
                radius: 8, backgroundColor: Colors.red,
                child: const Text('3',
                  style: TextStyle(fontSize: 10, color: Colors.white)),
              ),
            ),
          ]),
        ],
      ),
    );
  }
}
Key technique Wrapping AppBar in a Container with backgroundColor: Colors.transparent lets the gradient show through. The Stack + Positioned pattern for the badge is reusable for any icon that needs a count indicator.

2. Avatar & Profile Card

Problem: Social apps, team dashboards, and chat interfaces all need a polished profile card with an avatar, online status indicator, user info, and action buttons. This pattern combines multiple widgets into one reusable component.

profile_card.dart
class ProfileCard extends StatelessWidget {
  const ProfileCard({super.key});

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.all(16),
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16)),
      child: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(children: [
          Stack(alignment: Alignment.bottomRight, children: [
            const CircleAvatar(
              radius: 48,
              backgroundImage: NetworkImage('https://i.pravatar.cc/150'),
            ),
            Container(
              width: 16, height: 16,
              decoration: BoxDecoration(
                color: Colors.green,
                shape: BoxShape.circle,
                border: Border.all(color: Colors.white, width: 2),
              ),
            ),
          ]),
          const SizedBox(height: 16),
          const Text('Sarah Chen',
            style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
          const Text('Flutter Developer & UI Designer',
            style: TextStyle(color: Colors.grey)),
          const SizedBox(height: 16),
          Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
            _StatItem(128, 'Projects'),
            _StatItem(4500, 'Followers'),
            _StatItem(320, 'Following'),
          ]),
          const SizedBox(height: 16),
          SizedBox(width: double.infinity,
            child: ElevatedButton(
              onPressed: () {},
              style: ElevatedButton.styleFrom(
                backgroundColor: Colors.indigo,
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(12)),
              ),
              child: const Text('Follow',
                style: TextStyle(color: Colors.white)),
            ),
          ),
        ]),
      ),
    );
  }
}

class _StatItem extends StatelessWidget {
  final int count;
  final String label;
  const _StatItem(this.count, this.label);

  @override
  Widget build(BuildContext context) {
    return Column(children: [
      Text('$count',
        style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
      Text(label, style: TextStyle(color: Colors.grey[600])),
    ]);
  }
}
Key technique The green dot uses Stack with Alignment.bottomRight to overlay the online status indicator on the avatar. Extract the stats into a private _StatItem widget for clean separation.

3. Animated List

Problem: You need items to slide in when added and slide out when removed — essential for to-do apps, chat lists, and any dynamic collection. A plain ListView doesn't animate item changes.

animated_list_example.dart
class AnimatedListScreen extends StatefulWidget {
  const AnimatedListScreen({super.key});
  @override
  State<AnimatedListScreen> createState() => _AnimatedListScreenState();
}

class _AnimatedListScreenState extends State<AnimatedListScreen> {
  final _listKey = GlobalKey<AnimatedListState>();
  final List<String> _items = [];

  void _addItem() {
    final index = _items.length;
    _items.add('Item ${index + 1}');
    _listKey.currentState!.insertItem(index);
  }

  void _removeItem(int index) {
    final removed = _items.removeAt(index);
    _listKey.currentState!.removeItem(
      index,
      (context, animation) => SizeTransition(
        sizeFactor: animation,
        child: Card(child: ListTile(title: Text(removed))),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Animated List')),
      floatingActionButton: FloatingActionButton(
        onPressed: _addItem,
        child: const Icon(Icons.add),
      ),
      body: AnimatedList(
        key: _listKey,
        initialItemCount: 0,
        itemBuilder: (context, index, animation) {
          return FadeTransition(
            opacity: animation,
            child: SlideTransition(
              position: Tween<Offset>(
                begin: const Offset(1, 0), end: Offset.zero,
              ).animate(animation),
              child: Card(child: ListTile(
                title: Text(_items[index]),
                trailing: IconButton(
                  icon: const Icon(Icons.delete),
                  onPressed: () => _removeItem(index),
                ),
              )),
            ),
          );
        },
      ),
    );
  }
}
Key technique AnimatedList requires you to call insertItem and removeItem explicitly. Always provide an animation widget in the itemBuilder — typically a FadeTransition or SlideTransition — for smooth entry/exit effects.

4. Custom Bottom Navigation

Problem: The standard BottomNavigationBar can't have a floating center button (like Uber, Instagram, or food delivery apps). You need a custom solution with animation and a raised action button.

custom_bottom_nav.dart
class CustomBottomNav extends StatefulWidget {
  const CustomBottomNav({super.key});
  @override
  State<CustomBottomNav> createState() => _CustomBottomNavState();
}

class _CustomBottomNavState extends State<CustomBottomNav> {
  int _currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Stack(alignment: Alignment.bottomCenter, children: [
      NavigationBar(
        selectedIndex: _currentIndex,
        onDestinationSelected: (i) => setState(() => _currentIndex = i),
        destinations: const [
          NavigationDestination(icon: Icon(Icons.home_outlined), label: 'Home'),
          NavigationDestination(icon: Icon(Icons.explore_outlined), label: 'Explore'),
          NavigationDestination(icon: Icon(Icons.bookmark_outline), label: 'Saved'),
          NavigationDestination(icon: Icon(Icons.person_outline), label: 'Profile'),
        ],
      ),
      Positioned(
        bottom: 20,
        child: Container(
          height: 56, width: 56,
          decoration: BoxDecoration(
            gradient: const LinearGradient(
              colors: [Colors.indigo, Colors.purple]),
            shape: BoxShape.circle,
            boxShadow: [BoxShadow(
              color: Colors.indigo.withOpacity(0.4),
              blurRadius: 8, offset: const Offset(0, 4),
            )],
          ),
          child: IconButton(
            icon: const Icon(Icons.add, color: Colors.white, size: 28),
            onPressed: () {},
          ),
        ),
      ),
    ]);
  }
}
Key technique The floating button is a Positioned widget inside a Stack that overlays the navigation bar. Use BoxShadow with a matching gradient color for a polished raised effect. The NavigationBar (Material 3) handles the rest.

5. Pull-to-Refresh

Problem: Users expect to pull down on a list to reload fresh data — it's the universal gesture for "get me the latest." The RefreshIndicator wraps any scrollable widget and adds this behavior out of the box.

pull_to_refresh.dart
class PullToRefreshScreen extends StatefulWidget {
  const PullToRefreshScreen({super.key});
  @override
  State<PullToRefreshScreen> createState() => _PullToRefreshScreenState();
}

class _PullToRefreshScreenState extends State<PullToRefreshScreen> {
  List<String> _items = [];

  Future<void> _refresh() async {
    await Future.delayed(const Duration(seconds: 2));
    setState(() {
      _items = List.generate(20, (i) => 'Fresh item ${i + 1}');
    });
  }

  @override
  void initState() {
    super.initState();
    _refresh();
  }

  @override
  Widget build(BuildContext context) {
    return RefreshIndicator(
      onRefresh: _refresh,
      child: ListView.builder(
        itemCount: _items.length,
        itemBuilder: (context, index) =>
          ListTile(title: Text(_items[index])),
      ),
    );
  }
}
Key technique The wrapped scrollable must be able to overscroll — ListView, CustomScrollView, and SingleChildScrollView all work. Always return a Future from onRefresh so the spinner dismisses when data is ready.

6. Swipe to Delete

Problem: Email apps, task managers, and chat apps let users swipe items to delete them, with an undo option. The Dismissible widget handles this gesture and provides the standard swipe-to-reveal pattern.

swipe_delete.dart
ListView.builder(
  itemCount: tasks.length,
  itemBuilder: (context, index) {
    final task = tasks[index];
    return Dismissible(
      key: Key(task.id),
      direction: DismissDirection.endToStart,
      background: Container(
        alignment: Alignment.centerRight,
        padding: const EdgeInsets.only(right: 20),
        color: Colors.red,
        child: const Icon(Icons.delete, color: Colors.white),
      ),
      confirmDismiss: (direction) async {
        return await showDialog<bool>(
          context: context,
          builder: (ctx) => AlertDialog(
            title: const Text('Delete task?'),
            actions: [
              TextButton(onPressed: () => Navigator.pop(ctx, false),
                child: const Text('Cancel')),
              TextButton(onPressed: () => Navigator.pop(ctx, true),
                child: const Text('Delete')),
            ],
          ),
        );
      },
      onDismissed: (direction) {
        setState(() => tasks.removeAt(index));
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('${task.title} deleted'),
            action: SnackBarAction(
              label: 'UNDO',
              onPressed: () => setState(() => tasks.insert(index, task)),
            ),
          ),
        );
      },
      child: ListTile(title: Text(task.title)),
    );
  },
)
Key technique confirmDismiss returns a Future<bool> — return true to actually remove the item. The SnackBar action lets users undo the deletion. Always give Dismissible a unique Key for proper list reconciliation.

7. Custom Text Field

Problem: Every form needs polished input fields with labels, icons, validation, and special features like password visibility toggles. Wrapping TextFormField in a reusable widget eliminates repetitive decoration code.

custom_textfield.dart
class CustomTextField extends StatefulWidget {
  final String label;
  final IconData icon;
  final String? hint;
  final bool isPassword;
  final String? Function(String?)? validator;

  const CustomTextField({
    required this.label,
    required this.icon,
    this.hint,
    this.isPassword = false,
    this.validator,
    super.key,
  });

  @override
  State<CustomTextField> createState() => _CustomTextFieldState();
}

class _CustomTextFieldState extends State<CustomTextField> {
  bool _obscure = true;

  @override
  Widget build(BuildContext context) {
    return TextFormField(
      obscureText: widget.isPassword ? _obscure : false,
      validator: widget.validator,
      decoration: InputDecoration(
        labelText: widget.label,
        hintText: widget.hint,
        prefixIcon: Icon(widget.icon),
        suffixIcon: widget.isPassword
            ? IconButton(
                icon: Icon(_obscure
                    ? Icons.visibility_off
                    : Icons.visibility),
                onPressed: () => setState(() => _obscure = !_obscure),
              )
            : null,
        border: OutlineInputBorder(
          borderRadius: BorderRadius.circular(12)),
        filled: true,
      ),
    );
  }
}

// Usage:
Form(
  child: Column(children: [
    const CustomTextField(
      label: 'Email',
      icon: Icons.email_outlined,
      hint: '[email protected]',
      validator: (v) => v!.contains('@') ? null : 'Invalid email',
    ),
    const SizedBox(height: 16),
    const CustomTextField(
      label: 'Password',
      icon: Icons.lock_outlined,
      isPassword: true,
      validator: (v) => v!.length >= 8 ? null : 'Min 8 characters',
    ),
  ]),
)
Key technique The password toggle uses a local _obscure state to flip between Icons.visibility and Icons.visibility_off. The validator callback pattern lets callers define custom validation logic without subclassing.

8. Tag/Chip Filter

Problem: E-commerce, music, and news apps use horizontal filter chips that users tap to select categories. You need selection state, horizontal scrolling, and visual feedback — all in one clean pattern.

chip_filter.dart
class ChipFilter extends StatefulWidget {
  const ChipFilter({super.key});
  @override
  State<ChipFilter> createState() => _ChipFilterState();
}

class _ChipFilterState extends State<ChipFilter> {
  final List<String> _tags = ['All', 'Flutter', 'Dart', 'Firebase', 'UI/UX', 'Testing'];
  int _selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 48,
      child: ListView.separated(
        scrollDirection: Axis.horizontal,
        padding: const EdgeInsets.symmetric(horizontal: 16),
        itemCount: _tags.length,
        separatorBuilder: (_, __) => const SizedBox(width: 8),
        itemBuilder: (context, index) {
          final selected = index == _selectedIndex;
          return FilterChip(
            label: Text(_tags[index]),
            selected: selected,
            onSelected: (_) => setState(() => _selectedIndex = index),
            selectedColor: Colors.indigo,
            checkmarkColor: Colors.white,
            labelStyle: TextStyle(
              color: selected ? Colors.white : Colors.grey[700],
            ),
          );
        },
      ),
    );
  }
}
Key technique FilterChip provides built-in selection state. For multi-select, replace the single _selectedIndex with a Set<int> and toggle membership on each tap. The ListView.separated adds consistent spacing between chips.

9. Shimmer Loading

Problem: When content loads from an API, users see a blank screen or a spinner. Shimmer skeletons (like Facebook and LinkedIn) give the impression of content loading and reduce perceived wait time significantly.

shimmer_loading.dart
class ShimmerLoading extends StatefulWidget {
  const ShimmerLoading({super.key});
  @override
  State<ShimmerLoading> createState() => _ShimmerLoadingState();
}

class _ShimmerLoadingState extends State<ShimmerLoading>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 2),
    )..repeat();
  }

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

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, _) => Column(
        children: List.generate(5, (_) => Padding(
          padding: const EdgeInsets.symmetric(
            horizontal: 16, vertical: 8),
          child: Row(children: [
            Container(
              width: 48, height: 48,
              decoration: BoxDecoration(
                color: Colors.grey[300],
                shape: BoxShape.circle,
              ),
            ),
            const SizedBox(width: 16),
            Expanded(child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Container(height: 14, width: 120,
                  color: Colors.grey[300]),
                const SizedBox(height: 8),
                Container(height: 12, width: 80,
                  color: Colors.grey[200]),
              ],
            )),
          ]),
        )),
      ),
    );
  }
}
Key technique For a true shimmer effect, use the shimmer package: wrap your skeleton in Shimmer.fromColors with baseColor and highlightColor. The manual animation above shows the core concept without external dependencies.

10. Gradient Cards

Problem: Flat grey cards look dated. Gradient cards with icons and subtle shadows give your UI a modern, premium feel — perfect for feature highlights, stats dashboards, and promotional sections.

gradient_cards.dart
class GradientCard extends StatelessWidget {
  final String title;
  final String subtitle;
  final IconData icon;
  final List<Color> colors;

  const GradientCard({
    required this.title,
    required this.subtitle,
    required this.icon,
    required this.colors,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(20),
      decoration: BoxDecoration(
        gradient: LinearGradient(
          colors: colors,
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
        borderRadius: BorderRadius.circular(16),
        boxShadow: [BoxShadow(
          color: colors.first.withOpacity(0.3),
          blurRadius: 12,
          offset: const Offset(0, 6),
        )],
      ),
      child: Row(children: [
        Container(
          padding: const EdgeInsets.all(12),
          decoration: BoxDecoration(
            color: Colors.white.withOpacity(0.2),
            borderRadius: BorderRadius.circular(12),
          ),
          child: Icon(icon, color: Colors.white, size: 28),
        ),
        const SizedBox(width: 16),
        Expanded(child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(title, style: const TextStyle(
              color: Colors.white,
              fontWeight: FontWeight.bold,
              fontSize: 16,
            )),
            Text(subtitle, style: TextStyle(
              color: Colors.white.withOpacity(0.8),
              fontSize: 13,
            )),
          ],
        )),
      ]),
    );
  }
}

// Usage:
Column(children: [
  const GradientCard(
    title: 'Revenue', subtitle: '\$12,450 this month',
    icon: Icons.trending_up,
    colors: [Colors.indigo, Colors.purple],
  ),
  const SizedBox(height: 16),
  const GradientCard(
    title: 'Users', subtitle: '1,280 active today',
    icon: Icons.people,
    colors: [Colors.teal, Colors.green],
  ),
])
Key technique The BoxShadow uses the first gradient color at 30% opacity to create a natural, colored shadow that matches the card. The frosted icon container uses Colors.white.withOpacity(0.2) for a glassmorphism effect.

11. Responsive Sidebar

Problem: A sidebar that looks great on desktop becomes unusable on mobile. You need a pattern that shows a NavigationRail on tablets and collapses to a Drawer on phones — all from a single component.

responsive_sidebar.dart
class ResponsiveScaffold extends StatefulWidget {
  const ResponsiveScaffold({super.key});
  @override
  State<ResponsiveScaffold> createState() => _ResponsiveScaffoldState();
}

class _ResponsiveScaffoldState extends State<ResponsiveScaffold> {
  int _selectedIndex = 0;
  final _destinations = const [
    NavigationRailDestination(
      icon: Icon(Icons.home_outlined),
      selectedIcon: Icon(Icons.home),
      label: Text('Home'),
    ),
    NavigationRailDestination(
      icon: Icon(Icons.settings_outlined),
      selectedIcon: Icon(Icons.settings),
      label: Text('Settings'),
    ),
    NavigationRailDestination(
      icon: Icon(Icons.person_outlined),
      selectedIcon: Icon(Icons.person),
      label: Text('Profile'),
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth > 600) {
          return Row(children: [
            NavigationRail(
              selectedIndex: _selectedIndex,
              onDestinationSelected: (i) =>
                setState(() => _selectedIndex = i),
              destinations: _destinations,
            ),
            const VerticalDivider(width: 1),
            Expanded(child: _pages[_selectedIndex]),
          ]);
        }
        return Scaffold(
          appBar: AppBar(title: const Text('dftacademy')),
          drawer: Drawer(
            child: ListView(children: [
              const DrawerHeader(child: Text('Menu')),
              ListTile(
                leading: const Icon(Icons.home),
                title: const Text('Home'),
                onTap: () => setState(() => _selectedIndex = 0),
              ),
              ListTile(
                leading: const Icon(Icons.settings),
                title: const Text('Settings'),
                onTap: () => setState(() => _selectedIndex = 1),
              ),
            ]),
          ),
          body: _pages[_selectedIndex],
        );
      },
    );
  }

  final _pages = const [
    Center(child: Text('Home')),
    Center(child: Text('Settings')),
    Center(child: Text('Profile')),
  ];
}
Key technique LayoutBuilder checks constraints.maxWidth at build time. On wide screens, NavigationRail sits beside the content in a Row. On narrow screens, a Drawer hides the nav off-screen. This pattern works for any adaptive navigation pattern.

12. Real-World: Complete Dashboard

Problem: You've learned individual patterns — now you need to combine them into a real, production-quality screen. This dashboard pulls together gradient cards, a profile header, filter chips, and a responsive layout into one cohesive screen.

complete_dashboard.dart
class DashboardScreen extends StatefulWidget {
  const DashboardScreen({super.key});
  @override
  State<DashboardScreen> createState() => _DashboardScreenState();
}

class _DashboardScreenState extends State<DashboardScreen> {
  int _tab = 0;
  final _tabs = ['Overview', 'Analytics', 'Reports'];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Dashboard'),
        actions: [
          IconButton(icon: const Icon(Icons.notifications),
            onPressed: () {}),
        ],
      ),
      body: RefreshIndicator(
        onRefresh: () async => await Future.delayed(
          const Duration(seconds: 1)),
        child: ListView(padding: const EdgeInsets.all(16), children: [
          // Filter chips
          SizedBox(height: 48, child: ListView.separated(
            scrollDirection: Axis.horizontal,
            itemCount: _tabs.length,
            separatorBuilder: (_, __) => const SizedBox(width: 8),
            itemBuilder: (ctx, i) => ChoiceChip(
              label: Text(_tabs[i]),
              selected: i == _tab,
              onSelected: (_) => setState(() => _tab = i),
            ),
          )),
          const SizedBox(height: 16),
          // Stat cards
          LayoutBuilder(builder: (ctx, c) {
            var cols = c.maxWidth > 600 ? 3 : 2;
            return GridView.count(
              crossAxisCount: cols,
              shrinkWrap: true,
              physics: const NeverScrollableScrollPhysics(),
              mainAxisSpacing: 12,
              crossAxisSpacing: 12,
              children: [
                GradientCard(title: 'Users', subtitle: '12.4k',
                  icon: Icons.people,
                  colors: [Colors.indigo, Colors.purple]),
                GradientCard(title: 'Revenue', subtitle: '\$8.2k',
                  icon: Icons.attach_money,
                  colors: [Colors.teal, Colors.green]),
                GradientCard(title: 'Orders', subtitle: '342',
                  icon: Icons.shopping_cart,
                  colors: [Colors.orange, Colors.red]),
              ],
            );
          }),
          const SizedBox(height: 24),
          // Recent activity
          Text('Recent Activity',
            style: Theme.of(context).textTheme.titleMedium),
          const SizedBox(height: 8),
          ...List.generate(5, (i) =>
            ListTile(
              leading: CircleAvatar(
                child: Text('${i + 1}')),
              title: Text('Activity ${i + 1}'),
              subtitle: Text('Updated ${i + 1} min ago'),
              trailing: const Icon(Icons.chevron_right),
            ),
          ),
        ]),
      ),
    );
  }
}
Key technique Combining RefreshIndicator + ListView + GridView with shrinkWrap: true lets a grid coexist with a list inside a single scrollable. The LayoutBuilder dynamically adjusts the column count based on screen width.

Summary

  • Custom AppBar — gradient background with integrated search and badge indicators
  • Profile Card — stacked avatars with status dots and stat rows
  • Animated ListinsertItem/removeItem with slide transitions
  • Bottom Nav — floating center button via Stack + Positioned
  • Pull-to-RefreshRefreshIndicator wrapping any scrollable
  • Swipe to DeleteDismissible with undo SnackBar
  • Custom Text Field — reusable with validation and password toggle
  • Chip FilterFilterChip in a horizontal ListView
  • Shimmer Loading — skeleton placeholders with animated gradients
  • Gradient Cards — linear gradients with colored shadows and icon badges
  • Responsive SidebarNavigationRail on tablet, Drawer on mobile
  • Complete Dashboard — combining all patterns into one production screen
🚀 Next Step Continue to State Management to learn how to manage the data behind these widgets.
K
Kesavaraja Murugesan
Flutter Developer & Educator

Flutter developer with 3 years of experience building production-grade mobile applications. Passionate about teaching clean architecture patterns and helping developers write maintainable, scalable code.

← Layouts Next: State Management →