Skip to content

Use WCAG contrast ratio for calendar foreground color#67

Merged
TDannhauer merged 3 commits into
horde:FRAMEWORK_6_0from
pierrefardel:feature/wcag-contrast
Jul 2, 2026
Merged

Use WCAG contrast ratio for calendar foreground color#67
TDannhauer merged 3 commits into
horde:FRAMEWORK_6_0from
pierrefardel:feature/wcag-contrast

Conversation

@pierrefardel

Copy link
Copy Markdown
Contributor

Calendar text and icon foreground colors were chosen by thresholding
Horde_Image::brightness() (legacy YIQ). For some saturated backgrounds this
picks the lower-contrast foreground — e.g. white text on bright magenta
#fb00ec has a WCAG contrast ratio of only 3.33, below the AA minimum of
4.5, so the text is hard to read.

Change

Switch the three PHP call sites to Horde_Image::contrastColor(), which
compares the actual WCAG contrast ratio of both candidate colors:

  • Kronolith::foregroundColor()
  • Kronolith_Calendar::foreground()
  • Kronolith_Event icon color

Add a matching JS helper KronolithCore.contrastColor() for the live color
preview 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() and
relativeLuminance(). Existing return values are unchanged for colors that
were 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.

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()).
Copilot AI review requested due to automatic review settings July 2, 2026 07:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 with Horde_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.

Comment thread lib/Kronolith.php Outdated
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');

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread lib/Calendar.php Outdated
public function foreground()
{
return Horde_Image::brightness($this->background()) < 128 ? '#fff' : '#000';
return Horde_Image::contrastColor($this->background(), '#fff', '#000');

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread lib/Event.php Outdated

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');

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

pierrefardel and others added 2 commits July 2, 2026 10:09
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.
@TDannhauer TDannhauer merged commit 42019f6 into horde:FRAMEWORK_6_0 Jul 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants