Skip to content

♿ Improved newsletter email footer text contrast - #29670

Closed
jatinder14 wants to merge 1 commit into
TryGhost:mainfrom
jatinder14:fix/email-footer-contrast
Closed

♿ Improved newsletter email footer text contrast#29670
jatinder14 wants to merge 1 commit into
TryGhost:mainfrom
jatinder14:fix/email-footer-contrast

Conversation

@jatinder14

Copy link
Copy Markdown
Contributor

Summary

  • Newsletter email footers used `rgba(0, 0, 0, 0.6)` / `rgba(255, 255, 255, 0.6)`, which often renders near `#738A94` (~3.6:1 on white) and fails WCAG 1.4.3 for small text including the unsubscribe link.
  • Switched to solid `#5B6B73` (light) and `#ADB5BD` (dark), both above 4.5:1 against their backgrounds.

Test plan

  • Send / preview a newsletter email on a light background — footer + unsubscribe readable, contrast ≥ 4.5:1
  • Same check on a dark newsletter background

Closes #29666

Made with Cursor

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
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Updated the email footer partial so both footer text and footer links use #ADB5BD when backgroundIsDark is true and #5B6B73 otherwise.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly reflects the main change: improving newsletter email footer contrast.
Description check ✅ Passed The description matches the change and issue by explaining the contrast fix for footer text and the unsubscribe link.
Linked Issues check ✅ Passed The updated footer and link colors directly address the linked contrast issue for small email footer text.
Out of Scope Changes check ✅ Passed The PR only changes email footer styling relevant to the contrast fix and adds no obvious unrelated scope.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

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

📥 Commits

Reviewing files that changed from the base of the PR and between e1d00a5 and 7559f3c.

📒 Files selected for processing (1)
  • ghost/core/core/server/services/email-rendering/partials/base-styles.hbs

Comment on lines +94 to +96
color: #ADB5BD;
{{else}}
color: #15212a;
color: rgba(0, 0, 0, 0.6);
color: #5B6B73;

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.

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.

[a11y] Email footer text doesn't have enough contrast against the background

2 participants