prefer_single_widget_per_file
Flags every public widget class in a file after the first. Private widgets (_Name) and non-widget classes are not counted by default.
One widget per file makes the file system an index of the widget library: the file name tells you what is in it, an import pulls in exactly what it says, and a diff touches one widget. Private helpers stay next to the widget they serve.
This rule is in the opinionated preset, so it is on with preset: opinionated and preset: pedantic.
class InvoiceCard extends StatelessWidget { const InvoiceCard({super.key});
@override Widget build(BuildContext context) => const Text('Invoice');}
// The second public widget in the file is the one reportedclass InvoiceList extends StatelessWidget { const InvoiceList({super.key});
@override Widget build(BuildContext context) => const Text('Invoices');}// invoice_card.dart — one public widget per fileclass InvoiceCard extends StatelessWidget { const InvoiceCard({super.key});
@override Widget build(BuildContext context) => const _InvoiceHeader();}
// Private helpers are fine in the same fileclass _InvoiceHeader extends StatelessWidget { const _InvoiceHeader();
@override Widget build(BuildContext context) => const Text('Invoice');}InvoiceList moves to invoice_list.dart.
Counting private widgets too
Section titled “Counting private widgets too”ignore_private_widgets: false makes a _Header count like any other widget — the strict reading of one widget per file:
rules: prefer_single_widget_per_file: ignore_private_widgets: falseclass InvoiceCard extends StatelessWidget { const InvoiceCard({super.key});
@override Widget build(BuildContext context) => const _InvoiceHeader();}
// Now reported under the config aboveclass _InvoiceHeader extends StatelessWidget { const _InvoiceHeader();
@override Widget build(BuildContext context) => const Text('Invoice');}Test-only widgets
Section titled “Test-only widgets”A widget existing only so tests can drive something is a deliberate extra, not a file that needs splitting. ignore_visible_for_testing: true exempts it:
rules: prefer_single_widget_per_file: ignore_visible_for_testing: trueclass InvoiceCard extends StatelessWidget { const InvoiceCard({super.key});
@override Widget build(BuildContext context) => const Text('Invoice');}
@visibleForTestingclass InvoiceCardHarness extends StatelessWidget { // not reported const InvoiceCardHarness({super.key});
@override Widget build(BuildContext context) => const InvoiceCard();}Known limitations
Section titled “Known limitations”Only widget classes are counted. A file may hold any number of enums, extensions, typedefs and plain classes without being reported — prefer_single_declaration_per_file covers those.
The report lands on the second widget and every one after it, not on the file, so a file with four widgets carries three diagnostics.
Options
Section titled “Options”many_lints: rules: prefer_single_widget_per_file: ignore_private_widgets: false ignore_visible_for_testing: truerules: prefer_single_widget_per_file: ignore_private_widgets: false ignore_visible_for_testing: true| Option | Type | Default | Description |
|---|---|---|---|
ignore_private_widgets |
bool | true |
Skip widgets whose name starts with _ |
ignore_visible_for_testing |
bool | false |
Skip widgets annotated @visibleForTesting |
Turning this rule off
Section titled “Turning this rule off”rules: prefer_single_widget_per_file: falseTo keep the rule on but skip certain paths, use per-rule exclude.
Related rules
Section titled “Related rules”prefer_single_declaration_per_file— Keep one top-level declaration per file, with per-type budgets.prefer_match_file_name— Name a file after the first public declaration in it.match_lib_folder_structure— Keep folders under lib/ in lower_snake_case.avoid_deep_widget_nesting— Keep a widget tree within a nesting budget.