From 3959de6f07738621869738eefe73ff4d326865a4 Mon Sep 17 00:00:00 2001 From: suthat Date: Wed, 5 Aug 2026 13:05:48 +0700 Subject: [PATCH 1/2] fix: map USWDS dark background wrapper onto the HDS dark palette .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

, , and 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 #177 Co-authored-by: Cursor --- .changeset/olive-donuts-hammer.md | 11 +++++ AGENTS.md | 5 ++- docs/ARCHITECTURE.md | 7 +-- docs/DESIGN.md | 29 +++++++++--- public-api.snapshot.txt | 1 + src/scss/base/_palettes.scss | 40 +++++++++++------ src/scss/base/_print.scss | 12 ++--- stories/guides/USWDS.mdx | 10 ++--- stories/guides/USWDSDarkSections.stories.js | 50 ++++++++++++++++----- 9 files changed, 119 insertions(+), 46 deletions(-) create mode 100644 .changeset/olive-donuts-hammer.md diff --git a/.changeset/olive-donuts-hammer.md b/.changeset/olive-donuts-hammer.md new file mode 100644 index 0000000..9fec2c7 --- /dev/null +++ b/.changeset/olive-donuts-hammer.md @@ -0,0 +1,11 @@ +--- +'@nasa-hds/core': minor +--- + +`.usa-dark-background` now renders on the HDS dark surface, closing the USWDS dark-context family. + +`.usa-hero__callout` and `.usa-section--dark` were bridged onto the HDS dark palette in the previous release. `.usa-dark-background` is the third and last USWDS context that paints its own dark background, and it was still left to the manual `.hds-palette-dark` path. Its surface already looked correct — USWDS uses `base-darker`, which HDS themes to a near-Carbon-90 gray — but USWDS only reverses `

`, ``, and `` inside it. Headings and HDS components kept resolving against whatever palette wrapped the page, so on the default white palette a heading came out Carbon Black at 1.22:1 and a `.usa-link` at 1.04:1 against their own background. + +The wrapper now carries the full dark palette, so headings, links, buttons, and focus rings inside it match the surface they sit on. No markup changes are needed. To put one of these blocks on a different surface, add a palette class to the same element (`class="usa-dark-background hds-palette-blue"`). + +Background utility classes (`.bg-base-darker` and friends) and your own dark wrappers are unchanged — those still need an explicit `.hds-palette-dark`. diff --git a/AGENTS.md b/AGENTS.md index c477dbe..97e095d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -135,11 +135,12 @@ color: var(--hds-palette-link-text, #{$hds-color-carbon-90}); ### USWDS dark contexts — do NOT flatten -`base/_palettes.scss` also maps `.usa-hero__callout` and `.usa-section--dark` onto the dark palette. USWDS fills both with `primary-darker` (NASA Red under the HDS theme) and colors their headings `accent-cool` (cyan), neither of which a palette wrapper can reach. +`base/_palettes.scss` also maps `.usa-hero__callout`, `.usa-section--dark`, and `.usa-dark-background` onto the dark palette. USWDS fills the first two with `primary-darker` (NASA Red under the HDS theme) and colors their headings `accent-cool` (cyan); `.usa-dark-background` uses `base-darker` and reverses only `

