Skip to main content

Fix WordPress FSE Theme Footer Text Visibility - WCAG Contrast Issue

· 3 min read

While developing a WordPress FSE enterprise theme for a client, I discovered the Footer block text was nearly invisible across multiple Style Variations. This post documents the complete fix process - from WCAG contrast diagnosis to introducing semantic colors and handling global styles override.

TL;DR

Problem: FSE theme's contrast color token has conflicting semantics. In light themes, contrast ≈ light gray ≈ base (white), resulting in Footer contrast ratio of only 1.05:1.

Solution:

  1. Introduce surface semantic color for dark block backgrounds
  2. Delete override styles in wp_global_styles
  3. Add surface definition to all Style Variations

Result: Contrast ratio improved from 1.05:1 to 15.8:1 (WCAG AAA grade).

Problem

Footer block uses backgroundColor="contrast" + textColor="base":

<!-- wp:group {"backgroundColor":"contrast","textColor":"base"} -->
<div class="has-base-color has-contrast-background-color">
Footer content
</div>

Under default theme, Footer text is nearly invisible:

CombinationForegroundBackgroundContrastWCAG
Footer text#ffffff (base)#f8fafc (contrast)1.05:1❌ Fail
Footer link#f59e0b (accent)#f8fafc (contrast)1.78:1❌ Fail

WCAG AA standard requires ≥ 4.5:1 for normal text. Current state is far below standard.

Root Cause Analysis

1. Conflicting contrast Semantics

contrast was designed as "background color that contrasts with base", but semantics conflict across themes:

VariationbasecontrastExpected vs Actual
Default (light)#ffffff white#f8fafc light grayExpected dark, actual light
Tech (dark)#0f0f1a deep black#1e1e2e deep purpleExpected light, actual dark

Footer Pattern assumes contrast is a dark background, but in 5/6 Style Variations it's light.

2. Missing Semantic Color for Dark Blocks

Original design system only had one "contrast" color, without distinguishing:

  • Light contrast blocks (CTA Banner, etc.)
  • Dark contrast blocks (Footer, dark Hero, etc.)

Solution

Step 1: Introduce surface Semantic Color

Add surface token in theme.json for dark block backgrounds:

{
"slug": "surface",
"color": "#0f172a",
"name": "Surface"
}

Step 2: Update All Style Variations

Each variation defines its own surface color (usually equals primary):

// styles/commerce.json
{ "slug": "surface", "color": "#1f2937", "name": "Surface" }

// styles/nature.json
{ "slug": "surface", "color": "#14532d", "name": "Surface" }

// styles/tech.json (dark theme)
{ "slug": "surface", "color": "#1e1e2e", "name": "Surface" }
<!-- wp:group {"backgroundColor":"surface","textColor":"base"} -->
<div class="has-base-color has-surface-background-color">
Footer content
</div>

Step 4: Delete Global Styles Override

Colors still not working after modifying theme.json? Check global styles:

# Check if global styles exist
docker exec wp_cli wp post list --post_type=wp_global_styles --fields=ID,post_title --allow-root

# Delete global styles
docker exec wp_cli wp post delete <ID> --force --allow-root
docker exec wp_cli wp cache flush --allow-root

Reason: color.palette in wp_global_styles completely overrides (not merges) theme.json's palette.

Results

Variationsurface + base ContrastWCAG Grade
Default15.8:1✅ AAA
Commerce13.1:1✅ AAA
Industrial12.6:1✅ AAA
Professional9.9:1✅ AAA
Nature10.8:1✅ AAA
Tech11.5:1✅ AAA

Color Semantics Summary

TokenPurpose
primaryBrand color (Logo, primary button)
secondarySecondary elements
accentCall to action (CTA, links)
basePage main background
contrastLight contrast block background
surfaceDark block background (Footer, dark CTA) ← New

Interested in similar solutions? Get in touch