InheritedWidget — what it is, data passing in the tree and how it works

Author: IT Sectr Published: 2026-07-02 Reading time: 9 min

InheritedWidget is a special widget in Flutter that passes data down the widget tree without explicitly passing it through constructors. Child widgets access data through BuildContext and automatically subscribe to updates. When data in InheritedWidget changes, all dependent widgets rebuild. According to Flutter API Reference, 2025, InheritedWidget is the foundation of Theme, MediaQuery, Localizations, and most state management libraries.

Key Takeaways

  • InheritedWidget passes data down the Widget Tree without explicitly threading it through every widget.
  • Automatic subscription — widgets using dependOnInheritedWidgetOfExactType rebuild when data changes.
  • Theme and MediaQuery are built-in examples of InheritedWidget, available in every Flutter application.
  • Provider and Riverpod are built on top of InheritedWidget and extend its capabilities for state management.
  • Proper implementation requires overriding updateShouldNotify to prevent unnecessary rebuilds.

What is InheritedWidget in Flutter?

InheritedWidget is a widget that makes its data available to all descendants in the Widget Tree. Unlike a regular widget that only passes data through constructors to child elements, InheritedWidget allows any widget in the subtree to access data without a chain of parameters. This solves the “prop drilling” problem — passing data through many intermediate widgets that don’t use this data themselves.

Built-in InheritedWidgets

Flutter includes several built-in InheritedWidgets: Theme (color scheme and styles), MediaQuery (screen size, orientation, pixel density), Localizations (localized strings), Directionality (text direction), DefaultTextStyle (default text style). These widgets are set by root widgets like MaterialApp and are available throughout the application.

InheritedWidget lifecycle

InheritedWidget does not have its own state — it stores data passed through the constructor. When the parent of InheritedWidget rebuilds with new data, the updateShouldNotify method is called to compare old and new data. If the method returns true, all dependent widgets are marked for rebuilding. This is a simple but effective reactive update mechanism.

How data passing works through InheritedWidget

The data passing mechanism through InheritedWidget is based on the Element Tree. When a widget calls dependOnInheritedWidgetOfExactType, the corresponding element registers a dependency on InheritedElement. When InheritedWidget changes, InheritedElement notifies all dependent elements, which rebuild in the next frame.

Dependency registration

The dependOnInheritedWidgetOfExactType method doesn’t just find InheritedWidget in the tree — it subscribes the current element to notifications. If you used findAncestorWidgetOfExactType instead of dependOn, the widget would get the data but wouldn’t rebuild when it changes. This is an important difference: dependOn is a subscription, findAncestor is a one-time lookup.

Tree traversal of InheritedWidget

When a widget requests an InheritedWidget, Flutter walks up the Element Tree from the current element to the root, checking each InheritedElement for a type match. The first matching InheritedElement is returned. This means the nearest InheritedWidget in the tree takes priority — you can override data at a specific level by placing InheritedWidget closer to the descendants.

dart
class ThemeData {
  final Color primaryColor;
  final TextTheme textTheme;

  const ThemeData({required this.primaryColor, required this.textTheme});
}

class MyTheme extends InheritedWidget {
  final ThemeData data;

  const MyTheme({required this.data, required Widget child}) : super(child: child);

  static MyTheme of(BuildContext context) {
    final widget = context.dependOnInheritedWidgetOfExactType<MyTheme>();
    assert(widget != null, "MyTheme not found in tree");
    return widget!;
  }

  @override
  bool updateShouldNotify(MyTheme oldWidget) => oldWidget.data != data;
}

In this example, MyTheme uses a static of method to provide data to descendants. The dependOnInheritedWidgetOfExactType method registers a dependency, and updateShouldNotify compares old and new data to determine whether dependent widgets need to rebuild.

Creating a custom InheritedWidget

Creating a custom InheritedWidget consists of two steps: defining a class that extends InheritedWidget, and implementing a static of method for access from descendants. Data is passed through the constructor, and the updateShouldNotify method determines when dependent widgets should rebuild.

Step 1: Defining the InheritedWidget class

The class must extend InheritedWidget and accept data through a constructor with a required child parameter. Data can be of any type: primitives, objects, functions. The main rule is that data must be immutable so that old and new values can be reliably compared.

Step 2: Static of method

The static of method takes BuildContext and returns InheritedWidget data. Inside, it calls dependOnInheritedWidgetOfExactType, which finds the nearest InheritedWidget of the specified type in the tree. If InheritedWidget is not found, the method throws an exception or returns a default value depending on the implementation.

Step 3: Using in widgets

To access data, the widget calls MyWidget.of(context) inside the build method. Flutter automatically subscribes the widget to updates. If data changes, the widget rebuilds in the next frame. This allows for clean and declarative code without unnecessary parameters.

dart
class UserPreferences extends InheritedWidget {
  final String languageCode;
  final bool darkMode;

  const UserPreferences({
    required this.languageCode,
    required this.darkMode,
    required Widget child,
  }) : super(child: child);

  static UserPreferences of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<UserPreferences>()!;
  }

  @override
  bool updateShouldNotify(UserPreferences oldWidget) =>
    oldWidget.languageCode != languageCode || oldWidget.darkMode != darkMode;
}

In this example, UserPreferences stores user settings. The updateShouldNotify method compares each field individually, which prevents unnecessary rebuilds when only one parameter changes. Use a similar approach for your own InheritedWidgets with multiple fields.

The updateShouldNotify method and preventing unnecessary rebuilds

