image

30 Dec 2025

9K

35K

Building a Collapsible Sidebar Menu Widget in Flutter

A collapsible sidebar menu is a common UI pattern in modern applications, providing efficient navigation while conserving screen real estate. It allows users to expand or collapse sections of menu items, keeping the interface clean and organized. In Flutter, building such a widget is straightforward thanks to its rich set of UI components and animation capabilities.

This article will guide you through the process of creating a professional and functional collapsible sidebar menu widget in your Flutter application.

Understanding the Core Components

Before diving into the code, let's outline the key Flutter widgets we'll utilize:

  • Scaffold: Provides the basic visual structure for the material design app, including AppBar and Drawer.
  • Drawer: A material design panel that slides in from the edge of the Scaffold. This is where our sidebar content will reside.
  • ListView: Essential for displaying a scrollable list of widgets, which will be our menu items.
  • ListTile: A single fixed-height row that typically contains some text as well as a leading or trailing icon. Perfect for individual menu items.
  • StatefulWidget: Necessary for managing the expanded/collapsed state of our menu sections.
  • AnimatedSize: A widget that animates its size when the child's size changes. This will provide a smooth expansion/collapse effect.
  • RotationTransition: For animating the rotation of an icon (e.g., an arrow) to indicate the expanded state.

Project Setup

First, create a new Flutter project or open an existing one. We'll modify the main.dart file to include our sidebar menu.

main.dart - Initial Setup

Here's a basic main.dart to get started:


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: 'Collapsible Sidebar Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: const HomePage(),
    );
  }
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home Page'),
      ),
      drawer: const CollapsibleSidebarMenu(), // Our custom sidebar widget
      body: const Center(
        child: Text('Welcome to the main content!'),
      ),
    );
  }
}

Building the Collapsible Sidebar Menu

Now, let's create the CollapsibleSidebarMenu widget and its sub-components.

1. CollapsibleSidebarMenu Widget

This will be the main Drawer content, holding a ListView of our menu items and collapsible sections.


class CollapsibleSidebarMenu extends StatelessWidget {
  const CollapsibleSidebarMenu({super.key});

  @override
  Widget build(BuildContext context) {
    return Drawer(
      child: ListView(
        padding: EdgeInsets.zero,
        children: <Widget>[
          const DrawerHeader(
            decoration: BoxDecoration(
              color: Colors.blue,
            ),
            child: Text(
              'App Menu',
              style: TextStyle(
                color: Colors.white,
                fontSize: 24,
              ),
            ),
          ),
          // Non-collapsible menu item
          ListTile(
            leading: const Icon(Icons.home),
            title: const Text('Home'),
            onTap: () {
              // Handle tap
              Navigator.pop(context); // Close the drawer
            },
          ),
          // Collapsible section
          CollapsibleMenuSection(
            title: 'Categories',
            icon: Icons.category,
            children: <Widget>[
              ListTile(
                title: const Text('Electronics'),
                onTap: () {
                  // Handle tap
                  Navigator.pop(context);
                },
              ),
              ListTile(
                title: const Text('Books'),
                onTap: () {
                  // Handle tap
                  Navigator.pop(context);
                },
              ),
              ListTile(
                title: const Text('Clothing'),
                onTap: () {
                  // Handle tap
                  Navigator.pop(context);
                },
              ),
            ],
          ),
          // Another non-collapsible item
          ListTile(
            leading: const Icon(Icons.settings),
            title: const Text('Settings'),
            onTap: () {
              // Handle tap
              Navigator.pop(context);
            },
          ),
          // Another collapsible section
          CollapsibleMenuSection(
            title: 'About Us',
            icon: Icons.info,
            children: <Widget>[
              ListTile(
                title: const Text('Company Profile'),
                onTap: () {
                  // Handle tap
                  Navigator.pop(context);
                },
              ),
              ListTile(
                title: const Text('Contact Info'),
                onTap: () {
                  // Handle tap
                  Navigator.pop(context);
                },
              ),
            ],
          ),
        ],
      ),
    );
  }
}

2. CollapsibleMenuSection Widget

This is the core widget responsible for the collapsible behavior. It will be a StatefulWidget to manage its expansion state and use AnimatedSize for smooth transitions.


class CollapsibleMenuSection extends StatefulWidget {
  final String title;
  final IconData icon;
  final List<Widget> children;

  const CollapsibleMenuSection({
    super.key,
    required this.title,
    required this.icon,
    required this.children,
  });

  @override
  State<CollapsibleMenuSection> createState() => _CollapsibleMenuSectionState();
}

