prefer-action-button-tooltip

Path
lint/a11y/prefer-action-button-tooltip
Default
Off, opt in
Origin
Falcon
Domain
flutter
Rule
311 of 512
flutter

Requires useful tooltips on Flutter floating action buttons and icon buttons.

Tooltips describe actions to assistive technology. Resolved Flutter FloatingActionButton (unnamed, small, large, extended) and IconButton (unnamed, filled, filledTonal, outlined) constructors are checked. Missing, literal null, and empty non-interpolated string tooltips are reported. Dynamic labels and other button APIs are not guessed; custom classes and shadowed names are ignored. This checks the button's own tooltip argument, including extended FABs; labels or enclosing Tooltip/Semantics widgets do not exempt the button.

Invalid

example.dartdart
import 'package:flutter/material.dart';
import 'package:flutter/material.dart' as m;

Widget missing() => FloatingActionButton(onPressed: () {});
Widget empty() => FloatingActionButton(tooltip: '', onPressed: () {});
Widget nullLabel() => new FloatingActionButton(tooltip: null, onPressed: () {});
Widget prefixed() => m.FloatingActionButton.small(onPressed: () {});
Widget extended() => FloatingActionButton.extended(label: Text('Add'), onPressed: () {});
Widget icon() => IconButton(icon: Icon(Icons.add), onPressed: () {});
Widget filled() => IconButton.filled(tooltip: r'', icon: Icon(Icons.add), onPressed: () {});
Widget constant() => const FloatingActionButton(onPressed: null);
Widget adjacent() => FloatingActionButton(tooltip: '' '', onPressed: () {});
Widget large() => FloatingActionButton.large(onPressed: () {});
Widget outlined() => IconButton.outlined(icon: Icon(Icons.add), onPressed: () {});
Widget tonal() => IconButton.filledTonal(icon: Icon(Icons.add), onPressed: () {});
Widget wrapped() => Tooltip(
  message: 'Add',
  child: IconButton(icon: Icon(Icons.add), onPressed: () {}),
);
Widget iconLabel() => IconButton(icon: Icon(Icons.add, semanticLabel: 'Add'), onPressed: () {});
Provide a non-empty tooltip describing this action button.
3
4Widget missing() => FloatingActionButton(onPressed: () {});
Provide a non-empty tooltip describing this action button.
4Widget missing() => FloatingActionButton(onPressed: () {});
5Widget empty() => FloatingActionButton(tooltip: '', onPressed: () {});
Provide a non-empty tooltip describing this action button.
5Widget empty() => FloatingActionButton(tooltip: '', onPressed: () {});
6Widget nullLabel() => new FloatingActionButton(tooltip: null, onPressed: () {});
Provide a non-empty tooltip describing this action button.
6Widget nullLabel() => new FloatingActionButton(tooltip: null, onPressed: () {});
7Widget prefixed() => m.FloatingActionButton.small(onPressed: () {});
Provide a non-empty tooltip describing this action button.
7Widget prefixed() => m.FloatingActionButton.small(onPressed: () {});
8Widget extended() => FloatingActionButton.extended(label: Text('Add'), onPressed: () {});
Provide a non-empty tooltip describing this action button.
8Widget extended() => FloatingActionButton.extended(label: Text('Add'), onPressed: () {});
9Widget icon() => IconButton(icon: Icon(Icons.add), onPressed: () {});
Provide a non-empty tooltip describing this action button.
9Widget icon() => IconButton(icon: Icon(Icons.add), onPressed: () {});
10Widget filled() => IconButton.filled(tooltip: r'', icon: Icon(Icons.add), onPressed: () {});
Provide a non-empty tooltip describing this action button.
10Widget filled() => IconButton.filled(tooltip: r'', icon: Icon(Icons.add), onPressed: () {});
11Widget constant() => const FloatingActionButton(onPressed: null);
Provide a non-empty tooltip describing this action button.
11Widget constant() => const FloatingActionButton(onPressed: null);
12Widget adjacent() => FloatingActionButton(tooltip: '' '', onPressed: () {});
Provide a non-empty tooltip describing this action button.
12Widget adjacent() => FloatingActionButton(tooltip: '' '', onPressed: () {});
13Widget large() => FloatingActionButton.large(onPressed: () {});
Provide a non-empty tooltip describing this action button.
13Widget large() => FloatingActionButton.large(onPressed: () {});
14Widget outlined() => IconButton.outlined(icon: Icon(Icons.add), onPressed: () {});
Provide a non-empty tooltip describing this action button.
14Widget outlined() => IconButton.outlined(icon: Icon(Icons.add), onPressed: () {});
15Widget tonal() => IconButton.filledTonal(icon: Icon(Icons.add), onPressed: () {});
Provide a non-empty tooltip describing this action button.
17 message: 'Add',
18 child: IconButton(icon: Icon(Icons.add), onPressed: () {}),
Provide a non-empty tooltip describing this action button.
19);
20Widget iconLabel() => IconButton(icon: Icon(Icons.add, semanticLabel: 'Add'), onPressed: () {});

Valid

example.dartdart
import 'package:flutter/material.dart';
import 'package:flutter/material.dart' as m;

Widget labeled() => FloatingActionButton(tooltip: 'Add', onPressed: () {});
Widget iconLabeled() => IconButton(tooltip: 'Add', icon: Icon(Icons.add), onPressed: () {});
Widget unknown(String? label) => FloatingActionButton(tooltip: label, onPressed: () {});
Widget interpolated(String label) => FloatingActionButton(tooltip: '$label', onPressed: () {});
Widget otherButton() => TextButton(child: Text('Add'), onPressed: () {});
Widget shadowed(dynamic FloatingActionButton) => FloatingActionButton(onPressed: () {});
Widget prefixShadow(dynamic m) => m.FloatingActionButton(onPressed: () {});
Widget nestedShadow(dynamic IconButton) => Tooltip(message: 'Add', child: IconButton(onPressed: () {}));
Widget localCustom() => CustomActionButton();
class CustomActionButton extends StatelessWidget {
  Widget build(BuildContext context) => const SizedBox();
}

Configure prefer-action-button-tooltip

Set its severity in falcon.json. See the configuration guide.

falcon.jsonjson
{
  "linter": {
    "rules": {
      "a11y": {
        "prefer-action-button-tooltip": "error"
      }
    }
  }
}

Or silence one occurrence with a suppression comment; the reason is required.

example.dartdart
// falcon-ignore lint/a11y/prefer-action-button-tooltip: <reason>