Skip to content

fix: bridge .usa-dark-background onto the HDS dark palette - #179

Open
suthat wants to merge 1 commit into
nasa:mainfrom
suthat:fix/uswds-dark-background-bridge
Open

fix: bridge .usa-dark-background onto the HDS dark palette#179
suthat wants to merge 1 commit into
nasa:mainfrom
suthat:fix/uswds-dark-background-bridge

Conversation

@suthat

@suthat suthat commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Picks up #177 and closes the USWDS dark-context family: .usa-dark-background now gets the same treatment .usa-hero__callout and .usa-section--dark got in #166.

Closes #177

What I found, which isn't quite what the issue says

The issue predicted this one would be the mild case — no red block, just cyan headings and a surface that isn't HDS's designed dark. I built the CSS and measured it before touching anything, and it's the other way round: this is the worst of the three.

.usa-dark-background doesn't set heading colors at all. There's no cyan. The whole rule is:

.usa-dark-background {
  background-color: color("base-darker");
  p, span { color: color($theme-text-reverse-color); }
  a { @include set-link-from-bg("base-darker", $theme-link-reverse-color, ...); }
}

So USWDS reverses <p>, <span>, and bare <a> — and stops. Headings and every HDS component inside keep reading --hds-palette-* from whatever palette wraps the page. On the default white palette, that's a Carbon Black <h2> on a #1b1b1b background.

Measured in Chromium against the built CSS, all six palettes:

Element Before (white / light / midtone) After (all six)
Surface USWDS base-darker #1b1b1b Carbon 90 #17171b
Heading Carbon Black, 1.22:1 White, 17.9:1
.usa-link / prose link Carbon 90, 1.04:1 White, 17.9:1
.usa-button--outline label Carbon Black, 1.22:1 White, 17.9:1
.usa-button--outline border NASA Blue #1c67e3, 3.5:1 unchanged, 3.5:1
<p>, <span>, bare <a> White (USWDS pins these), 17.2:1 White, 17.9:1

1.04:1 is a link rendered in a colour four RGB points from its own background. The dark, blue and black palettes were already fine — which is exactly why this survived #166. If you happened to test inside a dark wrapper, nothing looked wrong.

The decision

Acceptance criterion 1 was "decide whether it's auto-bridged, and onto which surface." Bridged, onto hds-palette-dark, same as the other two. Reasons, shortest first:

  1. The surface barely moves. base-darker themes to #1b1b1b; Carbon 90 is #17171b. Adopters won't see the difference, so unlike fix: map USWDS hero callout and dark sections onto the HDS dark palette #166 this carries essentially no visual-change risk while fixing a 1.04:1 link.
  2. It's the same family. Three USWDS contexts paint their own dark background; two are bridged. Leaving the third on the manual path is the inconsistency the issue was filed about.
  3. A wrapper can't fix it either. Same reason as fix: map USWDS hero callout and dark sections onto the HDS dark palette #166 — the background is a background-color on the component, not a palette custom property.

The code change is the one-line selector addition #177 predicted:

