Creating a Multi-Level Menu Widget in Flutter

29 Dec 2025

9K

35K

Creating a Multi-Level Menu Widget in Flutter

Building intuitive navigation is crucial for any application, and a multi-level menu often serves as an elegant solution for organizing complex hierarchies of features or content. In Flutter, creating such a widget can be achieved efficiently by combining Flutter's declarative UI capabilities with a recursive approach to handle arbitrary levels of depth. This article will guide you through the process of developing a reusable multi-level menu widget.

Understanding the Core Concept

A multi-level menu essentially consists of menu items that can either trigger an action directly or expand to reveal a sub-menu of further items. This hierarchical structure lends itself well to a recursive widget design, where a menu item widget renders itself and, if it has children, recursively renders instances of the same menu item widget for its children.

Flutter's

ExpansionTile
widget is a perfect candidate for the expandable nature of a parent menu item, as it provides built-in expand/collapse functionality and a visually distinct appearance.

Defining the Menu Item Data Structure

First, let's define a data model for our menu items. This class will hold the title, an optional icon, an optional callback for when the item is tapped, and crucially, a list of

MenuItem
objects for its children.

import 'package:flutter/material.dart';

class MenuItem {
  final String title;
  final IconData? icon;
  final List? children;
  final VoidCallback? onTap;

  MenuItem({
    required this.title,
    this.icon,
    this.children,
    this.onTap,
  });

  bool get hasChildren => children != null && children!.isNotEmpty;
}

The

hasChildren
getter is a convenience method to quickly check if an item is a parent with sub-menus.

Implementing the Multi-Level Menu Widget

Now, let's create the widget that will render each

MenuItem
. This widget will decide whether to render itself as an expandable tile (if it has children) or as a simple list item (if it's a leaf node).

import 'package:flutter/material.dart';

class MultiLevelMenuItem extends StatelessWidget {
  final MenuItem item;
  final double indentLevel; // Used to visually indent child items

  const MultiLevelMenuItem({
    Key? key,
    required this.item,
    this.indentLevel = 0,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    // Determine the padding for visual indentation
    final EdgeInsetsGeometry padding = EdgeInsets.only(left: indentLevel);

    if (item.hasChildren) {
      return Padding(
        padding: padding,
        child: ExpansionTile(
          leading: item.icon != null ? Icon(item.icon) : null,
          title: Text(item.title),
          children: item.children!
              .map((child) => MultiLevelMenuItem(
                    item: child,
                    indentLevel: indentLevel + 16.0, // Increase indent for children
                  ))
              .toList(),
        ),
      );
    } else {
      return Padding(
        padding: padding,
        child: ListTile(
          leading: item.icon != null ? Icon(item.icon) : null,
          title: Text(item.title),
          onTap: item.onTap,
        ),
      );
    }
  }
}

In this

MultiLevelMenuItem
widget:
  • We accept a
    MenuItem
    object as a required parameter.
  • We introduce an
    indentLevel
    property to control the visual offset for nested items, making the hierarchy clear.
  • If
    item.hasChildren
    is true, we return an
    ExpansionTile
    . The
    children
    of the
    ExpansionTile
    are populated by recursively calling
    MultiLevelMenuItem
    for each child item, incrementing the
    indentLevel
    .
  • If there are no children, we return a standard
    ListTile
    which triggers the
    onTap
    callback when pressed.

Example Usage

To demonstrate how to use the

MultiLevelMenuItem
, let's create some sample data and integrate it into a simple Flutter app.

import 'package:flutter/material.dart';
// Assuming MenuItem and MultiLevelMenuItem classes are in the same project or imported.
// import 'menu_item.dart';
// import 'multi_level_menu_item.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Multi-Level Menu Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State createState() => _MyHomePageState();
}

class _MyHomePageState extends State {
  // Sample data for our multi-level menu
  final List _menuItems = [
    MenuItem(
      title: 'Dashboard',
      icon: Icons.dashboard,
      onTap: () => print('Dashboard tapped'),
    ),
    MenuItem(
      title: 'Products',
      icon: Icons.shopping_bag,
      children: [
        MenuItem(
          title: 'All Products',
          onTap: () => print('All Products tapped'),
        ),
        MenuItem(
          title: 'Categories',
          children: [
            MenuItem(
              title: 'Electronics',
              onTap: () => print('Electronics tapped'),
            ),
            MenuItem(
              title: 'Books',
              onTap: () => print('Books tapped'),
            ),
            MenuItem(
              title: 'Clothing',
              children: [
                MenuItem(
                  title: 'Men\'s',
                  onTap: () => print('Men\'s Clothing tapped'),
                ),
                MenuItem(
                  title: 'Women\'s',
                  onTap: () => print('Women\'s Clothing tapped'),
                ),
              ],
            ),
          ],
        ),
        MenuItem(
          title: 'Add New Product',
          onTap: () => print('Add New Product tapped'),
        ),
      ],
    ),
    MenuItem(
      title: 'Orders',
      icon: Icons.receipt,
      children: [
        MenuItem(
          title: 'Pending Orders',
          onTap: () => print('Pending Orders tapped'),
        ),
        MenuItem(
          title: 'Completed Orders',
          onTap: () => print('Completed Orders tapped'),
        ),
      ],
    ),
    MenuItem(
      title: 'Settings',
      icon: Icons.settings,
      children: [
        MenuItem(
          title: 'Profile',
          onTap: () => print('Profile tapped'),
        ),
        MenuItem(
          title: 'Privacy',
          onTap: () => print('Privacy tapped'),
        ),
        MenuItem(
          title: 'About',
          onTap: () => print('About tapped'),
        ),
      ],
    ),
    MenuItem(
      title: 'Logout',
      icon: Icons.logout,
      onTap: () => print('Logout tapped'),
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Multi-Level Menu'),
      ),
      body: ListView(
        children: _menuItems
            .map((item) => MultiLevelMenuItem(item: item))
            .toList(),
      ),
    );
  }
}

In this example, a

ListView
is used to display the top-level menu items. Each top-level
MenuItem
is passed to a
MultiLevelMenuItem
widget, which then handles the rendering of its children recursively.

Conclusion

By leveraging Flutter's

ExpansionTile
and a recursive widget structure, you can create a flexible and powerful multi-level menu widget that can adapt to any depth of hierarchy. This approach keeps the code clean and maintainable, as the logic for rendering a menu item is self-contained within the
MultiLevelMenuItem
widget itself. You can further enhance this widget by adding custom animations, styling options, or integrating with a state management solution for more complex application flows.

Related Articles

Aug 02, 2026

Building a Flutter Notification Center with Grouping & Actions

Learn to create a sophisticated in-app notification center in Flutter, featuring grouping, swipe-to-dismiss, custom icons, and actionable buttons for enhanced u

Aug 01, 2026

Flutter Layout Tips: Using FractionallySizedBox, AspectRatio, ConstrainedBox for Adaptive UI

Master Flutter's FractionallySizedBox, AspectRatio, and ConstrainedBox to build truly adaptive UIs. Learn how to control widget sizing, maintain proportions, an

Aug 01, 2026

Flutter Product Quick View: Animation, Related Items, Badges

Learn to create a dynamic product quick view widget in Flutter, incorporating smooth animations, relevant related items, and eye-catching promo badges for an en