class _CollapsibleMenuSectionState extends State<CollapsibleMenuSection>
    with SingleTickerProviderStateMixin {
  bool _isExpanded = false;
  late AnimationController _controller;
  late Animation<double> _iconTurns;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 200),
      vsync: this,
    );
    _iconTurns = Tween<double>(begin: 0.0, end: 0.5).animate(_controller);
  }

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

  void _handleTap() {
    setState(() {
      _isExpanded = !_isExpanded;
      if (_isExpanded) {
        _controller.forward();
      } else {
        _controller.reverse();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        ListTile(
          leading: Icon(widget.icon),
          title: Text(widget.title),
          trailing: RotationTransition(
            turns: _iconTurns,
            child: const Icon(Icons.expand_more),
          ),
          onTap: _handleTap,
        ),
        // Use AnimatedSize to smoothly animate the height of the children
        AnimatedSize(
          duration: const Duration(milliseconds: 200),
          curve: Curves.easeInOut,
          child: SizedBox(
            height: _isExpanded ? null : 0.0, // Set height to 0 when collapsed
            child: Column(
              children: widget.children.map((child) {
                // Add padding to sub-items for visual hierarchy
                return Padding(
                  padding: const EdgeInsets.only(left: 16.0),
                  child: child,
                );
              }).toList(),
            ),
          ),
        ),
        const Divider(height: 1), // Optional: separator for sections
      ],
    );
  }
}

Explanation of the Code

  1. HomePage: This widget contains a Scaffold with an AppBar and sets our CollapsibleSidebarMenu as the drawer. When the drawer icon in the app bar is tapped, the CollapsibleSidebarMenu slides in.

  2. CollapsibleSidebarMenu:

    • It's a StatelessWidget because its own state (the overall menu content) doesn't change, only the state of its children.
    • It uses a Drawer as its root widget.
    • A ListView holds all the menu items and sections, allowing them to scroll if the content exceeds the screen height.
    • DrawerHeader provides a prominent area at the top of the drawer.
    • Regular ListTile widgets are used for non-collapsible items like "Home" or "Settings".
    • CollapsibleMenuSection widgets are used for expandable groups like "Categories" and "About Us".

  3. CollapsibleMenuSection: This is the most important part for the collapsible functionality.

    • It's a StatefulWidget because it needs to manage its own internal state (_isExpanded).
    • _isExpanded: A boolean variable that determines if the section's children are visible.
    • AnimationController and Animation<double>: Used to smoothly rotate the trailing icon (expand_more) when the section expands or collapses, providing visual feedback. The SingleTickerProviderStateMixin is required for AnimationController.
    • _handleTap(): Toggles _isExpanded and triggers the animation controller to either forward() or reverse(), causing the icon to rotate.
    • The main section header is a ListTile which contains the title, icon, and a RotationTransition wrapped expand_more icon.
    • AnimatedSize: This widget is crucial. It wraps a SizedBox which in turn wraps a Column containing all the child ListTiles.
      • When _isExpanded is true, SizedBox's height is null, allowing the children to determine its natural height.
      • When _isExpanded is false, SizedBox's height is set to 0.0, effectively hiding the children.
      • AnimatedSize then smoothly animates between these height values.
    • The child ListTiles within a collapsible section have Padding applied to their left side to create a visual indentation, indicating they are sub-items.

Customization and Enhancements

You can further customize and enhance this collapsible sidebar menu:

  • Colors and Themes: Adjust colors, text styles, and icons to match your app's branding.
  • Deep Nesting: To create multi-level collapsible menus, you can nest CollapsibleMenuSection widgets within the children of another CollapsibleMenuSection. Be mindful of excessive nesting, as it can complicate navigation.
  • Navigation: Implement robust navigation logic using Navigator.push() or a routing package like GoRouter to move to different screens when menu items are tapped. Remember to call Navigator.pop(context) to close the drawer after selection.
  • State Management: For more complex applications, consider using a state management solution (Provider, BLoC, Riverpod) to manage the expansion state, especially if menu states need to be persisted or shared across different parts of the app.
  • Custom Animations: Experiment with different curve properties for AnimatedSize and AnimationController durations to fine-tune the animation feel.

Conclusion

By leveraging Flutter's powerful widget tree and animation capabilities, creating a professional collapsible sidebar menu is both efficient and flexible. This structured approach allows you to build a clean, navigable, and user-friendly interface that enhances the overall user experience of your application. The CollapsibleMenuSection widget provides a reusable pattern for any section of your app that requires expandable content, not just for sidebars.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is