๐จ Color use
Common mistake
A common mistake is using color as the only way to communicate the state of an element. For example, showing an active button in green and an inactive one in grey without any other visual distinction. Users with color blindness or reduced color perception cannot tell the elements apart.
๐ฏ Relevant elements
This guideline applies to any element that uses color to convey meaning:
- Active / inactive or enabled / disabled states
- Error, warning, and success indicators
- Required field markers
- Progress steps or status badges
- Chart lines and legend items
WCAG Guideline
This guideline is based on WCAG 2.2 - 1.4.1 Use of Color (Level A). Color must not be used as the only visual means of conveying information, indicating an action, prompting a response, or distinguishing a visual element.
Solution
Always pair color with a secondary visual indicator such as an icon, label, pattern, or text so that the information is still available when color cannot be perceived.
// โ Don't - only the fill color distinguishes active from inactive
Row(
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.green),
onPressed: onActive,
child: const Text('Active'),
),
ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.grey),
onPressed: null,
child: const Text('Inactive'),
),
],
);
// โ
Do - pair color with an icon so the state is perceivable without color
Row(
children: [
ElevatedButton.icon(
style: ElevatedButton.styleFrom(backgroundColor: Colors.green),
onPressed: onActive,
icon: const Icon(Icons.check_circle_outline),
label: const Text('Active'),
),
ElevatedButton.icon(
style: ElevatedButton.styleFrom(backgroundColor: Colors.grey),
onPressed: null,
icon: const Icon(Icons.remove_circle_outline),
label: const Text('Inactive'),
),
],
);
For error states, combine a color change with an icon and a descriptive text label:
// โ
Do - error state communicated by color, icon, and text
TextFormField(
decoration: InputDecoration(
labelText: 'Email',
errorText: hasError ? 'Enter a valid email address' : null,
errorStyle: const TextStyle(color: Colors.red),
suffixIcon: hasError
? const Icon(Icons.error_outline, color: Colors.red)
: null,
),
);
Validation & Testing
See the Validation & Testing setup guide for tool configuration.
Manual review
Activate a grayscale display filter (Android: Developer Options โ Simulate color space โ Monochromacy; iOS: Settings โ Accessibility โ Display & Text Size โ Color Filters) and verify that all states and statuses are still distinguishable.
TalkBack & VoiceOver
Navigate to each status element. The semantic label must describe the state explicitly โ do not rely on the screen reader picking up color.
flutter_test
testWidgets('error state is indicated by icon and text, not only color', (tester) async {
await tester.pumpWidget(
MaterialApp(home: Scaffold(body: MyForm(hasError: true))),
);
expect(find.byIcon(Icons.error_outline), findsOneWidget);
expect(find.text('Enter a valid email address'), findsOneWidget);
});
๐ ๏ธ Usage baseflow-a11y-components library
๐ ๏ธ Work in progressโฆ