From 39413944587f6118c34ec789aa690c891963b160 Mon Sep 17 00:00:00 2001 From: Karan Sharma <55722391+ksharma-xyz@users.noreply.github.com> Date: Thu, 13 Feb 2025 22:49:44 +1100 Subject: [PATCH] UI: Improve TransportModeChip text contrast accessibility (#611) ### TL;DR Enhanced color contrast handling for transport mode chips to ensure better accessibility ### What changed? - Added contrast ratio checking for transport mode chip text colors - Introduced constants for WCAG AA contrast requirements - Enhanced `getForegroundColor` function to accept an optional foreground color parameter - Improved text color handling in `TransportModeChip` component ### How to test? 1. Navigate to the trip planner screen 2. Verify that transport mode chip text remains readable when selected 3. Check text contrast against different transport mode background colors 4. Test with both light and dark themes ### Why make this change? To improve accessibility by ensuring text remains readable across all transport mode chips, regardless of their background color. This change follows WCAG AA guidelines for text contrast ratios, making the app more accessible to users with visual impairments. --- .../ui/components/TransportModeChip.kt | 11 +++--- .../xyz/ksharma/krail/taj/theme/A11yColors.kt | 34 +++++++++++++++++-- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeChip.kt b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeChip.kt index ef7bc859..d67d56a0 100644 --- a/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeChip.kt +++ b/feature/trip-planner/ui/src/commonMain/kotlin/xyz/ksharma/krail/trip/planner/ui/components/TransportModeChip.kt @@ -43,7 +43,10 @@ fun TransportModeChip( ) val textColor by animateColorAsState( - targetValue = if (selected) Color.White else Color.Gray, + targetValue = if (selected) getForegroundColor( + backgroundColor = transportMode.colorCode.hexToComposeColor(), + foregroundColor = Color.White, + ) else Color.Gray, animationSpec = tween(200), ) @@ -80,11 +83,7 @@ fun TransportModeChip( adaptiveSize = true, ) - CompositionLocalProvider( - LocalTextColor provides textColor, - ) { - Text(text = transportMode.name) - } + Text(text = transportMode.name, color = textColor) } } } diff --git a/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/A11yColors.kt b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/A11yColors.kt index 7383239f..ca48dd36 100644 --- a/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/A11yColors.kt +++ b/taj/src/commonMain/kotlin/xyz/ksharma/krail/taj/theme/A11yColors.kt @@ -3,6 +3,13 @@ package xyz.ksharma.krail.taj.theme import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.luminance +// https://www.w3.org/TR/WCAG21/#contrast-minimum +private const val DEFAULT_TEXT_SIZE_CONTRAST_AA = 4.5f + +// when font scale greater than 1.2f. Text size is 18dp default and 14dp bold. +private const val LARGE_TEXT_SIZE_CONTRAST_AA = 3.0f + + /** * Calculates the contrast ratio between two colors */ @@ -16,11 +23,32 @@ fun Color.contrastRatio(other: Color): Float { * Returns a foreground color that has a contrast ratio of at least 4.0 with the provided background * color. */ -fun getForegroundColor(backgroundColor: Color): Color { +/** + * Determines the appropriate foreground color based on the provided background color. + * If a foreground color is provided, it checks the contrast ratio between the foreground + * and background colors. If the contrast ratio is sufficient (>= 4.0), it returns the + * provided foreground color. Otherwise, it defaults to checking predefined light and dark + * theme colors and returns the one with a sufficient contrast ratio. + * + * @param backgroundColor The background color to compare against. + * @param foregroundColor An optional foreground color to check for contrast ratio. + * @return The appropriate foreground color with sufficient contrast ratio. + */ +fun getForegroundColor( + backgroundColor: Color, + foregroundColor: Color? = null, +): Color { + // If a foreground color is provided, check its contrast ratio + foregroundColor?.let { color -> + if (color.contrastRatio(backgroundColor) >= DEFAULT_TEXT_SIZE_CONTRAST_AA) return color + } + + // Default to predefined light and dark theme colors val lightForegroundColor = md_theme_dark_onSurface val darkForegroundColor = md_theme_light_onSurface - return if (lightForegroundColor.contrastRatio(backgroundColor) >= 4.0f) { + // Return the color with a sufficient contrast ratio + return if (lightForegroundColor.contrastRatio(backgroundColor) >= DEFAULT_TEXT_SIZE_CONTRAST_AA) { lightForegroundColor } else { darkForegroundColor @@ -28,5 +56,5 @@ fun getForegroundColor(backgroundColor: Color): Color { } fun shouldUseDarkIcons(backgroundColor: Color): Boolean { - return getForegroundColor(backgroundColor) == md_theme_dark_onSurface + return getForegroundColor(backgroundColor = backgroundColor) == md_theme_dark_onSurface }