Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,9 @@ table td {

.footer {
{{#if backgroundIsDark}}
color: #ffffff;
color: rgba(255, 255, 255, .6);
color: #ADB5BD;
{{else}}
color: #15212a;
color: rgba(0, 0, 0, 0.6);
color: #5B6B73;
Comment on lines +94 to +96

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.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
fd -a 'base-styles\.hbs$' . || true

echo "== file excerpt =="
FILE="ghost/core/core/server/services/email-rendering/partials/base-styles.hbs"
wc -l "$FILE" 2>/dev/null || true
sed -n '70,125p' "$FILE" 2>/dev/null || true

echo "== search for backgroundIsDark/isDark/backgroundColor =="
rg -n "backgroundIsDark|isDark\(|backgroundColor|base-styles\.hbs|email-rendering" ghost/core -S --glob '!**/node_modules/**' | sed -n '1,220p'

Repository: TryGhost/Ghost

Length of output: 18151


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== email-design.js outline/excerpts =="
ast-grep outline ghost/core/core/server/services/email-rendering/email-design.js --view expanded || true
sed -n '1,230p' ghost/core/core/server/services/email-rendering/email-design.js

echo "== color utils/functions definitions =="
rg -n "function textColorForBackgroundColor|const textColorForBackgroundColor|function isDark|const isDark|isDark \\(" ghost/core/core/server/services/email-rendering -S
sed -n '1,140p' ghost/core/core/server/services/email-rendering/color.js 2>/dev/null || true
rg -n "textColorForBackgroundColor|isDark" ghost/core/core/frontend/helpers -S --glob '*color*.js' -A 20 -B 5

echo "== tests around email design palette =="
sed -n '90,145p' ghost/core/test/unit/server/services/email-rendering/email-design.test.js
sed -n '2978,3065p' ghost/core/test/unit/server/services/email-service/email-renderer.test.js

Repository: TryGhost/Ghost

Length of output: 14437


🌐 Web query:

@tryghost color-utils textColorForBackgroundColor WCAG contrast default algorithm

💡 Result:

The Ghost CMS contrast_text_color helper, which utilizes the textColorForBackgroundColor function from the @tryghost/color-utils package, uses a YIQ-based brightness formula to determine whether to return black (#000000) or white (#FFFFFF) text for a given background color [1][2][3]. The algorithm calculates the YIQ luminance of the background color using the following weighted sum of the RGB channels [1]: Luminance = (Red * 0.299) + (Green * 0.587) + (Blue * 0.114) If the resulting value exceeds a certain threshold (typically designed to favor legibility against white/black), it returns black text; otherwise, it returns white text [4][5]. Technical Note: A known issue existed in the @tryghost/color-utils package where the YIQ formula incorrectly accessed the blue channel using a .b() method (which referred to a different color component) rather than the .blue() method [1][4]. This caused the luminance calculation to be incorrect for many colors, resulting in poor contrast selections for certain shades (e.g., specific pastels) [4][6]. This was identified as a bug and subsequently corrected to ensure the formula uses the correct RGB blue channel [1]. Themes typically use this helper in their Handlebars templates to ensure accessibility when using dynamic brand or accent colors, as shown in the following example [2][3]: Subscribe

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "== package references for color-utils =="
rg -n '"`@tryghost/color-utils`"|color-utils' package.json pnpm-lock.yaml ghost -S --glob '!**/node_modules/**' | sed -n '1,120p'

echo "== local candidates =="
fd -a 'color.*|^color.*\.(js|ts|cjs|mjs)$|text.*|contrast.*' ghost -S --type f | sed -n '1,120p'

Repository: TryGhost/Ghost

Length of output: 1215


🌐 Web query:

@tryghost/color-utils textColorForBackgroundColor source

💡 Result:

The textColorForBackgroundColor function is located within the @tryghost/color-utils package [1][2]. The function uses a YIQ-based luminance calculation to determine whether white or black text provides better contrast against a given background color [1]. As of May 2026, it was identified that the implementation contained a bug where the formula incorrectly called.b (a different color component) instead of.blue when calculating the blue channel contribution to the YIQ luminance [1][2][3]. This typo resulted in incorrect contrast color selection for certain background colors (e.g., light pastels) [4][3]. A fix for this issue was submitted in the TryGhost/SDK repository (PR #905), which updates the calculation to use.blue instead of.b to ensure accurate YIQ contrast logic [1]. This utility is used by the Ghost {{contrast_text_color}} Handlebars helper, which provides readable text colors for themes [5][6].

Citations:


🌐 Web query:

github tryghost color-utils textColorForBackgroundColor textColorForBackgroundColor

💡 Result:

The textColorForBackgroundColor function is a utility within the @tryghost/color-utils package used by Ghost to determine the appropriate text color (black or white) based on a given background color, ensuring sufficient contrast [1][2]. A known issue involving this function was identified where the YIQ luminance calculation incorrectly called .b() instead of .blue() [3][1]. The .b() method mistakenly retrieved the HSB brightness component rather than the blue RGB channel, leading to incorrect luminance values and faulty text color selection (e.g., returning white text instead of black for certain light pastel backgrounds) [3][1]. This logic is used by the {{contrast_text_color}} Handlebars helper in Ghost themes [1][4]. A fix for this issue was proposed in the TryGhost/SDK repository, which replaces the .b() call with .blue() to ensure the YIQ formula accurately processes the RGB values [3].

Citations:


🌐 Web query:

pnpm color-utils@0.2.19 textColorForBackgroundColor

💡 Result:

The textColorForBackgroundColor function is part of the @tryghost/color-utils package, and specifically in version 0.2.19, it contains a known bug [1][2]. In this version, the implementation of textColorForBackgroundColor incorrectly calculates the YIQ luminance because it calls the .b() method instead of the .blue() method on the background color object [3][1]. The .b() method refers to a different color component (likely related to Lab color space), whereas the YIQ luminance formula requires the RGB blue channel [3]. This error causes the function to return incorrect contrast colors for many backgrounds, often defaulting to white text on light pastel backgrounds instead of the expected black text [1][2]. This issue has been identified and addressed in the Ghost development ecosystem, with recommendations to use .blue() to accurately compute the YIQ luminance [3].

Citations:


🌐 Web query:

github TryGhostSDK package color-utils@0.2.19 textColorForBackgroundColor

💡 Result:

The textColorForBackgroundColor function within the @tryghost/color-utils package (part of the Ghost SDK) was identified as containing a bug in versions prior to its fix [1][2]. In earlier versions, including 0.2.19, the YIQ luminance calculation inside textColorForBackgroundColor incorrectly called the .b() method on the background color object [3][1]. This method, which refers to a Lab color space component, was used instead of the .blue() method intended to access the blue RGB channel [3]. As a result, the function produced incorrect text color contrasts for many background colors (e.g., returning white instead of black for certain light pastel backgrounds) [1][2]. This issue was addressed in the TryGhost/SDK repository via a pull request that updated the calculation to correctly reference backgroundColor.blue() [3]. Users experiencing this issue are advised to ensure they are using a version of the package where this fix has been implemented [3].

Citations:


🌐 Web query:

github tryghost color-utils@0.2.19 textColorForBackgroundColor

💡 Result:

In the @tryghost/color-utils package, version 0.2.19, the textColorForBackgroundColor function contained a bug where it incorrectly used .b() instead of .blue() when calculating the YIQ color space luminance [1][2][3]. The function is designed to determine whether to return white or black text based on a provided background color to ensure readability [4][5]. Because .b() (which refers to the Lab color space b-channel) was used instead of the RGB blue channel, the YIQ formula produced incorrect results for many background colors, often causing the helper to return white text for light pastel backgrounds where black text would be more appropriate [1][2][3]. This issue was identified and documented in Ghost repository issue #27797, and a fix was proposed in the TryGhost/SDK repository (pull request #905), which updates the calculation to use backgroundColor.blue() [1][2][3]. Version 0.2.19 of @tryghost/color-utils is the version affected by this bug [1][2][6].

Citations:


Derive footer text colors from actual background contrast.

backgroundColor accepts any valid hex string, but .footer and .footer a only choose between #ADB5BD and #5B6B73 based on backgroundIsDark, which is based on textColorForBackgroundColor()’s black/white YIQ selection. Those fixed semi-muted colors don’t guarantee 4.5:1 for all valid backgrounds in either branch, so add contrast regression coverage or base the footer/link colors on WCAG contrast from the actual background color.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ghost/core/core/server/services/email-rendering/partials/base-styles.hbs`
around lines 94 - 96, Update the footer and footer-link color selection in the
base styles to derive colors from WCAG contrast against the actual
backgroundColor, rather than relying solely on backgroundIsDark and the fixed
`#ADB5BD/`#5B6B73 values. Ensure both .footer and .footer a meet the required
4.5:1 contrast ratio for every valid hex background, and add regression coverage
for representative backgrounds and both branches.

{{/if}}
margin-top: 20px;
text-align: center;
Expand All @@ -109,11 +107,9 @@ table td {

.footer a {
{{#if backgroundIsDark}}
color: #ffffff;
color: rgba(255, 255, 255, .6);
color: #ADB5BD;
{{else}}
color: #15212a;
color: rgba(0, 0, 0, 0.6);
color: #5B6B73;
{{/if}}
text-decoration: underline;
font-size: 13px;
Expand Down
Loading