Skip to content

avoid_deep_widget_nesting

v1.0.0WarningConfigurableWidget Best Practices

Flags a widget tree nested deeper than max_depth (default 8).

A build can be short and still be unreadable: eight levels of Padding inside Column inside Expanded push the widget that matters off the right edge, and every edit has to count brackets to find its place. Extracting a subtree into a named widget removes a level and names the part in one move.

Only widget instantiations are counted, so lists, closures and conditionals between them do not inflate the number. The diagnostic lands once per tree, on its root — the widget whose subtree has to be split — and the message says how far the subtree goes.

This rule is in the pedantic preset: a nesting budget is a house style.

See also: Flutter — performance best practices

analysis_options.yaml
many_lints:
rules:
avoid_deep_widget_nesting:
enabled: true
max_depth: 8

Nine widget levels, one over the default budget of 8. The diagnostic is on Scaffold, the root:

@override
Widget build(BuildContext context) => Scaffold( // 1
body: SafeArea( // 2
child: Padding( // 3
padding: const EdgeInsets.all(16),
child: Column( // 4
children: [
Expanded( // 5
child: Center( // 6
child: Padding( // 7
padding: const EdgeInsets.all(8),
child: Container( // 8
child: Text('Finally'), // 9
),
),
),
),
],
),
),
),
);

Cut the tree where it has a name. Each half is now well inside the budget:

@override
Widget build(BuildContext context) => Scaffold(
body: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16),
child: const _Content(),
),
),
);
class _Content extends StatelessWidget {
const _Content();
@override
Widget build(BuildContext context) => Column(
children: [
Expanded(child: Center(child: Text('Finally'))),
],
);
}

A builder: closure is its own build function, so its depth is counted from zero rather than added to the caller’s. This tree is never over budget however long the page around it gets:

ListView.builder(
itemCount: 10,
itemBuilder: (context, index) => Padding( // depth 1 inside the closure
padding: const EdgeInsets.all(8),
child: Text('$index'), // depth 2
),
);

A screen-level widget in a design system can legitimately run deeper. Set the number once rather than annotating every file:

many_lints.yaml
rules:
avoid_deep_widget_nesting:
max_depth: 12
Option Type Default Description
max_depth int 8 How many widget levels a tree may nest

To disable this rule:

many_lints.yaml
rules:
avoid_deep_widget_nesting: false

To keep the rule on but skip certain paths, use per-rule exclude.