`, ``, and ``. A palette wrapper can reach none of it. - Apply the full `_scheme-dark`, not just a background. Components inside read `--hds-palette-*`; a background-only fix leaves them on light-palette values. - Keep `:where()` here too, for the same override contract. -- These are not a stopgap. Change them only on a design call — see Issue #148 and docs/DESIGN.md → USWDS Dark Contexts. +- The family is closed. Do not add a fourth selector without checking that USWDS actually paints its own dark surface there — background _utility_ classes stay on the manual `.hds-palette-*` path. +- These are not a stopgap. Change them only on a design call — see Issues #148 and #177 and docs/DESIGN.md → USWDS Dark Contexts. ### Class naming diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 6524c6b..9ddcdde 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -321,21 +321,22 @@ These are a stopgap. Remove them when the components get real HDS theming. ### USWDS dark context surfaces -USWDS paints two layout contexts dark on their own: the hero callout and `.usa-section--dark` (the surface the graphic list sits on in the landing page template). Both use `color("primary-darker")`, which the HDS theme maps to NASA Red, and both color their headings `color("accent-cool")`, a family HDS never themes. A palette wrapper cannot reach either value — the red is a `background-color` on the component, not a custom property. +USWDS paints three contexts dark on their own: the hero callout, `.usa-section--dark` (the surface the graphic list sits on in the landing page template), and the `.usa-dark-background` typography wrapper. The first two use `color("primary-darker")`, which the HDS theme maps to NASA Red, and color their headings `color("accent-cool")`, a family HDS never themes. `.usa-dark-background` uses `color("base-darker")` (`gray-90`), which is close enough to Carbon 90 to look correct, but it only reverses `

`, ``, and ``. A palette wrapper cannot reach any of it — the background is a `background-color` on the component, not a custom property. -`base/_palettes.scss` maps both onto palette 4 (dark, Carbon 90): +`base/_palettes.scss` maps all three onto palette 4 (dark, Carbon 90): | Selector | Palette applied | What it corrects | | --- | --- | --- | | `.usa-hero__callout` | Dark | Red callout box, cyan heading, red CTA on red | | `.usa-section--dark` | Dark | Red section band, cyan headings, light-palette components inside | +| `.usa-dark-background` | Dark | Carbon Black headings and Carbon 90 links on a `#1b1b1b` surface (1.2:1 and 1.0:1 on light palettes) | | `.usa-hero__heading--alt` | — | Takes `--hds-palette-muted` so the eyebrow stays distinct from the heading | Applying the full dark scheme, not just a background, is what makes components inside the section correct: `.usa-link`, `.usa-button--outline`, and focus rings all read `--hds-palette-*` values, which previously resolved against whatever palette wrapped the page. Selectors use `:where()`, so specificity stays at zero and `.hds-palette-*` on the same element still wins — the same contract as the surface bridges above. These rules override USWDS by cascade layer, not specificity: `hds-base` outranks `uswds` regardless of how the two selectors compare. -`base/_print.scss` lists both selectors alongside the palette containers. Browsers drop background colors when printing, so a dark surface that keeps its white text prints as blank paper. The print reset also has to override USWDS's white `

` and `` inside `.usa-section--dark` directly, because those are set on the children and only reached by inheritance otherwise. +`base/_print.scss` lists all three selectors alongside the palette containers. Browsers drop background colors when printing, so a dark surface that keeps its white text prints as blank paper. The print reset also has to override USWDS's white `

