Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .changeset/olive-donuts-hammer.md

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@suthat You raised the q of whether this should be a patch instead, and I think it probably should be, but no need to change here since we're likely going up to v1.0 in the next release. I will go ahead and update our semver table in CONTRIBUTING to draw a more nuanced line between "non-breaking" visual changes (like this) and "breaking" changes that significantly and unexpectedly alter the page.

Original file line number Diff line number Diff line change
@@ -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 `<p>`, `<span>`, and `<a>` 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`.
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<p>`, `<span>`, and `<a>`. 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

Expand Down
7 changes: 4 additions & 3 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,21 +325,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 `<p>`, `<span>`, and `<a>`. 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 `<p>` and `<a>` 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 `<p>`, `<span>`, and `<a>` 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.

Expand Down
29 changes: 23 additions & 6 deletions docs/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
| --- | --- | --- |
Expand All @@ -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 `<p>` and `<a>` 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 `<p>`, `<span>`, and `<a>` 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) |
| `<p>`, `<span>`, bare `<a>` | 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 `<p>`, `<span>`, and `<a>` 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

Expand Down Expand Up @@ -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. |
Expand Down
1 change: 1 addition & 0 deletions public-api.snapshot.txt
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,7 @@ hds-type-classes
.usa-checkbox__label
.usa-checkbox__label-description
.usa-current
.usa-dark-background
.usa-error-message
.usa-fieldset
.usa-footer
Expand Down
2 changes: 1 addition & 1 deletion scripts/css-output-hash.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
19b48a3637a10b6bcb9fa1751c588b9fa576401259fdb7c8abac2801e7e27cb9
9df0f275da903adaf51675cf4ed23d4d384df6dc38998d3c1c9e7fd39188244c
40 changes: 26 additions & 14 deletions src/scss/base/_palettes.scss
Original file line number Diff line number Diff line change
Expand Up @@ -344,20 +344,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 <p>, <span>, and <a>: 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,
Expand All @@ -366,7 +378,7 @@
// guarantee .hds-palette-dark gives, without asking adopters to
// add a class to markup they already have.
//
// USWDS also pins <p> and <a> inside .usa-section--dark to
// USWDS also pins <p>, <span>, and <a> 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
Expand All @@ -378,7 +390,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;

Expand Down
12 changes: 7 additions & 5 deletions src/scss/base/_print.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}

Expand Down
Loading
Loading