Use WCAG contrast ratio for calendar foreground color#67
Conversation
Calendar text/icon foreground was chosen by thresholding Horde_Image::brightness() (legacy YIQ), which picks the lower-contrast option for some saturated colors — e.g. white on bright magenta (#fb00ec) has a WCAG ratio of 3.33, below the AA minimum of 4.5. Switch the three PHP call sites (Kronolith::foregroundColor, Kronolith_Calendar::foreground, Kronolith_Event icon color) to Horde_Image::contrastColor(), which compares the actual WCAG contrast ratio of both candidates. Add a matching JS helper KronolithCore.contrastColor() for the color preview in the calendar dialog. Requires horde/Image#8 (adds contrastColor()/relativeLuminance()).
There was a problem hiding this comment.
Pull request overview
This PR updates Kronolith’s calendar foreground-color selection to use WCAG contrast ratio logic (instead of a legacy brightness threshold), improving readability for saturated background colors and aligning server/client behavior.
Changes:
- Replace
Horde_Image::brightness()thresholding withHorde_Image::contrastColor()in three PHP call sites that choose calendar/event foreground colors. - Add a JavaScript
KronolithCore.contrastColor()helper (WCAG luminance/contrast) and use it for the live calendar color preview.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| lib/Kronolith.php | Uses Horde_Image::contrastColor() for Kronolith::foregroundColor() selection. |
| lib/Event.php | Uses Horde_Image::contrastColor() to pick the event icon (fff/000) variant for better contrast. |
| lib/Calendar.php | Uses Horde_Image::contrastColor() for calendar foreground selection. |
| js/kronolith.js | Adds KronolithCore.contrastColor() and switches the dialog preview to use it. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| public static function foregroundColor($calendar) | ||
| { | ||
| return Horde_Image::brightness(is_string($calendar) ? $calendar : self::backgroundColor($calendar)) < 128 ? '#fff' : '#000'; | ||
| return Horde_Image::contrastColor(is_string($calendar) ? $calendar : self::backgroundColor($calendar), '#fff', '#000'); |
There was a problem hiding this comment.
Fixed in 9f7a090 — the call is now guarded with method_exists('Horde_Image', 'contrastColor') and falls back to the legacy brightness() threshold when the method isn't available, so this works against older horde/image and is safe to merge independently of horde/Image#8.
| public function foreground() | ||
| { | ||
| return Horde_Image::brightness($this->background()) < 128 ? '#fff' : '#000'; | ||
| return Horde_Image::contrastColor($this->background(), '#fff', '#000'); |
There was a problem hiding this comment.
Fixed in 9f7a090 — the call is now guarded with method_exists('Horde_Image', 'contrastColor') and falls back to the legacy brightness() threshold when the method isn't available, so this works against older horde/image and is safe to merge independently of horde/Image#8.
|
|
||
| if ($icons && $prefs->getValue('show_icons')) { | ||
| $icon_color = Horde_Image::brightness($this->_backgroundColor) < 128 ? 'fff' : '000'; | ||
| $icon_color = Horde_Image::contrastColor($this->_backgroundColor, 'fff', '000'); |
There was a problem hiding this comment.
Fixed in 9f7a090 — the call is now guarded with method_exists('Horde_Image', 'contrastColor') and falls back to the legacy brightness() threshold when the method isn't available, so this works against older horde/image and is safe to merge independently of horde/Image#8.
Per review: guard the three call sites so kronolith keeps working against an older horde/image that doesn't yet provide contrastColor(). When the method is present, use the WCAG ratio; otherwise fall back to the legacy brightness threshold. Makes this change safe to merge independently of horde/Image#8.
Cover Kronolith::foregroundColor(), calendar foreground(), and event icon color candidates. Include the bright-magenta regression where legacy brightness() picks white but WCAG contrast picks black.
Calendar text and icon foreground colors were chosen by thresholding
Horde_Image::brightness()(legacy YIQ). For some saturated backgrounds thispicks the lower-contrast foreground — e.g. white text on bright magenta
#fb00echas a WCAG contrast ratio of only 3.33, below the AA minimum of4.5, so the text is hard to read.
Change
Switch the three PHP call sites to
Horde_Image::contrastColor(), whichcompares the actual WCAG contrast ratio of both candidate colors:
Kronolith::foregroundColor()Kronolith_Calendar::foreground()Kronolith_Eventicon colorAdd a matching JS helper
KronolithCore.contrastColor()for the live colorpreview in the calendar dialog, so client and server agree.
For
#fb00ec, foreground is now black (ratio 6.31) instead of white.Dependency
Requires horde/Image#8, which adds
contrastColor()andrelativeLuminance(). Existing return values are unchanged for colors thatwere already correct, so this is backward compatible.
Testing
Verified on a live H6 dev instance: a magenta calendar now renders readable
(black) text; previously-correct colors (blue, dark green → white) are
unchanged.