♿ Improved newsletter email footer text contrast - #29670
Conversation
Replace translucent footer greys with solid colors that meet WCAG 1.4.3 (4.5:1) on light and dark newsletter backgrounds. The previous rgba(0,0,0,.6) rendered around #738A94 (~3.6:1) in email clients. Closes #29666
WalkthroughUpdated the email footer partial so both footer text and footer links use 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with 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.
Inline comments:
In `@ghost/core/core/server/services/email-rendering/partials/base-styles.hbs`:
- Around line 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.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 104fa06c-0805-45cf-aee7-07cb201b3265
📒 Files selected for processing (1)
ghost/core/core/server/services/email-rendering/partials/base-styles.hbs
| color: #ADB5BD; | ||
| {{else}} | ||
| color: #15212a; | ||
| color: rgba(0, 0, 0, 0.6); | ||
| color: #5B6B73; |
There was a problem hiding this comment.
🎯 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.jsRepository: 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:
- 1: Use .blue() instead of .b() in textColorForBackgroundColor SDK#905
- 2: https://docs.ghost.org/themes/helpers/utility/contrast_text_color
- 3: https://themeix.com/blog/improve-readability-in-your-ghost-theme-design/
- 4: {{contrast_text_color}} helper returns incorrect text color #27797
- 5: https://gist.github.com/Louis-7/965942c0045d5379f6f847e4178c82b8
- 6: {{contrast_text_color}} helper returns incorrect text color #27797
🏁 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:
- 1: Use .blue() instead of .b() in textColorForBackgroundColor SDK#905
- 2: {{contrast_text_color}} helper returns incorrect text color #27797
- 3: {{contrast_text_color}} helper returns incorrect text color #27797
- 4: {{contrast_text_color}} helper returns incorrect text color #27797
- 5: https://docs.ghost.org/themes/helpers/utility/contrast_text_color
- 6: https://docs.ghost.org/themes/helpers/utility
🌐 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:
- 1: {{contrast_text_color}} helper returns incorrect text color #27797
- 2: {{contrast_text_color}} helper returns incorrect text color #27797
- 3: Use .blue() instead of .b() in textColorForBackgroundColor SDK#905
- 4: https://themeix.com/blog/improve-readability-in-your-ghost-theme-design/
🌐 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:
- 1: {{contrast_text_color}} helper returns incorrect text color #27797
- 2: {{contrast_text_color}} helper returns incorrect text color #27797
- 3: Use .blue() instead of .b() in textColorForBackgroundColor SDK#905
🌐 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:
- 1: {{contrast_text_color}} helper returns incorrect text color #27797
- 2: {{contrast_text_color}} helper returns incorrect text color #27797
- 3: Use .blue() instead of .b() in textColorForBackgroundColor SDK#905
🌐 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:
- 1: {{contrast_text_color}} helper returns incorrect text color #27797
- 2: Use .blue() instead of .b() in textColorForBackgroundColor SDK#905
- 3: {{contrast_text_color}} helper returns incorrect text color #27797
- 4: https://docs.ghost.org/themes/helpers/utility
- 5: https://themeix.com/blog/improve-readability-in-your-ghost-theme-design/
- 6: TryGhost/SDK@70a16e4
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.
Summary
Test plan
Closes #29666
Made with Cursor