`, ``, and `` inside `.usa-section--dark` and `.usa-dark-background` directly, because those are set on the children and only reached by inheritance otherwise. Unlike the surface bridges, these are not a stopgap for missing theming. They stay in place unless the design direction for dark sections changes. See DESIGN.md for the color decisions and contrast figures. diff --git a/docs/DESIGN.md b/docs/DESIGN.md index 169f4f8..fad0737 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -53,11 +53,13 @@ HDS links use body text color — not brand color — for the text itself. The d ### USWDS Dark Contexts -USWDS fills its two dark layout contexts — the hero callout (`.usa-hero__callout`) and `.usa-section--dark`, which the graphic list sits on — with `color("primary-darker")`, and colors the headings inside them with `color("accent-cool")`. HDS themes the primary family to NASA Red and never themes accent-cool, so both surfaces rendered as a dark red block with cyan headings, with a red `.usa-button` on the red hero callout. +USWDS has three contexts that paint their own dark background: the hero callout (`.usa-hero__callout`), `.usa-section--dark` (which the graphic list sits on), and the `.usa-dark-background` typography wrapper. All three are mapped onto palette 4 (dark, Carbon 90) in `base/_palettes.scss`. That reuses a surface HDS already designed rather than inventing a new one, and everything inside — headings, body text, links, buttons, focus rings — resolves against the surface it is actually sitting on. -A `.hds-palette-*` wrapper could not correct this. The red is a `background-color` on the component itself, not a palette custom property, and a wrapper actually made the headings worse: inside `.hds-palette-white` they resolved to Carbon Black on the red block. +They were broken in two different ways. + +**Hero callout and dark section** use `color("primary-darker")` and color their headings `color("accent-cool")`. HDS themes the primary family to NASA Red and never themes accent-cool, so both rendered as a dark red block with cyan headings, with a red `.usa-button` on the red hero callout. -Both surfaces are now mapped onto palette 4 (dark, Carbon 90) in `base/_palettes.scss`. That reuses a surface HDS already designed rather than inventing a new one, and everything inside — headings, body text, links, buttons, focus rings — resolves against the surface it is actually sitting on. +A `.hds-palette-*` wrapper could not correct this. The red is a `background-color` on the component itself, not a palette custom property, and a wrapper actually made the headings worse: inside `.hds-palette-white` they resolved to Carbon Black on the red block. | What | Before | After | | --- | --- | --- | @@ -68,11 +70,26 @@ Both surfaces are now mapped onto palette 4 (dark, Carbon 90) in `base/_palettes | `.usa-button--outline` label inside a dark section | Carbon Black on the red block (2.1:1) | White (17.9:1) | | `.usa-button` on the hero callout | `#d83933` on `#8b0a03` (2.1:1 against its container) | `#d83933` on Carbon 90 (3.9:1) | -USWDS also pins `

` and `` inside `.usa-section--dark` to white. Those declarations are left alone: white is what the dark scheme resolves to, and the blue palette — the other surface an adopter would plausibly swap in on a section marked dark — is also a dark surface with white body text. +**`.usa-dark-background`** fails differently, and worse. It uses `color("base-darker")`, which the HDS theme maps to `gray-90` (`#1b1b1b`) — four RGB points off Carbon 90, so the surface itself already looked right and there was no red block to notice. But USWDS only reverses `

`, ``, and `` inside it. Headings and every HDS component keep resolving against whatever palette wraps the page, which on the default white palette is near-invisible. + +The measured values below are for the three light palettes (white, light, midtone). The dark, blue, and black palettes were already correct by accident, which is what made this easy to miss. + +| What | Before (light palettes) | After (all palettes) | +| ---------------------------------- | --------------------------------------------- | -------------------- | +| Surface | USWDS `base-darker` `#1b1b1b` | Carbon 90 `#17171b` | +| Headings | `--hds-palette-heading` Carbon Black (1.22:1) | White (17.9:1) | +| `.usa-link` and `.usa-prose` links | `--hds-palette-link-text` Carbon 90 (1.04:1) | White (17.9:1) | +| `.usa-button--outline` label | `--hds-palette-heading` Carbon Black (1.22:1) | White (17.9:1) | +| `.usa-button--outline` border | NASA Blue `#1c67e3` (3.5:1) | Unchanged (3.5:1) | +| `

`, ``, bare `` | White, pinned by USWDS (17.2:1) | White (17.9:1) | + +Bridging it rather than leaving it on the manual `.hds-palette-dark` path was a deliberate call: it is the same family as the other two, the surface shift is imperceptible, and the alternative is asking adopters to add a class to fix a heading that is currently 1.22:1 against its own background. + +USWDS also pins `

`, ``, and `` inside these contexts to white. Those declarations are left alone: white is what the dark scheme resolves to, and the blue palette — the other surface an adopter would plausibly swap in on a section marked dark — is also a dark surface with white body text. `.usa-section--light` is untouched. USWDS paints it `base-lightest`, which the HDS theme maps to white, so it already agrees with the default palette. -Tracked in [Issue #148](https://github.com/nasa/hds-core/issues/148). See ARCHITECTURE.md for the cascade contract. +Tracked in [Issue #148](https://github.com/nasa/hds-core/issues/148) and [Issue #177](https://github.com/nasa/hds-core/issues/177). See ARCHITECTURE.md for the cascade contract. ### Data Visualization Color Alignment @@ -489,7 +506,7 @@ Pending visual sign-offs: | Item | Question | Context | | --- | --- | --- | -| USWDS dark contexts | Is the HDS dark palette (Carbon 90) the right surface for the USWDS hero callout and `.usa-section--dark`, or should a red hero be a deliberate NASA treatment? And should the hero's `--alt` eyebrow stay muted Carbon 30 rather than matching the heading white? | Issue #148. Shipping the dark palette mapping; every value is an existing palette token, so a different call is a one-line change in `base/_palettes.scss`. | +| USWDS dark contexts | Is the HDS dark palette (Carbon 90) the right surface for the USWDS hero callout, `.usa-section--dark`, and `.usa-dark-background`, or should a red hero be a deliberate NASA treatment? And should the hero's `--alt` eyebrow stay muted Carbon 30 rather than matching the heading white? | Issues #148 and #177. Shipping the dark palette mapping for all three; every value is an existing palette token, so a different call is a one-line change in `base/_palettes.scss`. | | Focus bold on light backgrounds | C30 on white/light/midtone fails WCAG 1.4.11 (3:1 non-text contrast). Should bold focus treatment use a darker value on light backgrounds? | Tracked in Issue #40. Ships as Figma spec for now. | | Overline font-weight | Should overline use 400 (Proposal) or 500 (Figma)? | Proposal says "normal" (400). Figma uses Medium (500). Currently shipping 500. | | Blue palette utility | Blue palette utility now uses Proposal palette 5 values (Carbon Black fill, Carbon 60 stroke — matching dark scheme). Figma shows NASA Blue fill + Blue Tint stroke. | Currently shipping Proposal values. Flagged in case CD prefers Figma treatment. | diff --git a/public-api.snapshot.txt b/public-api.snapshot.txt index 1632317..2bca6b5 100644 --- a/public-api.snapshot.txt +++ b/public-api.snapshot.txt @@ -468,6 +468,7 @@ hds-type-classes .usa-checkbox__label .usa-checkbox__label-description .usa-current +.usa-dark-background .usa-error-message .usa-fieldset .usa-footer diff --git a/src/scss/base/_palettes.scss b/src/scss/base/_palettes.scss index 4f5f023..58f4a6b 100644 --- a/src/scss/base/_palettes.scss +++ b/src/scss/base/_palettes.scss @@ -342,20 +342,32 @@ // ============================================================ // USWDS DARK CONTEXT SURFACES // ============================================================ -// USWDS paints its two dark layout contexts — the hero callout -// and .usa-section--dark (which the graphic list sits on in the -// USWDS landing page template) — with color("primary-darker"), -// and colors the headings inside them with color("accent-cool"). +// Three USWDS contexts paint their own dark background: the +// hero callout, .usa-section--dark (which the graphic list sits +// on in the USWDS landing page template), and the +// .usa-dark-background typography wrapper. // -// HDS themes the primary family to NASA Red, so both surfaces -// come out as a dark red block, and accent-cool is a family HDS -// never themes, so the headings come out USWDS cyan. Neither -// color is in the HDS palette. The red block also puts a red -// .usa-button on a red background, roughly 2.1:1 apart. +// The first two use color("primary-darker") and color their +// headings color("accent-cool"). HDS themes the primary family +// to NASA Red, so both come out as a dark red block, and +// accent-cool is a family HDS never themes, so the headings +// come out USWDS cyan. Neither color is in the HDS palette. The +// red block also puts a red .usa-button on a red background, +// roughly 2.1:1 apart. // -// A .hds-palette-* wrapper cannot correct this: the red is a -// background-color on the component itself, not a palette -// custom property, so it survives any wrapper (Issue #148). +// .usa-dark-background fails differently. It uses +// color("base-darker"), which the HDS theme maps to gray-90 +// (#1b1b1b) — near Carbon 90, so the surface itself looks +// right. But it only reverses

, , and : headings +// and every HDS component inside keep resolving against +// whatever palette wraps the page. On the default white palette +// that is a Carbon Black heading at 1.22:1 and a .usa-link at +// 1.04:1 (Issue #177). +// +// A .hds-palette-* wrapper cannot correct any of it: the +// background is set on the component itself, not through a +// palette custom property, so it survives any wrapper +// (Issue #148). // // HDS already has a designed dark surface — palette 4, Carbon // 90 — so each USWDS dark context is mapped onto it. Text, @@ -364,7 +376,7 @@ // guarantee .hds-palette-dark gives, without asking adopters to // add a class to markup they already have. // -// USWDS also pins

and inside .usa-section--dark to +// USWDS also pins

, , and inside these contexts to // white. Those are left as they are: white is what the dark // scheme resolves to, and the blue palette — the other surface // an adopter would plausibly swap in here — is also a dark @@ -376,7 +388,7 @@ // bridges above. // ============================================================ -:where(.usa-hero__callout, .usa-section--dark) { +:where(.usa-hero__callout, .usa-section--dark, .usa-dark-background) { @include _scheme-universal; @include _scheme-dark; diff --git a/src/scss/base/_print.scss b/src/scss/base/_print.scss index 341c5c4..9aefb00 100644 --- a/src/scss/base/_print.scss +++ b/src/scss/base/_print.scss @@ -15,7 +15,8 @@ [data-hds-palette], [class*='hds-palette-'], .usa-hero__callout, - .usa-section--dark { + .usa-section--dark, + .usa-dark-background { --hds-palette-bg: #ffffff !important; --hds-palette-heading: #000000 !important; --hds-palette-text: #000000 !important; @@ -29,10 +30,11 @@ color: #000000 !important; } - // USWDS pins paragraphs and links inside .usa-section--dark to - // white on the elements themselves, so the reset above only - // reaches them by inheritance and loses. - .usa-section--dark :is(p, a) { + // USWDS pins paragraphs, spans, and links inside the dark + // contexts to white on the elements themselves, so the reset + // above only reaches them by inheritance and loses. + .usa-section--dark :is(p, a), + .usa-dark-background :is(p, span, a) { color: #000000 !important; } diff --git a/stories/guides/USWDS.mdx b/stories/guides/USWDS.mdx index fe9e5cb..5d64098 100644 --- a/stories/guides/USWDS.mdx +++ b/stories/guides/USWDS.mdx @@ -52,7 +52,7 @@ After installing, review the following to bring your site into HDS's intended vi HDS Core uses a palette system activated through `.hds-palette-*` wrapper classes. When no palette class is set, HDS applies its default white palette. -**Two USWDS dark contexts are handled for you.** A hero callout (`.usa-hero__callout`) and a section marked `.usa-section--dark` render on the HDS dark surface, and the headings, text, links, and buttons inside them follow. Your markup does not change. In vanilla USWDS these two contexts paint themselves with the primary color, which under HDS would put them in NASA red. +**The three USWDS dark contexts are handled for you.** A hero callout (`.usa-hero__callout`), a section marked `.usa-section--dark`, and a `.usa-dark-background` wrapper all render on the HDS dark surface, and the headings, text, links, and buttons inside them follow. Your markup does not change. In vanilla USWDS the first two paint themselves with the primary color, which under HDS would put them in NASA red; `.usa-dark-background` gets its surface right but leaves headings and HDS components resolving against the surrounding palette. To put one of them on a different surface, add a palette class to the same element: @@ -62,16 +62,16 @@ To put one of them on a different surface, add a palette class to the same eleme ``` -**Every other dark context still needs a palette class.** `.usa-dark-background`, background utility classes, and your own dark wrappers give you a dark background, but HDS components inside them still use white-palette colors. This can produce contrast failures, such as light-mode buttons on a dark background. Add `.hds-palette-dark` to the element or a parent wrapper: +**Your own dark contexts still need a palette class.** Background utility classes (`.bg-base-darker`, `.bg-primary-darker`) and custom dark wrappers give you a dark background, but HDS components inside them still use white-palette colors. This can produce contrast failures, such as light-mode buttons on a dark background. Add `.hds-palette-dark` to the element or a parent wrapper: ```html -

+
-
+
``` @@ -193,4 +193,4 @@ Settings not reserved by HDS Core continue to work. See the [Sass Configuration ## Live preview -Use the **Documentation**, **Landing Page**, and **Multi-Step Form** stories in the sidebar to see unmodified USWDS template markup rendered under the HDS Core theme. [**Dark Sections**](?path=/story/guides-existing-uswds-site-dark-sections--dark-sections) shows the hero callout and dark section on their own, which is the quickest way to check how your own dark markup will land. +Use the **Documentation**, **Landing Page**, and **Multi-Step Form** stories in the sidebar to see unmodified USWDS template markup rendered under the HDS Core theme. [**Dark Sections**](?path=/story/guides-existing-uswds-site-dark-sections--dark-sections) shows all three bridged dark contexts together, which is the quickest way to check how your own dark markup will land. diff --git a/stories/guides/USWDSDarkSections.stories.js b/stories/guides/USWDSDarkSections.stories.js index 0042332..3857a37 100644 --- a/stories/guides/USWDSDarkSections.stories.js +++ b/stories/guides/USWDSDarkSections.stories.js @@ -1,15 +1,21 @@ // ============================================================ -// USWDS Dark Sections — Surface Mapping Check +// USWDS Dark Contexts — Surface Mapping Check // ============================================================ -// The two USWDS layout contexts that paint their own dark -// surface: the hero callout and .usa-section--dark, which the -// graphic list sits on in the USWDS landing page template. +// The three USWDS contexts that paint their own dark surface: +// the hero callout, .usa-section--dark (which the graphic list +// sits on in the USWDS landing page template), and the +// .usa-dark-background typography wrapper. // -// USWDS fills both with color("primary-darker") and colors the -// headings inside them with color("accent-cool"). Under the HDS -// theme that reads as a NASA-red block with cyan headings, so -// base/_palettes.scss maps both surfaces onto the HDS dark -// palette instead (Issue #148). +// USWDS fills the first two with color("primary-darker") and +// colors the headings inside them with color("accent-cool"). +// Under the HDS theme that reads as a NASA-red block with cyan +// headings. .usa-dark-background uses color("base-darker"), which +// HDS themes to a near-Carbon-90 gray, so it looks right but +// leaves every heading and HDS component inside it resolving +// against whatever palette wraps the page. +// +// base/_palettes.scss maps all three onto the HDS dark palette +// (Issues #148 and #177). // // The palette story is the regression guard. It renders the same // markup inside all six palette wrappers — the surfaces are @@ -78,10 +84,32 @@ const graphicList = ` `; +const darkBackground = ` +
+`; + export const DarkSections = { - name: 'Hero and dark section', + name: 'Hero, dark section, and dark background', tags: ['!dev'], - render: () => `${hero}${graphicList}`, + render: () => `${hero}${graphicList}${darkBackground}`, }; export const PaletteA11y = { From f690f0b55cfe725ae99e3c26291baece8dee7dfd Mon Sep 17 00:00:00 2001 From: Abby Bowman Date: Mon, 24 Aug 2026 09:09:25 -0400 Subject: [PATCH 2/2] ci: update CSS output hash --- scripts/css-output-hash.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/css-output-hash.txt b/scripts/css-output-hash.txt index 32f0eb8..7bfabb9 100644 --- a/scripts/css-output-hash.txt +++ b/scripts/css-output-hash.txt @@ -1 +1 @@ -19b48a3637a10b6bcb9fa1751c588b9fa576401259fdb7c8abac2801e7e27cb9 +9df0f275da903adaf51675cf4ed23d4d384df6dc38998d3c1c9e7fd39188244c