[BUG] dark-daltonized: userMessageBackground renders at 1.18:1 on a default dark terminal, leaving user messages with no visible separator

Status Open
Reported on v2.1.229
Maintainer reply ✓ Yes — bcherny
Activity 3 comments · opened Aug 12, 2026
💡 Likely answer: A maintainer (bcherny, collaborator) responded on this thread — see the highlighted reply below.

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

In viewMode: "focus", the only visual cue separating a user message from Claude's output is a background block painted with the userMessageBackground theme token. In both dark themes that token is rgb(55,55,55) (#373737).

Against a default dark terminal background, that block is not merely hard to see, it is imperceptible. Measured with WCAG 2.x relative luminance:

| Terminal background | Contrast of the #373737 block |
| --- | --- |
| Ghostty 1.3.1 default, #282c34 | 1.18:1 |
| Pure black, #000000 | 1.76:1 |
| VS Code dark+ default, #1f1f1f | 1.38:1 |

For reference, WCAG's floor for non-text UI elements is 3:1.

The text drawn on the block is not the problem. text is #ffffff, which is 11.90:1 against #373737. It is the block-versus-terminal-background separation that fails, which means the cue is absent rather than low quality. Nothing is drawn that a reader can find.

Why I think this is a bug and not a preference: it lands hardest in dark-daltonized, the theme labeled "Dark mode (colorblind-friendly)". Diffing that palette against plain dark, the tokens userMessageBackground, userMessageBackgroundHover, subtle, inactive, and text are identical. Every token the daltonized theme actually changes is a hue substitution.

So the theme advertised as colorblind-friendly leaves untouched the one cue in the transcript that carries no hue information at all. A user who cannot rely on the hue channel gets no benefit from the daltonized theme on the hardest part of reading a transcript, which is telling their own words apart from the assistant's at a glance. The theme's stated purpose and its actual effect diverge precisely where it matters most.

Same root cause, lower severity, mentioning it here only for context rather than as a second report: subtle is #505050, which is 1.74:1 against #282c34. Happy to file separately if you prefer strict one-token-per-issue.

What Should Happen?

The block behind user messages should be perceptible against a typical dark terminal background.

Worth knowing before picking a fix: there is a hard ceiling on the obvious approach, because the block's foreground is the global text token. The renderer draws Do("text", theme) over Do("userMessageBackground", theme, "background"), so raising the block's luminance to separate it from the terminal background lowers the contrast of the user's own typed text sitting on it, at close to a 1:1 exchange. Measured against a pure black terminal background:

| userMessageBackground | Block vs. background | White text on block |
| --- | --- | --- |
| #373737 (current) | 1.76:1 | 11.90:1 |
| #5a7596 | 4.42:1 | 4.75:1 |
| #63809f | 5.12:1 | 4.10:1 |
| #6c8aa8 | 5.84:1 | 3.60:1 |

#5a7596 is the brightest value where both numbers still clear 4.5:1. Past it, you are trading the legibility of the user's own text for the visibility of the block.

Three options, in my order of preference:

  1. Add a channel that is not color. A left rule, a border, or a gutter glyph on user messages would survive any terminal background, any theme, and any type of color vision. It is the only fix here with no ceiling, and it is the standard remedy for cue-carried-by-color-alone. Related in spirit: #83570 asks for a shape channel on session status for the same reason.
  2. Raise the dark-theme value to roughly #5a7596, which clears 3:1 against every common dark terminal background while keeping text on the block above 4.5:1.
  3. Decouple the block's foreground from text by giving it its own token. The current coupling is what creates the ceiling in the first place. This also composes with #77041, which asks for userMessageBackground to be split because it is shared with the conversation selector and the Plugin/Skill/MCP badges.

Option 1 plus option 3 would leave the palette free to be tuned without a trade-off.

Error Messages/Logs

Not applicable, this is a rendering issue with no error output.

Steps to Reproduce

  1. Use Ghostty 1.3.1 with a default config, or any terminal with a dark background near #282c34.
  2. In ~/.claude/settings.json, set "theme": "dark-daltonized" and "viewMode": "focus".
  3. Start a session and exchange a few messages.
  4. Scroll back through the transcript. There is no perceptible block behind the user messages, so in a long thread the only remaining separator is the You / Claude label, which is a hue cue.
  5. To confirm numerically rather than by eye:
def lum(h):
    h = h.lstrip('#')
    c = [int(h[i:i+2], 16) / 255 for i in (0, 2, 4)]
    c = [(v / 12.92 if v <= 0.04045 else ((v + 0.055) / 1.055) ** 2.4) for v in c]
    return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]

def contrast(a, b):
    la, lb = lum(a), lum(b)
    hi, lo = max(la, lb), min(la, lb)
    return (hi + 0.05) / (lo + 0.05)