:where(.usa-hero__callout, .usa-section--dark, .usa-dark-background) {

Everything else is the print reset, the story, and docs.

Print

Same trap as #166. Browsers drop background colors, so a bridged dark surface prints white text on white paper. .usa-dark-background is added to the reset in base/_print.scss, and its child pins need span as well as p and a — USWDS sets those on the elements themselves, so inheritance alone loses. Verified under print emulation: surface white, all four elements black.

Where I drew the line

Bridged: the three contexts where USWDS itself paints a dark background. Not bridged: .bg-base-darker and friends, and adopters' own dark wrappers. Those are utility classes and custom markup — HDS can't know a <div> is meant to be a dark surface, and pinning a palette to a utility class would be a much bigger claim than pinning it to a component. The guide's "still needs a palette class" section now points at those instead of at .usa-dark-background, and I've left a note in AGENTS.md saying the family is closed so nobody adds a fourth selector on autopilot.

Type of change

  • Bug fix (patch)
  • New feature or component (minor)
  • Breaking change (see Public API section below)
  • Documentation only
  • Tooling or CI (no effect on published output)

Minor, matching #166. The visible delta here is much smaller — a 4-point background shift versus a red block going away — but it does change rendering on markup adopters already ship, which the semver rubric treats as visible rather than silent. Happy to be argued down to patch.

Checklist

  • npm run format:fix and npm run lint:scss pass
  • npm run lint:js, lint:md, lint:mdx pass
  • Tested across all 6 palettes in Storybook
  • Tested across mobile, tablet, and desktop viewports
  • Automated a11y checks pass (npm test — 242/242)
  • Storybook documentation updated

check:api-snapshot, check:tokens, check:uswds and check:uswds-core all pass too.

On the a11y box: a green test proves nothing until you've watched it go red. I reverted _palettes.scss, rebuilt, and re-ran — both stories fail on color-contrast for the <h2> (1.21:1) and the outline button (1.21:1), in the default story and in the white, light and midtone wrappers of the palette story. Reapplied, clean.

One honest gap: axe does not catch the .usa-link failure. HDS links carry a background-image for the dashed underline, and axe skips contrast on elements with background images — it reports incomplete, not violation. Same blind spot as #166. The 1.04:1 number is from getComputedStyle, not from axe, and the regression guard for links is really the heading and outline button sitting next to them. Worth knowing before anyone assumes the palette stories cover links.

Public API and changesets

  • Ran npm run update:api-snapshot and reviewed the diff
  • Changeset added with appropriate bump level
  • Bump level matches the semver rubric

Snapshot picks up one selector, .usa-dark-background. No new Sass symbols.

Visual review

Storybook: Guides → Existing USWDS Site → Dark Sections. Per the issue's second half, this story now shows all three bridged contexts stacked, so it doubles as the reference for the family rather than needing a separate page. The .usa-dark-background block self-labels in its own heading text, the way the hero callout already did — no extra chrome that isn't real USWDS markup, since the point of the story is that axe runs over markup an adopter would actually ship.

Notes for reviewers

The bit worth a second opinion is the scope line: utility classes stay manual. I think that's right — bridging .bg-base-darker would mean a background utility silently drags a full palette with it, which is a different kind of promise. But it does mean an adopter can still land in the same hole one class over, so if you'd rather we went further, say so and I'll open it up.

Also worth flagging that this reads as smaller than #166 in the diff and larger in the numbers. 1.04:1 was shipping on the default palette. It just never looked broken enough for anyone to file it.

.usa-dark-background is the third and last USWDS context that paints
its own dark surface. Its background is base-darker, which HDS themes
to gray-90, so the surface looked correct and nothing obvious flagged
it. But USWDS only reverses <p>, <span>, and <a> inside the wrapper:
headings and HDS components kept resolving against whatever palette
wrapped the page, giving a 1.22:1 heading and a 1.04:1 .usa-link on the
three light palettes.

Adds the selector to the existing :where() dark-context bridge and to
the print reset, extends the Dark Sections story to cover all three
contexts, and moves .usa-dark-background out of the "still needs a
palette class" section of the adopter guide.

Closes nasa#177

Co-authored-by: Cursor <cursoragent@cursor.com>
@suthat

suthat commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

CI: everything green except Chromatic, which fails with Missing project tokenCHROMATIC_PROJECT_TOKEN isn't exposed to fork PRs. Same failure on #166 from this fork, so it's not this branch. Someone with repo access will need to run the visual diff; the surface shift is #1b1b1b#17171b on .usa-dark-background plus white headings/links, so I'd expect one accepted change on the Dark Sections story and nothing else.

@abbybowman
abbybowman self-requested a review August 5, 2026 20:20
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.

[Task]: Bridge the remaining USWDS dark-context surface (.usa-dark-background)

1 participant