๐ŸŽจ 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โ€ฆ


This site uses Just the Docs, a documentation theme for Jekyll.