It just has to stop putting white text on it. Dark mode worked this out already — and so, quietly, did the cookie banner.
Outcome (2026-08-26): built, rendered, rejected. The flip was shipped
into the 29 app-v35 prototype screens so it could be judged in a real interface rather
than as a swatch. Verdict on sight: “green with black text — it’s odd.”
Dark-on-bright carries a small dense control like the consent button and does not survive
being scaled up to a primary CTA. Light reverts to #0f766e + white
(5.47:1); dark keeps its flip unchanged. The measurements below all still hold —
which is the point of the page: the numbers cleared it and looking at it did not, so the
numbers were never going to be the deciding input.
One durable finding survives the revert: the
brand green cannot carry white text. The brightest teal keeping white at AA is
#0e857b (4.51:1) — too close to the line to ship, given --indigo
shipped broken at 4.467:1. With real headroom the ceiling is about #0e8077
(4.80:1), only marginally brighter than #0f766e. That is a property of the
colour, not a decision available to anyone.
Two bright green buttons sit on the stealth page. One of them is broken and one of them is correct, and the difference between them is the ink.
The bright teal was never the problem. White ink on it is. The consent
banner has been shipping bright #0d9488 with near-black ink in light mode this
whole time, and it passes — which makes the ink flip a thing Relay already does, not a thing
Relay is being asked to try.
Every ratio below is computed, not estimated. A button carries two contrast obligations, and measuring only the first is how the muted value got chosen in the first place — ink vs fill ≥ 4.5:1 so the label is readable (WCAG 1.4.3), and fill vs page ≥ 3:1 so the button is findable at all (WCAG 1.4.11).
Correction, 2026-08-08. An earlier version of this page recommended
#2dd4bf for light on the strength of its 9.94:1 label contrast, and called it
“the brightest option is also the most accessible.” That measured the label only. Against a
white page the fill is 1.86:1 — far under the 3:1 a UI component
needs — so the button stops reading as an object. Bright teal needs a dark ground to be
findable, which is exactly why dark mode can use it and light mode cannot. The light
answer is #0d9488 + #0d1514 (ratified as v3.6); dark keeps
#2dd4bf + #0d1514, unchanged. They do not converge.
A swatch proves a ratio; it does not prove a look. Same fragment of app UI, with and without the flip.
Deliberately unchanged in the flip: Codes stay #0f766e.
A Code is text, and #0d9488 as text is 3.74:1. The flip applies to
fills — buttons and active states — never to text. That role split is the part of the
v3.5 system that is already right and should not be touched.
Every semantic colour in light mode lands at roughly the same contrast against white. Colours of equal contrast read at equal loudness — so a screen using several of them has no hierarchy at all. It reads as mud.
| Light token | Value | On white | Perceived weight |
|---|---|---|---|
| accent-text | #0f766e | 5.47:1 | |
| success | #15803d | 5.02:1 | |
| warning | #b45309 | 5.02:1 | |
| error | #dc2626 | 4.83:1 | |
| indigo | #585fee | 4.90:1 |
Dark mode has no such flattening — it runs 10.44 / 11.15 / 11.64 / 7.03, with
real separation between them. tokens.css even labels the dark block
“contrast mode; canonical orientation.” Light mode is the weaker theme, and it is
weaker because every hue was darkened until it cleared 4.5:1 on white, draining all of them
at once.
This is the real reason the app feels busier than the marketing page. The marketing page is near-black text, grey, hairlines and exactly one teal button. The app spends colour everywhere — and in light mode every colour costs the same, so none of it ranks. Fixing the button is worth doing. Fixing the hierarchy is worth more.
Correction, 2026-08-26 — the precedent. This page opens on the claim
that the ink flip is “a thing Relay already does” because the cookie banner
“quietly” ships near-black #001311 on #0d9488 in light mode at
5.09:1. The arithmetic is right — that pair does measure 5.09:1 — but no implementation
ships it. Three were checked, plus the history of the value across the estate:
relay-app’s consent bar is the only one using #001311, and it sets it on
#2dd4bf (10.24:1) inside a dark-styled bar; the marketing banner used
#0d9488 under white (3.74:1 — the failure, since fixed); the platform’s
own script uses a green-tinted ghost button, not a filled one. So the flip was a proposal
after all, not an observation. It does not change the outcome — the flip was built and
rejected on sight regardless — but the page’s founding argument does not hold, and neither
does the same sentence in the design history.
Raised on sight of the live stealth page — “isn’t that our main green now?
What about the marks?” It is a fair read: on one screen the mark draws
--rly-accent-ui and the primary button draws --rly-accent-fill,
and since RCTX-240 (2026-08-12) those have not been the same value. §2 asks what
colour the button should be. This asks whether it should match the mark at all — which is
only visible when the two are shown together, so they are.
There is no option where the button matches the mark and the page still passes — except one nobody has asked for. D is the 3.74:1 failure RCTX-240 was ratified to remove. B is the flip: it passes both obligations and was rejected on sight (see the top of this page), so it is closed on appearance, not on numbers. C is the only untried route — but it moves the identity value rather than a UI one: the mark, the favicon, the app icons, the social card and every PNG export would be redrawn and re-issued, and the brand would sit a step darker everywhere. That is a brand decision, not a contrast one.
And the gap is smaller than the token names suggest. Rendered at mark scale the two values barely separate — A and C above are near-indistinguishable in the lockup, and the difference only becomes legible at button size, where the fill covers enough area to read as a tone. Whatever this is, it is not a clash.
Worth knowing before treating this as a defect: the split is
light-mode only. In dark, --rly-accent-ui,
--rly-accent-text and --rly-accent-fill are all
#2dd4bf — one green, no split. And the role split is the part of v3.5 that is
already right: the mark is non-text so it may be the brighter value, while anything carrying
a label must clear 4.5:1 against it. A is what that principle produces. It is a
consequence, not an oversight.
Bright teal with near-black ink, matching dark mode and the consent banner. Brighter than today and measurably more accessible. The counter-argument is that dark-on-bright reads as a highlighter if it spreads beyond primary actions — so it is scoped to one primary button per surface, and active nav.
Settled by measurement rather than taste: #2dd4bf is only 1.86:1 against a
white page, so the button ceases to be findable as a component regardless of how good its
label contrast is. #0d9488 passes both obligations (4.94 label / 3.75 page)
and keeps the brand green. Dark mode keeps #2dd4bf — on a dark ground it is
10.44:1 and entirely correct.
Bigger than the button question and independent of it. Would mean pulling most status colour out of light mode entirely — status carried by label and weight, colour reserved for the few states that genuinely need to interrupt. This is the change that would make the app feel like the marketing page.