Color is a powerful design tool, but relying solely on color to convey information creates accessibility barriers for users with color blindness or low vision. In this post, we'll explore how to make your SwiftUI apps more accessible by ensuring that information is communicated through multiple visual channels, not just color alone.

What is Differentiate Without Color?

Differentiate Without Color is an accessibility feature that helps users who have difficulty distinguishing colors. Approximately 8% of men and 0.5% of women have some form of color vision deficiency, making it essential to provide alternative ways to convey information.

When users enable "Differentiate Without Color" in their accessibility settings, your app should supplement color-based information with additional visual indicators such as:

  • Icons and symbols

  • Text labels

  • Patterns and textures

  • Shapes and borders

  • Position and size differences

For example, instead of using only red and green to show error and success states, you should also include an X icon for errors and a checkmark for success.

How to Implement in SwiftUI

SwiftUI provides the accessibilityDifferentiateWithoutColor environment variable to detect when users have enabled this setting. You can use this to adapt your UI accordingly.

Checking the Environment Variable

import SwiftUI

struct ContentView: View {
    @Environment(\.accessibilityDifferentiateWithoutColor) 
    var differentiateWithoutColor

    var body: some View {
        VStack {
            if differentiateWithoutColor {
                Text("Differentiate Without Color is enabled")
            } else {
                Text("Using standard color scheme")
            }
        }
    }
}

Bad Practice: Color Only

Here's an example of what NOT to do - using color alone to convey status:

struct BadStatusView: View {
    let isSuccess: Bool

    var body: some View {
        Text("Operation Complete")
            .padding()
            .background(isSuccess ? Color.green : Color.red)
            .cornerRadius(8)
    }
}

This design fails because color-blind users may not be able to distinguish between the red and green states.

Good Practice: Color + Icons

Here's a better approach that uses both color and icons:

struct GoodStatusView: View {
    @Environment(\.accessibilityDifferentiateWithoutColor) 
    var differentiateWithoutColor
    let isSuccess: Bool

    var statusIcon: String {
        isSuccess ? "checkmark.circle.fill" : "xmark.circle.fill"
    }

    var statusColor: Color {
        isSuccess ? .green : .red
    }

    var body: some View {
        HStack {
            Image(systemName: statusIcon)
                .foregroundColor(differentiateWithoutColor ? .primary : statusColor)

            Text(isSuccess ? "Success" : "Error")
                .foregroundColor(differentiateWithoutColor ? .primary : statusColor)
        }
        .padding()
        .background(
            RoundedRectangle(cornerRadius: 8)
                .stroke(differentiateWithoutColor ? Color.primary : statusColor, lineWidth: 2)
        )
    }
}

Chart Example with Patterns

When creating charts or graphs, use patterns in addition to colors:

struct AccessibleChartView: View {
    @Environment(\.accessibilityDifferentiateWithoutColor) 
    var differentiateWithoutColor

    let data: [(String, Double, String)] = [
        ("Category A", 0.3, "circle.fill"),
        ("Category B", 0.5, "square.fill"),
        ("Category C", 0.2, "triangle.fill")
    ]

    var body: some View {
        VStack(spacing: 20) {
            ForEach(data, id: \.0) { item in
                HStack {
                    // Icon helps differentiate
                    if differentiateWithoutColor {
                        Image(systemName: item.2)
                            .frame(width: 30)
                    }

                    Text(item.0)
                        .frame(width: 100, alignment: .leading)

                    // Visual bar
                    Rectangle()
                        .fill(colorForCategory(item.0))
                        .frame(width: CGFloat(item.1) * 200, height: 30)
                        .overlay(
                            // Add pattern when needed
                            differentiateWithoutColor ? 
                                Image(systemName: item.2)
                                    .resizable()
                                    .scaledToFit()
                                    .padding(4) : nil
                        )
                }
            }
        }
        .padding()
    }

    func colorForCategory(_ category: String) -> Color {
        switch category {
        case "Category A": return .blue
        case "Category B": return .orange
        case "Category C": return .purple
        default: return .gray
        }
    }
}

Button States Example

For interactive elements like buttons, combine color with shape and text:

struct AccessibleButton: View {
    @Environment(\.accessibilityDifferentiateWithoutColor) 
    var differentiateWithoutColor

    let isActive: Bool
    let action: () -> Void

    var body: some View {
        Button(action: action) {
            HStack {
                // Always show text
                Text(isActive ? "Active" : "Inactive")

                // Add icon for clarity
                Image(systemName: isActive ? "checkmark.circle" : "circle")
            }
            .padding()
            .background(
                RoundedRectangle(cornerRadius: 8)
                    .fill(isActive && !differentiateWithoutColor ? 
                          Color.blue : Color.gray.opacity(0.2))
                    .overlay(
                        RoundedRectangle(cornerRadius: 8)
                            .stroke(isActive ? Color.primary : Color.gray, 
                                   lineWidth: differentiateWithoutColor ? 3 : 1)
                    )
            )
        }
    }
}

Best Practices

  1. Always provide multiple indicators: Never rely on color alone. Use icons, labels, patterns, or shapes alongside color.

  2. Test with color blindness simulators: Use tools to see how your app appears to users with different types of color vision deficiency.

  3. Use semantic colors: SwiftUI's semantic colors (.primary, .secondary, .accentColor) adapt to user preferences automatically.

  4. Provide clear labels: Text labels should always accompany color-coded information.

  5. Consider contrast: Even when adding icons, maintain sufficient color contrast for users with low vision.

  6. Respect user preferences: Check the accessibilityDifferentiateWithoutColor environment variable and adapt your UI accordingly.

Wrap up

Color alone is never enough to convey meaning — pair it with icons, labels, or patterns. The accessibilityDifferentiateWithoutColor environment variable makes it straightforward to adapt your UI when users have this preference enabled.

Resources:

Read more

Share


Share Bluesky Mastodon Twitter LinkedIn Facebook