image

05 Mar 2026

9K

35K

Creating an Accordion FAQ Widget with Nested Questions in Flutter

Frequently Asked Questions (FAQ) sections are crucial for providing users with quick answers and reducing support inquiries. In mobile applications, presenting a long list of FAQs efficiently often involves an accordion-style widget. This approach conserves screen space by showing only question titles, expanding to reveal answers upon user interaction. A more advanced requirement is to support nested questions, where an answer to a top-level question might lead to further sub-questions, forming a hierarchical FAQ structure.

This article will guide you through creating a professional and reusable Accordion FAQ widget in Flutter, complete with support for nested questions, leveraging Flutter's built-in widgets and state management capabilities.

1. Defining the Data Model

First, we need a robust data model to represent our FAQ items. Each item should have a question, an answer, and optionally, a list of sub-questions (which are themselves FAQ items). This recursive definition is key to handling arbitrary nesting levels.


class FAQItem {
  final String question;
  final String answer;
  final List? nestedQuestions;

  FAQItem({
    required this.question,
    required this.answer,
    this.nestedQuestions,
  });
}

2. Designing the FAQ Widget Structure

Flutter's ExpansionTile widget is perfectly suited for building accordion-style components. It provides an out-of-the-box solution for expanding and collapsing content. We will create a recursive widget that renders an ExpansionTile for each FAQItem and, if nested questions exist, renders more FAQItems within its children.

Let's create a main widget, FAQAccordion, that takes a list of FAQItems and renders them. Then, we'll create a helper widget, _FAQTile, to handle individual FAQ items and their potential nested structure.

3. Implementing the Main FAQAccordion Widget

The FAQAccordion widget will be a StatelessWidget that primarily uses a ListView.builder to iterate over the top-level FAQ items and render _FAQTile for each.


import 'package:flutter/material.dart';

class FAQAccordion extends StatelessWidget {
  final List faqItems;

  const FAQAccordion({Key? key, required this.faqItems}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: faqItems.length,
      itemBuilder: (context, index) {
        return _FAQTile(item: faqItems[index]);
      },
    );
  }
}

4. Implementing the Recursive _FAQTile Widget

This is where the magic happens for nested questions. The _FAQTile will itself be a StatefulWidget (or you can manage expansion state at a higher level, but for simplicity, ExpansionTile handles its own state). It will display the question as its title and the answer as its direct child. If nestedQuestions are present, it will recursively render new _FAQTile widgets inside its children list.


class _FAQTile extends StatefulWidget {
  final FAQItem item;
  final int level; // To manage padding/indentation for nested levels

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

  @override
  State<_FAQTile> createState() => _FAQTileState();
}

class _FAQTileState extends State<_FAQTile> {
  bool _isExpanded = false;

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: EdgeInsets.symmetric(
        horizontal: 8.0 + (widget.level * 16.0), // Indent nested items
        vertical: 4.0,
      ),
      elevation: widget.level == 0 ? 2.0 : 1.0, // Less elevation for nested cards
      child: ExpansionTile(
        key: PageStorageKey(widget.item.question), // Helps preserve state on scroll
        title: Text(
          widget.item.question,
          style: TextStyle(
            fontWeight: FontWeight.bold,
            fontSize: 16.0 - (widget.level * 1.0), // Smaller font for nested
            color: widget.level == 0 ? Colors.deepPurple : Colors.black87,
          ),
        ),
        trailing: Icon(
          _isExpanded ? Icons.keyboard_arrow_up : Icons.keyboard_arrow_down,
          color: Colors.deepPurple,
        ),
        onExpansionChanged: (bool expanded) {
          setState(() {
            _isExpanded = expanded;
          });
        },
        children: [
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
            child: Align(
              alignment: Alignment.centerLeft,
              child: Text(
                widget.item.answer,
                style: TextStyle(fontSize: 14.0 - (widget.level * 0.5)),
                textAlign: TextAlign.justify,
              ),
            ),
          ),
          // Recursively render nested questions
          if (widget.item.nestedQuestions != null && widget.item.nestedQuestions!.isNotEmpty)
            ...widget.item.nestedQuestions!.map((nestedItem) {
              return _FAQTile(item: nestedItem, level: widget.level + 1);
            }).toList(),
        ],
      ),
    );
  }
}

In the _FAQTile widget:

  • We use PageStorageKey for ExpansionTile. This helps Flutter preserve the expansion state of a tile when it scrolls off-screen and then back on.
  • The level parameter is passed down to children to allow for visual indentation and subtle styling changes (like font size or elevation) to clearly distinguish nesting levels.
  • The spread operator (...) is used to directly insert the list of nested _FAQTile widgets into the parent ExpansionTile's children list.

5. Example Usage

To use this widget, you'll prepare your FAQ data and then pass it to the FAQAccordion.


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

  @override
  Widget build(BuildContext context) {
    final List sampleFaqs = [
      FAQItem(
        question: "What is Flutter?",
        answer: "Flutter is an open-source UI software development kit created by Google.",
        nestedQuestions: [
          FAQItem(
            question: "Why use Flutter?",
            answer: "Flutter allows you to build natively compiled applications for mobile, web, and desktop from a single codebase.",
          ),
          FAQItem(
            question: "Is Flutter cross-platform?",
            answer: "Yes, Flutter is renowned for its cross-platform capabilities.",
            nestedQuestions: [
              FAQItem(
                question: "Which platforms does Flutter support?",
                answer: "Flutter supports Android, iOS, Web, Windows, macOS, and Linux.",
              ),
            ],
          ),
        ],
      ),
      FAQItem(
        question: "How do I get started with Flutter?",
        answer: "You can visit the official Flutter website for installation guides and documentation.",
      ),
      FAQItem(
        question: "What is Dart?",
        answer: "Dart is a client-optimized programming language for fast apps on any platform, developed by Google. It is the language Flutter uses.",
      ),
    ];

    return MaterialApp(
      title: 'FAQ Accordion Demo',
      theme: ThemeData(
        primarySwatch: Colors.deepPurple,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('FAQ with Nested Questions'),
        ),
        body: FAQAccordion(faqItems: sampleFaqs),
      ),
    );
  }
}

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

6. Customization and Enhancements

  • Styling: You can further customize the appearance of ExpansionTile by adjusting its backgroundColor, collapsedBackgroundColor, iconColor, and collapsedIconColor. Text styles for the question and answer can be dynamically changed based on the level.
  • Initial Expansion State: If you want certain items to be expanded by default, you can add an isInitiallyExpanded property to your FAQItem model and pass it to the ExpansionTile.
  • Animation: ExpansionTile comes with built-in animation. If you need more custom animations, you might consider building a custom accordion from scratch using AnimatedContainer or SizeTransition.
  • Accessibility: Ensure proper semantic labels and focus management if you're building a highly interactive or custom accordion, although ExpansionTile handles much of this by default.
  • Search Functionality: For a large number of FAQs, adding a search bar to filter the list of FAQItems would be a significant enhancement.

Conclusion

Building an Accordion FAQ widget with nested questions in Flutter is straightforward thanks to the versatile ExpansionTile widget. By defining a recursive data model and a corresponding recursive rendering widget, you can elegantly handle arbitrary levels of nested questions, providing a clean and intuitive user experience for navigating complex FAQ structures. This approach ensures reusability, maintainability, and professional presentation of information within your Flutter applications.

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