updateShouldNotify is the key method of InheritedWidget that determines whether dependent widgets need to be notified about data changes. If the method returns false, dependent widgets do not rebuild, even if the InheritedWidget itself received a new instance with the same data. This is critically important for performance.

Proper implementation of updateShouldNotify

Compare only those fields that have actually changed and affect the display. If InheritedWidget contains 10 fields but only one affects the UI, check only that field. For collections, use deep comparison or immutable data structures. Do not use == for List or Map, as they compare by reference.

  • Primitives — use direct comparisons: oldWidget.value != value.
  • Immutable objects — use overridden ==: oldWidget.data != data (if data overrides ==).
  • Collections — use listEquals, mapEquals from package:flutter/foundation.dart.

Errors in updateShouldNotify implementation

The most common mistake is returning true without comparison. This causes all dependent widgets to rebuild on every parent update, even if data hasn’t changed. The second mistake is returning false when data has changed, leading to a stale UI. The third is complex comparison that runs every frame and slows things down.

InheritedWidget vs callbacks: which to choose?

InheritedWidget and callbacks (passing functions through constructors) solve different problems. InheritedWidget is suitable for data needed by many widgets at different levels of the tree. Callbacks are convenient for one-way event passing from parent to a specific child or vice versa. The choice depends on application architecture and update frequency.

When to use InheritedWidget

Use InheritedWidget when data is needed by many widgets at different nesting levels: app theme, user settings, device information, current session data. InheritedWidget is especially effective for “global” data that rarely changes but is needed in different parts of the UI.

When to use callbacks

Callbacks (callback functions) are suitable for passing events from a child widget to a parent: button press, list item selection, form submission. Callbacks explicitly indicate what actions a child can perform and don’t create hidden dependencies. For passing data down the tree over a small number of levels, it’s also simpler to use constructor parameters.

CriterionInheritedWidgetCallbacks
DirectionTop-down (parent → descendants)Bottom-up (child → parent) or direct
ScopeEntire subtreeSpecific widget
RebuildingAutomatic on data changeRequires manual setState
ComplexityMedium (requires InheritedWidget class)Low (just a function)

InheritedWidget and state management libraries

Provider and Riverpod are popular state management libraries in Flutter built on top of InheritedWidget. They extend its capabilities: add ChangeNotifier support, automatic disposal on unmount, lazy initialization, and simplified syntax with generics.

Provider based on InheritedWidget

Provider uses InheritedWidget to pass an object of any type down the tree. ChangeNotifierProvider tracks changes through ChangeNotifier and calls updateShouldNotify when notifyListeners is called. This frees the developer from manually creating InheritedWidget and implementing updateShouldNotify.

Comparison with direct InheritedWidget

Direct InheritedWidget gives more control and doesn’t require external dependencies. Provider provides ready infrastructure: Consumer, Selector, MultiProvider, ProxyProvider. The choice depends on application complexity. For simple projects, direct InheritedWidget is sufficient; for large ones, Provider or Riverpod reduce boilerplate code.

dart
// Direct InheritedWidget
class UserProvider extends InheritedWidget {
  final UserData userData;
  const UserProvider({required this.userData, required Widget child}) : super(child: child);
  static UserData of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<UserProvider>()!.userData;
  @override
  bool updateShouldNotify(UserProvider old) => old.userData != userData;
}

// Provider equivalent
return ChangeNotifierProvider<UserData>(
  create: (_) => UserData(),
  child: MyApp(),
);

Both approaches in the example solve the same problem — passing UserData down the tree. Provider reduces code volume but hides the InheritedWidget mechanics. Direct InheritedWidget gives full control and understanding of what’s happening, which is especially important when learning Flutter and debugging complex rebuild issues.

Frequently Asked Questions

How is InheritedWidget different from a regular widget?

InheritedWidget makes data available to all descendants through BuildContext, while a regular widget only passes data through the constructor. InheritedWidget also subscribes descendants to data updates.

How often do dependent widgets rebuild?

Dependent widgets only rebuild when updateShouldNotify returns true. If the method is implemented correctly, rebuilding only happens when data actually changes, not on every parent rebuild.

Can multiple InheritedWidgets be used in the same tree?

Yes, you can use any number of InheritedWidgets in the same tree. Each provides data of a specific type, and widgets can get data from multiple InheritedWidgets simultaneously.

What is the difference between dependOnInheritedWidgetOfExactType and findAncestorWidgetOfExactType?

dependOn subscribes the widget to updates — when data changes, the widget rebuilds. findAncestor performs a one-time lookup without subscription, and the widget won’t know about data changes.

Is InheritedWidget suitable for complex state management?

For simple state (theme, settings) InheritedWidget is sufficient. For complex state with business logic, use Provider, Riverpod, or BLoC — they are built on InheritedWidget and add the necessary infrastructure.

Summary

  • InheritedWidget is a special Flutter widget for passing data down the tree with automatic subscription to updates.
  • How it works based on Element Tree: InheritedElement registers dependent elements and notifies them of changes.
  • updateShouldNotify — the key method for preventing unnecessary rebuilds of dependent widgets.
  • Built-in InheritedWidgets: Theme, MediaQuery, Localizations, Directionality, DefaultTextStyle.
  • Creating a custom InheritedWidget includes class inheritance, data passing through constructor, and a static of method.
  • Provider and Riverpod are built on top of InheritedWidget and add ChangeNotifier, Consumer, Selector, and simplified syntax.
  • InheritedWidget solves the prop drilling problem and is the foundation of reactive state management in Flutter.

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also