diff --git a/.changeset/stamped-versions.md b/.changeset/stamped-versions.md
new file mode 100644
index 00000000..8019a2cd
--- /dev/null
+++ b/.changeset/stamped-versions.md
@@ -0,0 +1,7 @@
+---
+'@nasa-hds/core': patch
+---
+
+Stamp the HDS and USWDS versions into the compiled CSS
+
+Every bundle now opens with a banner naming its version and the USWDS version it was built against, and `hds.min.css` exposes `--hds-version` and `--hds-uswds-version` for runtime reads. These are diagnostics for copied-`dist/` deployments that keep no other record of what is installed; both values belong in any bug report. See [Installation](?path=/docs/overview-installation--docs) for how to read them.
diff --git a/.config/postcss-hds-stamp.mjs b/.config/postcss-hds-stamp.mjs
new file mode 100644
index 00000000..186d79aa
--- /dev/null
+++ b/.config/postcss-hds-stamp.mjs
@@ -0,0 +1,66 @@
+/**
+ * HDS Version Stamp — PostCSS plugin
+ *
+ * Stamps the HDS + USWDS versions into every compiled bundle: a `/*!` banner
+ * on all bundles, plus --hds-version / --hds-uswds-version on :root in
+ * hds.min.css only (a stale copy of an optional bundle must not win the
+ * cascade and misreport the version).
+ *
+ * Runs last in the postcss chain so the banner survives comment-discarding
+ * and minification. Versions are read at build time, so the stamp tracks the
+ * changesets bump with nothing to regenerate.
+ */
+
+import fs from 'node:fs';
+
+const read = (path) => JSON.parse(fs.readFileSync(path, 'utf8'));
+
+const HDS_VERSION = read('./package.json').version;
+const USWDS_VERSION = read('./node_modules/@uswds/uswds/package.json').version;
+
+/** Bundles that carry the custom properties, keyed by output filename. */
+const STAMPS_PROPERTIES = new Set(['hds.min.css', 'hds.css']);
+
+export default function hdsStamp() {
+ return {
+ postcssPlugin: 'postcss-hds-stamp',
+
+ OnceExit(root, { result, Comment, Rule, Declaration, AtRule }) {
+ const file = result.opts.to ? result.opts.to.split(/[\\/]/).pop() : '';
+
+ // We compile USWDS from source, so its banner ships the literal
+ // `uswds @version` placeholder that USWDS substitutes in its own build.
+ root.walkComments((comment) => {
+ if (comment.text.includes('uswds @version')) {
+ comment.text = comment.text.replace('uswds @version', `uswds v${USWDS_VERSION}`);
+ }
+ });
+
+ if (STAMPS_PROPERTIES.has(file)) {
+ const rule = new Rule({ selector: ':root' });
+ // Unquoted: `getPropertyValue('--hds-version')` then reads back as
+ // `0.9.0` rather than `'0.9.0'`, which is what a support request or a
+ // console one-liner actually wants.
+ rule.append(new Declaration({ prop: '--hds-version', value: HDS_VERSION }));
+ rule.append(new Declaration({ prop: '--hds-uswds-version', value: USWDS_VERSION }));
+
+ // hds-base is already declared in every bundle's layer order;
+ // layers merge, so appending here needs no other coordination.
+ const layer = new AtRule({ name: 'layer', params: 'hds-base' });
+ layer.append(rule);
+ root.append(layer);
+ }
+
+ const banner = new Comment({
+ text: `! @nasa-hds/core v${HDS_VERSION}${file ? ` — ${file}` : ''} | uswds v${USWDS_VERSION} | CC0 1.0 | https://github.com/nasa/hds-core `,
+ });
+ banner.raws.left = '';
+ banner.raws.right = '';
+
+ // After @charset, which must stay first.
+ const charset = root.first?.name === 'charset' ? root.first : null;
+ if (charset) charset.after(banner);
+ else root.prepend(banner);
+ },
+ };
+}
diff --git a/package.json b/package.json
index 210c7939..123036e9 100644
--- a/package.json
+++ b/package.json
@@ -23,12 +23,12 @@
"./scss": "./src/scss/hds.scss",
"./scss/uswds": "./src/scss/hds-uswds.scss",
"./scss/dataviz": "./src/scss/hds-dataviz.scss",
- "./assets": "./dist/assets/"
+ "./js/uswds": "./dist/js/uswds.min.js",
+ "./assets/*": "./dist/assets/*"
},
"files": [
"dist/",
- "src/scss/",
- "src/assets/"
+ "src/scss/"
],
"scripts": {
"sass:hds": "sass src/scss/hds.scss dist/css/hds.css --load-path=node_modules/@uswds/uswds/packages --load-path=src/scss --source-map --style=expanded --quiet-deps",
diff --git a/postcss.config.mjs b/postcss.config.mjs
index 83425a91..78330a93 100644
--- a/postcss.config.mjs
+++ b/postcss.config.mjs
@@ -2,10 +2,15 @@ import autoprefixer from 'autoprefixer';
import discardComments from 'postcss-discard-comments';
import cssnano from 'cssnano';
+import hdsStamp from './.config/postcss-hds-stamp.mjs';
+
const plugins = [autoprefixer(), discardComments()];
if (process.env.MINIFY === 'true' || process.env.NODE_ENV === 'production') {
plugins.push(cssnano({ preset: 'default' }));
}
+// Last: the version banner must outlive comment discarding and minification.
+plugins.push(hdsStamp());
+
export default { plugins };
diff --git a/public-api.snapshot.txt b/public-api.snapshot.txt
index 1632317d..75cfa9d0 100644
--- a/public-api.snapshot.txt
+++ b/public-api.snapshot.txt
@@ -149,6 +149,8 @@ src/scss/hds-uswds.scss
--hds-summary-border-radius
--hds-summary-border-width
--hds-table-border-width
+--hds-uswds-version
+--hds-version
## Custom Properties (hds-dataviz.min.css)
--hds-dataviz-color-cat-1
diff --git a/stories/guides/NoBuildEnvironments.mdx b/stories/guides/NoBuildEnvironments.mdx
index 0b9b0167..c767880d 100644
--- a/stories/guides/NoBuildEnvironments.mdx
+++ b/stories/guides/NoBuildEnvironments.mdx
@@ -10,7 +10,7 @@ Before using this guide, confirm your site is approved to remain standalone. See
## Add the stylesheet and scripts
-Download the HDS Core distribution from GitHub or install via npm, then add these to your HTML:
+First [get the package](?path=/docs/overview-installation--docs#get-the-package): download the dist zip or install via npm, and verify the download. Then add these to your HTML:
```html
@@ -28,17 +28,13 @@ Download the HDS Core distribution from GitHub or install via npm, then add thes
USWDS 3.x is written in vanilla JavaScript and does not conflict with jQuery. If your legacy site depends on older versions of jQuery, the USWDS scripts run safely alongside it.
-### Verify the download before you copy it onto your server
+A copied `dist/` keeps no record of what you installed, so the stylesheet carries its own version. Read it at runtime to confirm what is deployed:
-npm verifies package integrity for you. If you download the dist zip from the [Releases page](https://github.com/nasa/hds-core/releases) instead, that check is yours to run. GitHub displays a SHA-256 digest under the zip asset on the release, and the release notes repeat the same digest under "Verify your download".
-
-Hash your download and confirm it matches the digest on the release before extracting:
-
-```sh
-sha256sum hds-core-*-dist.zip
+```js
+getComputedStyle(document.documentElement).getPropertyValue('--hds-version').trim(); // 0.9.0
```
-On macOS, use `shasum -a 256` instead. If the two digests differ, the file was corrupted or tampered with in transit. Do not deploy it: re-download, and if it still fails, [open an issue](https://github.com/nasa/hds-core/issues).
+See [Installation](?path=/docs/overview-installation--docs#which-version-am-i-running) for the full version and provenance details.
## Use HDS tokens in your existing CSS
@@ -135,7 +131,7 @@ Fixed-width containers break on mobile and fail responsive layout requirements.
```
-> These grid and layout classes require loading `hds-uswds.min.css` alongside `hds.min.css`. This is a temporary migration step — as you modernize your markup, the goal is to move toward CSS Grid and Flexbox natively, which removes the need for utility classes entirely.
+> These grid and layout classes are included in `hds.min.css`, so no extra file is needed. (The optional `hds-uswds.min.css` bundle adds spacing, color, and other utility classes like `.padding-2` or `.text-primary`, not grid.) Grid is a temporary migration step: as you modernize your markup, the goal is to move toward CSS Grid and Flexbox natively, which removes the need for utility classes entirely.
### Semantics: fix inaccessible markup
diff --git a/stories/guides/ReactSetup.mdx b/stories/guides/ReactSetup.mdx
index 06edd238..60c78ce9 100644
--- a/stories/guides/ReactSetup.mdx
+++ b/stories/guides/ReactSetup.mdx
@@ -65,9 +65,9 @@ export default defineConfig({
viteStaticCopy({
targets: [
// HDS fonts (DM Mono, Inter) — lands at dist/assets/fonts/
- { src: 'node_modules/@nasa-hds/core/src/assets/fonts', dest: 'assets', rename: { stripBase: 5 } },
+ { src: 'node_modules/@nasa-hds/core/dist/assets/fonts', dest: 'assets', rename: { stripBase: 5 } },
// HDS icon SVGs (accordion, table sort, external link, form error)
- { src: 'node_modules/@nasa-hds/core/src/assets/img', dest: 'assets', rename: { stripBase: 5 } },
+ { src: 'node_modules/@nasa-hds/core/dist/assets/img', dest: 'assets', rename: { stripBase: 5 } },
// Public Sans — from USWDS — lands at dist/assets/fonts/public-sans/
{ src: 'node_modules/@uswds/uswds/dist/fonts/public-sans', dest: 'assets/fonts', rename: { stripBase: 5 } },
// USWDS images (checkboxes, radio indicators, etc.)
@@ -79,7 +79,6 @@ export default defineConfig({
preprocessorOptions: {
scss: {
loadPaths: ['node_modules/@uswds/uswds/packages', 'node_modules/@nasa-hds/core/src/scss'],
- loadPaths: ['node_modules/@uswds/uswds/packages', 'node_modules/@nasa-hds/core/src/scss'],
},
},
},
diff --git a/stories/guides/SassConfiguration.mdx b/stories/guides/SassConfiguration.mdx
index bcb5da85..ce56d4ae 100644
--- a/stories/guides/SassConfiguration.mdx
+++ b/stories/guides/SassConfiguration.mdx
@@ -4,159 +4,80 @@ import { Meta } from '@storybook/addon-docs/blocks';
# Sass Configuration
-This guide covers advanced USWDS theme customization for projects using the Sass integration path. For basic Sass setup, see [Installation](?path=/docs/overview-installation--docs).
+The Sass path lets you build your own components in the HDS design language. You get HDS design tokens, USWDS functions, and HDS mixins as compile-time values, so custom styles you write match HDS exactly.
-## Customizing USWDS settings
+The entry point is a single forward:
-HDS Core configures USWDS with HDS values: colors, fonts, spacing, and component behavior. Many of these settings can be further customized in your project. Others are reserved by HDS Core and should not be overridden.
+```scss
+@forward '@nasa-hds/core/scss';
+```
-**USWDS Sass settings (`$theme-*` variables) cannot be overridden from consumer stylesheets.** HDS Core calls `@use 'uswds-core' with (...)` internally before your entry point runs. In Sass's module system, a module can only be configured the first time it loads. Attempting a second `@use 'uswds-core' with (...)` in your own Sass will throw a compile error. Do not add `@use 'uswds-core'` to your project stylesheets; see [Installation](?path=/docs/overview-installation--docs#common-mistakes) for why.
+`@nasa-hds/core/scss` is a package export that resolves to HDS Core's Sass entry. It is the one string to use; do not reach into internal paths like `@nasa-hds/core/src/scss/hds`. You also configure two Sass load paths so the compiler can find HDS and USWDS: see [Installation](?path=/docs/overview-installation--docs#sass-setup) for the load-path setup and the full entry-point examples (utilities, dataviz).
-## Global element styles
+You do not reconfigure the HDS theme from Sass. The NASA visual identity is fixed on purpose (see [USWDS settings](#uswds-settings)), the same way the VA and CMS design systems fix theirs. What you customize is your own code, using the surface below.
-By default, HDS Core does not style bare HTML elements (`
`, etc.). This prevents conflicts with existing styles in your project. Because USWDS settings are locked by HDS Core (see above), this default cannot be changed via Sass.
+## What you can use
-Use USWDS classes to activate styling on the elements you control:
+With HDS Core forwarded, HDS variables, USWDS functions, and HDS mixins are available in your own stylesheets:
-- `.usa-prose` wraps body content sections with full HDS typography (headings, paragraphs, lists, links)
-- `.usa-link` applies link styling to individual anchors
-- `.usa-button` applies button styling
-- `.usa-intro` applies lead/intro paragraph styling
+```scss
+.my-component {
+ background-color: $hds-color-carbon-05;
+ color: $hds-color-carbon-90;
+ font-family: family('heading');
+ font-size: size('heading', 'md');
+ padding: units(3);
-These class-based styles are always active and cover the same elements that global flags would affect.
+ &:focus-visible {
+ @include hds-focus-ring;
+ }
+}
+```
-## Settings you may need to adjust
+- **HDS Sass variables** (`$hds-color-*`, `$hds-spacing-*`, `$hds-font-*`, and the rest) return exact HDS values.
+- **USWDS functions** (`family()`, `size()`, `units()`, `color()`) work as documented in the [USWDS documentation](https://designsystem.digital.gov/design-tokens/).
+- **HDS mixins** apply HDS patterns: `hds-focus-ring` (and `hds-focus-ring-inline`, `hds-focus-ring-size`), `hds-link-appearance`, `hds-link-hover`, `visually-hidden`, and `hds-type` (applies a type-ramp step to any element).
+- **Two configuration flags** are yours to set at import time: `$hds-enable-dataviz-tokens` and `$hds-enable-auto-dark-mode`.
-Most projects won't need to change anything — HDS Core configures USWDS with the correct values. These are the few settings that depend on your specific project setup.
+`color()` returns USWDS system token values, which are close approximations of HDS Carbon colors but not exact matches. For example, USWDS `gray-90` is `#1b1b1b`, while HDS Carbon 90 is `#17171B`. For exact HDS values, use `$hds-color-*` variables or `var(--hds-color-*)` custom properties.
-### Asset paths
+Because your components are built on the shared HDS token surface rather than a private fork, they stay compatible with HDS and can be [contributed back](?path=/docs/overview-getting-started--docs) into HDS Core.
-Adjust these if your build pipeline outputs assets to a different location than the HDS Core default.
+## HDS utility classes
-| Setting | HDS Default | When to change |
-| ------------------- | ------------------- | ------------------------------------------------------------------------ |
-| `$theme-image-path` | `'../assets/img'` | Your compiled CSS is not one directory above your `assets/img/` folder |
-| `$theme-font-path` | `'../assets/fonts'` | Your compiled CSS is not one directory above your `assets/fonts/` folder |
+HDS ships a few utility classes beyond USWDS:
-### Fixed header offsets
+| Class | Purpose |
+| -------------------------- | ----------------------------------------------------------------------------------------------------------------- |
+| `.hds-print-visible` | Show an element in print that is hidden on screen (expanded URLs, critical alerts). |
+| `.hds-global-styles` | Opt-in wrapper that styles bare HTML elements inside it. See [Content element styling](#content-element-styling). |
+| `.hds-global-styles-reset` | Reset boundary inside a `.hds-global-styles` or `.usa-prose` scope, returning descendants to unstyled markup. |
-If your site has a fixed or sticky header, these prevent content from being hidden behind it.
+The `visually-hidden` mixin provides screen-reader-only text from your own Sass.
-| Setting | Default | What it controls |
-| -------------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------- |
-| `$theme-in-page-nav-top` | `4` (32px) | Distance from the top of the viewport when the in-page navigation sidebar becomes sticky. Increase to clear a fixed header. |
-| `$theme-table-sticky-top-offset` | `0px` | Offset for sticky table headers. Increase to clear a fixed header. |
+All [USWDS utility classes](https://designsystem.digital.gov/utilities/) for layout, spacing, and typography work with HDS Core. USWDS color utilities (`.bg-*`, `.text-*`, `.border-*`) also work, but resolve to USWDS system token values, which approximate HDS colors rather than matching them exactly. For exact HDS colors, use `$hds-color-*` Sass variables or `var(--hds-color-*)` custom properties.
-### Form input width
+HDS also ships typography classes (`.hds-h1`–`.hds-h6`, `.hds-display-*`, `.hds-stat-*`, and more) and palette wrappers (`.hds-palette-*`). Those are documented where they are designed: see [Typography](?path=/docs/foundations-typography--docs) and [Color](?path=/docs/foundations-color--docs).
-| Setting | Default | What it controls |
-| ------------------------ | ------------- | -------------------------------------------------------------------------------------- |
-| `$theme-input-max-width` | `"mobile-lg"` | Max width of text inputs and selects. Increase if your layout needs wider form fields. |
+## Content element styling
-### Compile output
+By default, HDS Core does not style bare HTML elements (`