print(round(contrast('#373737', '#282c34'), 2))  # 1.18  userMessageBackground vs Ghostty default
print(round(contrast('#505050', '#282c34'), 2))  # 1.74  subtle vs Ghostty default

Claude Model

Not model dependent.

Is this a regression?

Not that I can tell. #373737 appears to be the long-standing value.

Claude Code Version

2.1.229

Platform

macOS (Darwin 25.5.0), Ghostty 1.3.1, TERM=xterm-ghostty, COLORTERM=truecolor

Workaround, for anyone who finds this issue

Custom themes work today and can override the token, though the mechanism is undocumented. Create ~/.claude/themes/<slug>.json:

{
  "name": "High contrast",
  "base": "dark-daltonized",
  "overrides": {
    "userMessageBackground": "#5a7596",
    "userMessageBackgroundHover": "#6c8aa8",
    "briefLabelYou": "#8ecbff",
    "briefLabelClaude": "#ffc14d",
    "subtle": "#7d8590",
    "inactive": "#b0b8c2"
  }
}

Then set "theme": "custom:<slug>" in settings.json and restart. Accepted color formats are #rrggbb, #rgb, rgb(r,g,b), ansi256(n), and ansi:<name>. Note that unrecognized override keys are discarded silently, so a typo degrades contrast without any warning.

The briefLabelYou and briefLabelClaude values above are a blue/amber pair chosen so the label cue also survives dichromacy. Simulated with the Viénot 1999 model, the CIELAB delta E between them is 96.2 under protanopia, 107.5 under deuteranopia, and 54.2 under tritanopia, versus 65.3 / 79.8 / 69.6 for the stock daltonized pair.

Related issues

  • #77041, userMessageBackground is a shared token and should be split
  • #77129, distinguishing user prompts from assistant responses in long threads
  • #83570, session status encoded by color alone, add a shape channel
  • #32880, low-contrast syntax highlighting in the colorblind theme (closed as duplicate)

View original on GitHub ↗

3 Comments

samgutentag · 18 days ago

Correcting my own reproduction step 4, which overstated what the default configuration gives you.

I wrote that when the block is imperceptible, "the only remaining separator is the You / Claude label." That is wrong. In a default install those labels do not render at all.

The brief layout that draws them is gated:

jFv = Kbe() && (WiP || rt("tengu_kairos_brief", !1)) && $vm && !Bvm && !Uvm
//            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^  WiP = env.CLAUDE_CODE_BRIEF

So the label layout needs either CLAUDE_CODE_BRIEF in the environment or the tengu_kairos_brief gate, which defaults to false. Neither is set on a default install, viewMode: "focus" alone does not enable it.

And the block and the labels are mutually exclusive by construction, one line later in the same component:

const Dqn = Pqn ? void 0 : "userMessageBackground"

Brief layout suppresses the block and shows the labels. Without brief layout you get the block and no You label. You never get both.

The consequence is that this is somewhat worse than I first described, not better. In the default configuration the 1.18:1 block is the only separator between user and assistant turns, and when it is imperceptible there is no fallback cue of any kind. The label pair I referenced as a mitigation is latent, not active.

One related inconsistency I noticed while tracing this. The assistant-side label checks isBriefOnly on its own:

if (r?.isBriefOnly) { /* renders <Text color="briefLabelClaude">Claude</Text> */ }

while the user-side label and the block suppression check isBriefOnly and the gate. So the two sides can disagree about which layout is active, and a configuration exists where the Claude label renders but the You label does not. Happy to split that into its own issue if it is worth tracking separately.

bcherny collaborator · 14 days ago

Reproduced on 2.1.233 (Linux). With theme dark-daltonized (same for dark), the user-message block renders at rgb(55,55,55) — captured as 48;2;55;55;55 on truecolor terminals, 48;5;237 (≈rgb(58,58,58)) when quantized to 256 colors.

Measured WCAG contrast of the block vs common terminal backgrounds: 1.18:1 on #282c34, 1.76:1 on #000000, 1.38:1 on #1f1f1f — all below the 3:1 guideline for non-text UI, so your numbers check out (text on the block is fine at 11.9:1).

Two notes: user messages also have a prefix, so the background isn't the sole marker, and there's no version where this value was different — it's a longstanding default, not a regression. A fixed grey can't clear 3:1 against every dark terminal background, so we're considering a lighter default and/or deriving it from the detected terminal background.

In the meantime you can override it today with a custom theme: /theme → create a custom theme and set the userMessageBackground key (see https://code.claude.com/docs/en/terminal-config).

🤖 Generated with Claude Code

samgutentag-deepgram · 13 days ago

Ah yes, for the meantime i created this terminal config to adapt the colors for my needs.

reference: https://github.com/samgutentag/claude-code-contrast