@solexllc/usx-theme 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,587 @@
1
+ // theme-manifest.js — canonical manifest of themeable USX tokens.
2
+ //
3
+ // Single source of truth consumed by:
4
+ // - build.js → emits dist/theme-manifest.json, dist/theme.css
5
+ // (:root values), dist/_hooks.scss (Sass hooks)
6
+ // - the Storybook theme playground (controls, derivation, export)
7
+ // - test scripts → verify manifest entries match compiled var() refs
8
+ //
9
+ // Keep in sync with src/_variables.scss (each entry mirrors a hook/published
10
+ // token pair). Compiled CSS emits bare var(--usx-*) references with NO
11
+ // fallbacks — defaultValue is the value theme.css assigns on :root.
12
+ //
13
+ // Fields:
14
+ // name — SCSS token name without `$` (published token).
15
+ // cssVar — CSS custom property name.
16
+ // defaultValue — the default value theme.css assigns to cssVar on :root.
17
+ // group — color | spacing | radius | radius-advanced | border |
18
+ // border-advanced | typography | typography-advanced |
19
+ // component.
20
+ // type — color | length | number | font-family.
21
+ // derivedFrom — (optional) base token `name` this shade derives from; the
22
+ // playground re-derives it by applying the HSL-lightness
23
+ // delta between the two defaults to the new base value.
24
+
25
+ // systemDefault records which real USWDS system token (family + grade, from
26
+ // the immutable palette in system-colors.generated.js) this theme/state
27
+ // token points at by default — mirrors how USWDS's own settings
28
+ // (`$theme-color-primary: "blue-60v"`) are just pointers into the system
29
+ // palette, never raw hex. Only set for tokens with a real USWDS counterpart;
30
+ // USX-specific extensions (hover/active aliases, base-100/200/300, border,
31
+ // component tokens, etc.) omit it and stay plain hex in the playground.
32
+ const c = (name, defaultValue, derivedFrom, systemDefault) => ({
33
+ name,
34
+ cssVar: `--usx-${name}`,
35
+ defaultValue,
36
+ group: 'color',
37
+ type: 'color',
38
+ ...(derivedFrom ? { derivedFrom } : {}),
39
+ ...(systemDefault ? { systemDefault } : {})
40
+ });
41
+
42
+ const component = (name, defaultValue, derivedFrom) => ({
43
+ name,
44
+ cssVar: `--${name}`,
45
+ defaultValue,
46
+ group: 'component',
47
+ type: 'color',
48
+ ...(derivedFrom ? { derivedFrom } : {})
49
+ });
50
+
51
+ const scale = (name, cssVar, defaultValue, group, type = 'length') => ({
52
+ name,
53
+ cssVar,
54
+ defaultValue,
55
+ group,
56
+ type
57
+ });
58
+
59
+ const setting = (name, defaultValue, internal = false) => ({
60
+ name,
61
+ cssVar: `--${name}`,
62
+ defaultValue,
63
+ group: 'component',
64
+ type: 'string',
65
+ ...(internal ? { internal: true } : {})
66
+ });
67
+
68
+ // Default values below mirror the USWDS default theme color tokens
69
+ // (https://designsystem.digital.gov/design-tokens/color/theme-tokens/ and
70
+ // .../state-tokens/) so the "Default" theme matches USWDS out of the box.
71
+ export const themeManifest = [
72
+ // ── Color primitives ──────────────────────────────────────────────────────
73
+
74
+
75
+ // Tier names/values below match USWDS's official theme-color tables
76
+ // (designsystem.digital.gov/design-tokens/color/theme-tokens) exactly.
77
+ // `-hover`/`-active` are USX-specific convenience aliases (not real USWDS
78
+ // tokens) kept for existing component SCSS; they point at whichever real
79
+ // tier reads correctly as an interaction state (usually vivid/dark).
80
+ c('color-primary', '#005ea2', null, { family: 'blue', grade: '60', vivid: true }),
81
+ c('color-primary-lighter', '#d9e8f6', 'color-primary', { family: 'blue', grade: '10' }),
82
+ c('color-primary-light', '#73b3e7', 'color-primary', { family: 'blue', grade: '30' }),
83
+ c('color-primary-vivid', '#0050d8', 'color-primary', { family: 'blue-warm', grade: '60', vivid: true }),
84
+ c('color-primary-dark', '#1a4480', 'color-primary', { family: 'blue-warm', grade: '70', vivid: true }),
85
+ c('color-primary-darker', '#162e51', 'color-primary', { family: 'blue-warm', grade: '80', vivid: true }),
86
+ c('color-primary-hover', 'var(--usx-color-primary-dark)', 'color-primary'),
87
+ c('color-primary-active', 'var(--usx-color-primary-darker)', 'color-primary'),
88
+
89
+ c('color-secondary', '#d83933', null, { family: 'red', grade: '50' }),
90
+ c('color-secondary-lighter', '#f3e1e4', 'color-secondary', { family: 'red-cool', grade: '10' }),
91
+ c('color-secondary-light', '#f2938c', 'color-secondary', { family: 'red', grade: '30' }),
92
+ c('color-secondary-vivid', '#e41d3d', 'color-secondary', { family: 'red-cool', grade: '50', vivid: true }),
93
+ c('color-secondary-dark', '#b50909', 'color-secondary', { family: 'red', grade: '60', vivid: true }),
94
+ c('color-secondary-darker', '#8b0a03', 'color-secondary', { family: 'red', grade: '70', vivid: true }),
95
+ c('color-secondary-hover', 'var(--usx-color-secondary-dark)', 'color-secondary'),
96
+ c('color-secondary-active', 'var(--usx-color-secondary-darker)', 'color-secondary'),
97
+
98
+ c('color-accent-cool', '#00bde3', null, { family: 'cyan', grade: '30', vivid: true }),
99
+ c('color-accent-cool-lighter', '#e1f3f8', 'color-accent-cool', { family: 'blue-cool', grade: '5', vivid: true }),
100
+ c('color-accent-cool-light', '#97d4ea', 'color-accent-cool', { family: 'blue-cool', grade: '20', vivid: true }),
101
+ c('color-accent-cool-dark', '#28a0cb', 'color-accent-cool', { family: 'blue-cool', grade: '40', vivid: true }),
102
+ c('color-accent-cool-darker', '#07648d', 'color-accent-cool', { family: 'blue-cool', grade: '60', vivid: true }),
103
+ c('color-accent-cool-hover', 'var(--usx-color-accent-cool-dark)', 'color-accent-cool'),
104
+ c('color-accent-cool-active', 'var(--usx-color-accent-cool-darker)', 'color-accent-cool'),
105
+
106
+ c('color-accent-warm', '#fa9441', null, { family: 'orange', grade: '30', vivid: true }),
107
+ c('color-accent-warm-lighter', '#f2e4d4', 'color-accent-warm', { family: 'orange', grade: '10' }),
108
+ c('color-accent-warm-light', '#ffbc78', 'color-accent-warm', { family: 'orange', grade: '20', vivid: true }),
109
+ c('color-accent-warm-dark', '#c05600', 'color-accent-warm', { family: 'orange', grade: '50', vivid: true }),
110
+ c('color-accent-warm-darker', '#775540', 'color-accent-warm', { family: 'orange', grade: '60' }),
111
+ c('color-accent-warm-hover', 'var(--usx-color-accent-warm-dark)', 'color-accent-warm'),
112
+ c('color-accent-warm-active', 'var(--usx-color-accent-warm-darker)', 'color-accent-warm'),
113
+
114
+ c('color-base', '#71767a', null, { family: 'gray-cool', grade: '50' }),
115
+ c('color-base-lightest', '#f0f0f0', 'color-base', { family: 'gray', grade: '5' }),
116
+ c('color-base-lighter', '#dfe1e2', 'color-base', { family: 'gray-cool', grade: '10' }),
117
+ c('color-base-light', '#a9aeb1', 'color-base', { family: 'gray-cool', grade: '30' }),
118
+ c('color-base-dark', '#565c65', 'color-base', { family: 'gray-cool', grade: '60' }),
119
+ c('color-base-darker', '#3d4551', 'color-base', { family: 'gray-cool', grade: '70' }),
120
+ c('color-base-darkest', '#1b1b1b', 'color-base', { family: 'gray', grade: '90' }),
121
+ c('color-base-ink', '#1b1b1b', 'color-base', { family: 'gray', grade: '90' }),
122
+ c('color-base-hover', 'var(--usx-color-base-dark)', 'color-base'),
123
+ c('color-base-active', 'var(--usx-color-base-darker)', 'color-base'),
124
+
125
+ c('color-info', '#00bde3', null, { family: 'cyan', grade: '30', vivid: true }),
126
+ c('color-info-lighter', '#e7f6f8', 'color-info', { family: 'cyan', grade: '5' }),
127
+ c('color-info-light', '#99deea', 'color-info', { family: 'cyan', grade: '20' }),
128
+ c('color-info-dark', '#009ec1', 'color-info', { family: 'cyan', grade: '40', vivid: true }),
129
+ c('color-info-darker', '#2e6276', 'color-info', { family: 'blue-cool', grade: '60' }),
130
+
131
+ c('color-warning', '#ffbe2e', null, { family: 'gold', grade: '20', vivid: true }),
132
+ c('color-warning-lighter', '#faf3d1', 'color-warning', { family: 'yellow', grade: '5' }),
133
+ c('color-warning-light', '#fee685', 'color-warning', { family: 'yellow', grade: '10', vivid: true }),
134
+ c('color-warning-dark', '#e5a000', 'color-warning', { family: 'gold', grade: '30', vivid: true }),
135
+ c('color-warning-darker', '#936f38', 'color-warning', { family: 'gold', grade: '50', vivid: true }),
136
+
137
+ c('color-success', '#00a91c', null, { family: 'green-cool', grade: '40', vivid: true }),
138
+ c('color-success-lighter', '#ecf3ec', 'color-success', { family: 'green-cool', grade: '5' }),
139
+ c('color-success-light', '#70e17b', 'color-success', { family: 'green-cool', grade: '20', vivid: true }),
140
+ c('color-success-dark', '#008817', 'color-success', { family: 'green-cool', grade: '50', vivid: true }),
141
+ c('color-success-darker', '#216e1f', 'color-success', { family: 'green-cool', grade: '60', vivid: true }),
142
+
143
+ c('color-error', '#d54309', null, { family: 'red-warm', grade: '50', vivid: true }),
144
+ c('color-error-lighter', '#f4e3db', 'color-error', { family: 'red-warm', grade: '10' }),
145
+ c('color-error-light', '#f39268', 'color-error', { family: 'red-warm', grade: '30', vivid: true }),
146
+ c('color-error-dark', '#b50909', 'color-error', { family: 'red', grade: '60', vivid: true }),
147
+ c('color-error-darker', '#6f3331', 'color-error', { family: 'red', grade: '70' }),
148
+
149
+ c('color-emergency', '#9c3d10', null, { family: 'red-warm', grade: '60', vivid: true }),
150
+ c('color-emergency-dark', '#332d29', 'color-emergency', { family: 'red-warm', grade: '80' }),
151
+
152
+ // USWDS state color with no default USX theme role until now — added to
153
+ // complete parity with designsystem.digital.gov/design-tokens/color/state-tokens.
154
+ c('color-disabled', '#757575', null, { family: 'gray', grade: '50' }),
155
+ c('color-disabled-lighter', '#c9c9c9', 'color-disabled', { family: 'gray', grade: '20' }),
156
+ c('color-disabled-light', '#919191', 'color-disabled', { family: 'gray', grade: '40' }),
157
+ c('color-disabled-dark', '#454545', 'color-disabled', { family: 'gray', grade: '70' }),
158
+ c('color-disabled-darker', '#1b1b1b', 'color-disabled', { family: 'gray', grade: '90' }),
159
+
160
+ // USWDS's own default focus-outline color (`.usa-focus`, native form
161
+ // controls, etc.) — surfaced as a real state token so it can be
162
+ // overridden in one place instead of only per-component.
163
+ c('color-focus', '#2491ff', null, { family: 'blue', grade: '40', vivid: true }),
164
+ // Was only a link-component token (usx-link-visited-color); surfaced here
165
+ // so any component can chain to it, same as the other state colors.
166
+ c('color-visited', '#54278f', null, { family: 'violet', grade: '70', vivid: true }),
167
+
168
+ // Surface abstraction: components chain to these three roles — not
169
+ // directly to color-light/color-base-lightest/color-base-lighter — so a
170
+ // dark theme can repoint them freely without the confusing "lightest is
171
+ // actually the darkest surface" naming a raw USWDS base-tier alias would
172
+ // create. No `derivedFrom`: unlike shade scales, a dark preset's surface-2
173
+ // isn't a computed tint of surface-1, it's an independent value.
174
+ // surface-1 — the largest surfaces (page/main content background)
175
+ // surface-2 — table headers, accordion button, callouts, footer primary
176
+ // section, banner, task-list hover
177
+ // surface-3 — header/primary nav (default empty — see
178
+ // usx-header-background-color/usx-nav-background-color
179
+ // below), table grouped rows, footer secondary section,
180
+ // carousel media
181
+ // Default values match the real USWDS base-family tiers they replace, so
182
+ // the Default theme is pixel-identical to plain USWDS.
183
+ c('surface-1', '#ffffff'),
184
+ c('surface-2', '#e6e6e6'),
185
+ c('surface-3', '#f0f0f0'),
186
+
187
+ // Shared border color: checkbox/radio tiles, task-list item dividers,
188
+ // sidenav item dividers, and the default (overridable) for header/footer
189
+ // section dividers all chain to this via their own usx-*-border-color hook.
190
+ c('color-border', '#c9c9c9', 'color-base-lighter'),
191
+
192
+ c('color-beta', '#2e8540'),
193
+ c('color-test', '#7800af'),
194
+ c('color-dev', '#ee9a2d'),
195
+
196
+ // ── Text colors ───────────────────────────────────────────────────────────
197
+
198
+ // Text abstraction: components chain to these four roles, mirroring the
199
+ // surface-1/surface-2/surface-3 pattern above, so a dark theme can flip
200
+ // ink colors without fighting a token literally named "light-text" when
201
+ // the surface it sits on isn't necessarily light. No `derivedFrom`: these
202
+ // are independent roles, not computed tints. Along with surface-1/2/3 and
203
+ // the theme/state color families, these are the only tokens needed to
204
+ // build background and text colors anywhere in USX — there is no separate
205
+ // raw text-color primitive tier.
206
+ // text-ink — primary body/ink color
207
+ // text-muted — real USWDS bakes several nav/stepper text colors to
208
+ // its "base-dark" gray (#565c65) at build time — darker
209
+ // than text-subtle (nav primary links/buttons, sidenav
210
+ // links, step-indicator pending labels & counters)
211
+ // text-subtle — secondary/muted text (secondary links, attributions,
212
+ // eyebrows, etc.)
213
+ // text-inverse — text drawn on a surface that's the opposite polarity
214
+ // of the theme's main surfaces (e.g. a colored button,
215
+ // or a banner/dropdown panel that stays dark even under
216
+ // a light theme)
217
+ c('text-ink', '#1b1b1b'),
218
+ c('text-muted', '#565c65'),
219
+ c('text-subtle', '#71767a'),
220
+ c('text-inverse', '#ffffff'),
221
+
222
+ // ── Spacing ───────────────────────────────────────────────────────────────
223
+ scale('spacing-xs', '--usx-spacing-xs', '0.25rem', 'spacing'),
224
+ scale('spacing-sm', '--usx-spacing-sm', '0.5rem', 'spacing'),
225
+ scale('spacing-md', '--usx-spacing-md', '1rem', 'spacing'),
226
+ scale('spacing-lg', '--usx-spacing-lg', '1.5rem', 'spacing'),
227
+ scale('spacing-xl', '--usx-spacing-xl', '2rem', 'spacing'),
228
+
229
+ // ── Radius ────────────────────────────────────────────────────────────────
230
+ // Primitives — reserved for utility classes.
231
+ scale('radius-sm', '--usx-radius-sm', '0.25rem', 'radius'),
232
+ scale('radius-md', '--usx-radius-md', '0.5rem', 'radius'),
233
+ scale('radius-lg', '--usx-radius-lg', '1rem', 'radius'),
234
+ scale('radius-xl', '--usx-radius-xl', '2rem', 'radius'),
235
+ scale('radius-full', '--usx-radius-full', '100%', 'radius'),
236
+
237
+ // Semantic radii — components pick a radius by group (DaisyUI-style).
238
+ scale('radius-box', '--usx-radius-box', '1rem', 'radius'),
239
+ scale('radius-field', '--usx-radius-field', '0', 'radius'),
240
+ scale('radius-button', '--usx-radius-button', '0.25rem', 'radius'),
241
+ scale('radius-selector', '--usx-radius-selector', '2px', 'radius'),
242
+ scale('radius-none', '--usx-radius-none', '0', 'radius'),
243
+
244
+ // ── Advanced radius — per-component overrides, defaulting to the group var ─
245
+ scale('usx-accordion-radius', '--usx-accordion-radius', 'var(--usx-radius-none)', 'radius-advanced'),
246
+ scale('usx-alert-radius', '--usx-alert-radius', 'var(--usx-radius-none)', 'radius-advanced'),
247
+ scale('usx-input-radius', '--usx-input-radius', 'var(--usx-radius-field)', 'radius-advanced'),
248
+ scale('usx-textarea-radius', '--usx-textarea-radius', 'var(--usx-radius-field)', 'radius-advanced'),
249
+ scale('usx-code-radius', '--usx-code-radius', 'var(--usx-radius-box)', 'radius-advanced'),
250
+ scale('usx-summary-box-radius', '--usx-summary-box-radius', 'var(--usx-radius-box)', 'radius-advanced'),
251
+ scale('usx-hero-callout-radius', '--usx-hero-callout-radius', 'var(--usx-radius-box)', 'radius-advanced'),
252
+ scale('usx-image-radius', '--usx-image-radius', 'var(--usx-radius-box)', 'radius-advanced'),
253
+ scale('usx-tag-radius', '--usx-tag-radius', 'var(--usx-radius-selector)', 'radius-advanced'),
254
+ scale('usx-checkable-radius', '--usx-checkable-radius', 'var(--usx-radius-selector)', 'radius-advanced'),
255
+ scale('usx-checkable-tile-radius', '--usx-checkable-tile-radius', '0.25rem', 'radius-advanced'),
256
+ scale('usx-misc-banner-badge-radius', '--usx-misc-banner-badge-radius', '6px', 'radius-advanced'),
257
+
258
+ // ── Border widths ─────────────────────────────────────────────────────────
259
+ scale('border-width-sm', '--usx-border-width-sm', '1px', 'border'),
260
+ scale('border-width-md', '--usx-border-width-md', '2px', 'border'),
261
+ scale('border-width-lg', '--usx-border-width-lg', '0.25rem', 'border'),
262
+ scale('border-width-xl', '--usx-border-width-xl', '0.5rem', 'border'),
263
+ scale('border-width-inputs', '--usx-border-width-inputs', 'var(--usx-border-width-sm)', 'border'),
264
+
265
+ // ── Advanced border — per-component overrides, defaulting to a shared width ─
266
+ scale('usx-task-list-inner-border-width', '--usx-task-list-inner-border-width', 'var(--usx-border-width-sm)', 'border-advanced'),
267
+ scale('usx-task-list-outer-border-width', '--usx-task-list-outer-border-width', 'var(--usx-border-width-md)', 'border-advanced'),
268
+ scale('usx-step-indicator-segment-bar-width', '--usx-step-indicator-segment-bar-width', 'var(--usx-border-width-xl)', 'border-advanced'),
269
+ scale('usx-accordion-border-width', '--usx-accordion-border-width', 'var(--usx-border-width-lg)', 'border-advanced'),
270
+ scale('usx-button-outline-border-width', '--usx-button-outline-border-width', 'var(--usx-border-width-md)', 'border-advanced'),
271
+ scale('usx-switch-border-width', '--usx-switch-border-width', '2px', 'border-advanced'),
272
+ scale('usx-file-input-border-width', '--usx-file-input-border-width', 'var(--usx-border-width-inputs)', 'border-advanced'),
273
+ scale('usx-file-input-item-border-width', '--usx-file-input-item-border-width', 'var(--usx-border-width-sm)', 'border-advanced'),
274
+ scale('usx-range-slider-border-width', '--usx-range-slider-border-width', 'var(--usx-border-width-md)', 'border-advanced'),
275
+
276
+ // ── Typography ────────────────────────────────────────────────────────────
277
+ // Font stack matches the actual family baked into the precompiled USWDS
278
+ // bundle (Source Sans Pro Web) so the default theme causes zero visual
279
+ // drift.
280
+ scale('font-family', '--usx-font-family',
281
+ 'Source Sans Pro Web, Helvetica Neue, Helvetica, Roboto, Arial, sans-serif',
282
+ 'typography', 'font-family'),
283
+ // Chains to font-family by default — see _variables.scss.
284
+ scale('font-family-heading', '--usx-font-family-heading', 'var(--usx-font-family)',
285
+ 'typography', 'font-family'),
286
+ scale('font-size-base', '--usx-font-size-base', '1rem', 'typography'),
287
+ scale('font-size-h1', '--usx-font-size-h1', '2.5rem', 'typography'),
288
+ scale('font-size-h2', '--usx-font-size-h2', '2rem', 'typography'),
289
+ scale('font-size-h3', '--usx-font-size-h3', '1.75rem', 'typography'),
290
+ scale('font-size-h4', '--usx-font-size-h4', '1.5rem', 'typography'),
291
+ scale('font-size-h5', '--usx-font-size-h5', '1.25rem', 'typography'),
292
+ scale('font-size-h6', '--usx-font-size-h6', '1rem', 'typography'),
293
+ scale('font-weight-regular', '--usx-font-weight-regular', '400', 'typography', 'number'),
294
+ scale('font-weight-bold', '--usx-font-weight-bold', '700', 'typography', 'number'),
295
+ scale('line-height-base', '--usx-line-height-base', '1.5', 'typography', 'number'),
296
+ scale('line-height-heading', '--usx-line-height-heading', '1.2', 'typography', 'number'),
297
+
298
+ // ── Typography — component overrides (hardcoded-value promotions) ────────
299
+ // One-off font-size/weight/line-height values that don't fit the shared
300
+ // base/heading scale, promoted from hardcoded component SCSS literals to
301
+ // real runtime-themeable tokens, same pattern as radius-advanced.
302
+ scale('usx-attribution-primary-font-weight', '--usx-attribution-primary-font-weight', '600', 'typography-advanced', 'number'),
303
+ scale('usx-attribution-secondary-font-size', '--usx-attribution-secondary-font-size', '0.875rem', 'typography-advanced'),
304
+ scale('usx-avatar-content-font-weight', '--usx-avatar-content-font-weight', '600', 'typography-advanced', 'number'),
305
+ scale('usx-avatar-font-size-sm', '--usx-avatar-font-size-sm', '0.875rem', 'typography-advanced'),
306
+ scale('usx-avatar-font-size-lg', '--usx-avatar-font-size-lg', '1.25rem', 'typography-advanced'),
307
+ scale('usx-avatar-font-size-xl', '--usx-avatar-font-size-xl', '1.5rem', 'typography-advanced'),
308
+ scale('usx-calendar-date-font-size', '--usx-calendar-date-font-size', '1.13rem', 'typography-advanced'),
309
+ scale('usx-code-font-size', '--usx-code-font-size', '0.875rem', 'typography-advanced'),
310
+ scale('usx-misc-banner-font-size', '--usx-misc-banner-font-size', '1.07rem', 'typography-advanced'),
311
+ scale('usx-modal-heading-font-size', '--usx-modal-heading-font-size', '1.34rem', 'typography-advanced'),
312
+ scale('usx-tag-line-height', '--usx-tag-line-height', '1.6', 'typography-advanced', 'number'),
313
+ scale('usx-task-list-link-font-size', '--usx-task-list-link-font-size', '1.31rem', 'typography-advanced'),
314
+
315
+ // ── Component tokens (hardcoded-hex promotions) ───────────────────────────
316
+ component('usx-link-text', '#005ea2', 'color-primary'),
317
+ component('usx-link-text-visited', 'var(--usx-color-visited)'),
318
+ // USWDS swaps link/visited color to a light neutral on a dark background
319
+ // (`.usa-dark-background`) instead of the illegible blue/purple defaults;
320
+ // no existing primitive matches this exact shade, so it gets its own token.
321
+ component('usx-link-text-dark', '#e6e6e6'),
322
+ component('usx-link-text-visited-dark', '#e6e6e6'),
323
+ component('usx-summary-box-link-text-visited', 'var(--usx-color-visited)'),
324
+ // USWDS's summary-box "info" tint (background "info-lighter"/cyan-5,
325
+ // border "info-light"/cyan-20) matches our color-info-lighter/
326
+ // color-info-light primitives exactly, so chain to those (like
327
+ // usx-alert-info-background-color does) instead of a standalone duplicate.
328
+ component('usx-summary-box-bg', '#e7f6f8', 'color-info-lighter'),
329
+ component('usx-summary-box-border-color', '#99deea', 'color-info-light'),
330
+ component('usx-summary-box-text', '#1b1b1b', 'text-ink'),
331
+ component('usx-summary-box-link-text', '#005ea2', 'color-primary'),
332
+ component('usx-summary-box-link-text-hover', '#1a4480', 'color-primary-dark'),
333
+ component('usx-accordion-bg', '#f0f0f0', 'surface-2'),
334
+ component('usx-accordion-bg-hover', '#e6e6e6', 'surface-3'),
335
+ component('usx-accordion-content-bg', '#ffffff', 'surface-1'),
336
+ component('usx-accordion-text', 'var(--usx-text-ink)'),
337
+ component('usx-accordion-content-text', 'var(--usx-text-ink)'),
338
+ setting('usx-accordion-icon-position', '1.25rem auto'),
339
+ setting('usx-accordion-icon-padding-start', '3.5rem', true),
340
+ setting('usx-accordion-icon-padding-end', '1.25rem', true),
341
+ // Branding logo swap. A CSS variable can't rewrite an <img> src, so the
342
+ // theme toggles which of the two supplied variants is displayed instead.
343
+ setting('usx-logo-display', 'block'),
344
+ setting('usx-logo-inverse-display', 'none', true),
345
+ // USWDS defaults: background "base-lightest" (-> surface-2), auto-contrast
346
+ // text (-> text), action link/chevron following the shared link color.
347
+ component('usx-banner-bg', '#dfe1e2', 'surface-2'),
348
+ component('usx-banner-text', '#1b1b1b', 'text-ink'),
349
+ component('usx-banner-button-text', '#005ea2', 'usx-link-text'),
350
+ component('usx-banner-chevron-text', '#005ea2', 'color-primary'),
351
+ component('usx-carousel-dot-bg', '#c0c0c0'),
352
+ component('usx-carousel-dot-bg-hover', '#a0a0a0', 'usx-carousel-dot-bg'),
353
+ component('usx-carousel-dot-bg-active', '#808080', 'usx-carousel-dot-bg'),
354
+ component('usx-carousel-focus', 'var(--usx-color-focus)'),
355
+ // Real USWDS ships the step-indicator's CSS precompiled — these two get
356
+ // their own hooks (unlike the plain $color-primary-dark/-darker aliases
357
+ // in _variables.scss) so the --counters variant's ring/gap colors are
358
+ // independently overridable from the general surface/border roles.
359
+ component('usx-step-indicator-bg', 'var(--usx-surface-1)'),
360
+ component('usx-step-indicator-segment-pending-border', 'var(--usx-color-base-light)'),
361
+ // Follows usx-link-color by default (a `var()` reference, like the
362
+ // radius-advanced tokens default to `var(--usx-radius-none)`) unless its
363
+ // own custom property is explicitly overridden.
364
+ component('usx-task-list-link-text', 'var(--usx-link-text)'),
365
+ component('usx-clickable-focus', 'var(--usx-color-focus)'),
366
+ component('usx-clickable-text-hover', '#005ea2'),
367
+ component('usx-misc-banner-focus', '#9bdaf1'),
368
+ // Per-component overrides of the shared color-border token (see above).
369
+ component('usx-checkable-tile-border', 'var(--usx-color-border)'),
370
+ component('usx-task-list-border', 'var(--usx-color-border)'),
371
+ component('usx-file-input-border', 'var(--usx-color-border)'),
372
+ component('usx-file-input-item-border', 'var(--usx-color-border)'),
373
+ // USWDS bakes the "N files selected" heading and each preview row to a
374
+ // fixed "primary-lighter" callout background (with default body text),
375
+ // regardless of theme — same callout role as usx-accordion-bg/
376
+ // usx-banner-bg, so it chains to the same surface-2/text roles.
377
+ component('usx-file-input-preview-bg', '#d9e8f6', 'surface-2'),
378
+ component('usx-file-input-preview-text', '#1b1b1b', 'text-ink'),
379
+ component('usx-task-list-bg-hover', 'var(--usx-surface-2)'),
380
+ // Shared by both checkbox and radio unchecked ::before styling.
381
+ component('usx-checkable-bg', 'var(--usx-surface-1)'),
382
+ component('usx-checkable-border', 'var(--usx-text-ink)'),
383
+ component('usx-sidenav-bg-hover', 'var(--usx-surface-2)'),
384
+ component('usx-sidenav-border', 'var(--usx-color-border)'),
385
+ // Unlike tile/selector/sidenav (which have a visible border in USWDS by
386
+ // default), real USWDS renders .usa-header/.usa-footer with NO border at
387
+ // all by default, so these must stay invisible until a consumer opts in,
388
+ // rather than chaining to color-border's visible gray.
389
+ component('usx-header-border', 'transparent'),
390
+ // Same reasoning as the borders above: real USWDS renders .usa-header
391
+ // with no explicit background at all (just the ambient page background),
392
+ // so this stays transparent by default; the Sass fallback chains to
393
+ // surface-3 for consumers/presets that opt into a themed shell.
394
+ component('usx-header-bg', 'transparent'),
395
+ // Chains to $text at the Sass level (see _variables.scss), so unthemed
396
+ // output just inherits the ambient ink color; a theme with a dark/colored
397
+ // header can override this independently to var(--usx-text-inverse).
398
+ component('usx-header-text', 'var(--usx-text-ink)'),
399
+ // Unlike the header, real USWDS's mobile nav IS an opaque white off-canvas
400
+ // drawer (not "no background") — default to that explicitly rather than
401
+ // transparent, or the drawer becomes see-through until a theme opts in.
402
+ component('usx-header-nav-bg', '#ffffff'),
403
+ // Independent mobile-drawer override; defaults the same as desktop so
404
+ // Default's mobile drawer stays opaque white unless a theme opts in.
405
+ component('usx-header-nav-bg-mobile', '#ffffff'),
406
+ // Chains to $text-muted at the Sass level, matching real USWDS's baked-in
407
+ // nav/link gray; own hook so a theme with a dark/branded nav surface can
408
+ // override it independently (see usx-header-text above).
409
+ component('usx-header-nav-link-text', 'var(--usx-text-muted)'),
410
+ // Independent drawer override — the off-canvas mobile menu is a visually
411
+ // distinct surface from the desktop nav bar.
412
+ component('usx-header-nav-link-text-mobile', 'var(--usx-header-nav-link-text)'),
413
+ component('usx-header-nav-link-text-hover', 'var(--usx-color-primary)'),
414
+ component('usx-header-nav-link-text-hover-mobile', 'var(--usx-header-nav-link-text-hover)'),
415
+ // Chains to $text-subtle at the Sass level; own hook for the same reason
416
+ // as usx-nav-link-color above.
417
+ component('usx-header-secondary-link-text', 'var(--usx-text-subtle)'),
418
+ component('usx-header-secondary-link-text-hover', 'var(--usx-color-primary)'),
419
+ // Vendor hardcodes #f0f0f0 for nav link/button hover (mobile only) — track
420
+ // surface-2 so it stays distinct from the surface-3 drawer/header once a
421
+ // theme is active.
422
+ component('usx-header-nav-link-bg-hover', 'var(--usx-surface-2)'),
423
+ component('usx-header-nav-top-border', 'var(--usx-color-border)'),
424
+ component('usx-header-nav-bottom-border', 'transparent'),
425
+ component('usx-footer-border', 'transparent'),
426
+ component('usx-footer-primary-section-border', 'transparent'),
427
+ component('usx-footer-secondary-section-border', 'transparent'),
428
+ // Independent footer text/link/heading hooks — same rationale as the
429
+ // header text/nav-link hooks above: a theme whose footer secondary
430
+ // section sits on a drastically different surface (e.g. NASA's black
431
+ // surface-3) needs to override these without affecting the ambient
432
+ // text/color-primary roles used elsewhere.
433
+ component('usx-footer-text', 'var(--usx-text-ink)'),
434
+ component('usx-footer-link-text', 'var(--usx-color-primary)'),
435
+ component('usx-footer-link-text-hover', 'var(--usx-color-primary-dark)'),
436
+ component('usx-footer-secondary-link-text', 'var(--usx-color-primary)'),
437
+ component('usx-footer-secondary-link-text-hover', 'var(--usx-color-primary-dark)'),
438
+ component('usx-footer-heading-text', 'var(--usx-text-ink)'),
439
+ // Secondary-section headings (logo/contact) chain to the primary heading
440
+ // color by default but can be overridden independently since they sit on
441
+ // usx-footer-secondary-bg-color rather than usx-footer-primary-bg-color.
442
+ component('usx-footer-heading-secondary-text', 'var(--usx-footer-heading-text)'),
443
+ // Page chains its own background to surface-1 (some patterns render
444
+ // <Page> without a <Layout> ancestor to paint it); Section has no such
445
+ // fallback role and stays transparent, always nested in a Page/Layout
446
+ // that already paints the surface. Text on both chains live to usx-text
447
+ // so headings/copy stay legible instead of getting stuck on real USWDS's
448
+ // static, non-themeable body text color.
449
+ component('usx-page-bg', 'var(--usx-surface-1)'),
450
+ component('usx-page-text', 'var(--usx-text-ink)'),
451
+ component('usx-section-bg', 'transparent'),
452
+ component('usx-section-text', 'var(--usx-text-ink)'),
453
+ // Hover/selected row highlights alias the surface-2/surface-3 roles (like
454
+ // usx-sidenav-hover-color above) instead of a color-primary blue tint, so
455
+ // the table's aesthetic stays neutral and in step with the rest of the
456
+ // surface system rather than an unrelated brand-accent highlight.
457
+ component('usx-table-bg-hover', 'var(--usx-surface-2)'),
458
+ component('usx-table-selected-bg', 'var(--usx-surface-3)'),
459
+ // The table's own base cell/header/stripe backgrounds (real USWDS bakes
460
+ // these as static white/base-lighter/base-lightest), aliased to the same
461
+ // surface-1/2/3 roles so the whole table adapts to the theme, not just
462
+ // the hover/selected highlights above.
463
+ component('usx-table-bg', 'var(--usx-surface-1)'),
464
+ component('usx-table-header-bg', 'var(--usx-surface-2)'),
465
+ component('usx-table-stripe-bg', 'var(--usx-surface-3)'),
466
+ // Divider border (footer rule, sticky-first-column shadow line) — same
467
+ // shared color-border role used by header/footer/sidenav/tile dividers.
468
+ component('usx-table-border', 'var(--usx-color-border)'),
469
+ component('usx-table-placeholder-text', 'var(--usx-text-subtle)'),
470
+ component('usx-table-sorted-column-bg', 'var(--usx-color-accent-cool)'),
471
+ component('usx-table-grouped-row-bg', 'var(--usx-surface-3)'),
472
+ component('usx-table-grouped-row-text', 'var(--usx-text-ink)'),
473
+ // Real USWDS bakes the tooltip's background/font color as fixed values at
474
+ // its own build time, so it never followed our theme. Defaults mirror
475
+ // color-base-darkest/text-inverse (see _variables.scss) — a stable,
476
+ // theme-invariant dark neutral (not the per-preset brand-tinted
477
+ // color-dark-bg used by hero/banner) with light text on it.
478
+ component('usx-tooltip-bg', '#1b1b1b', 'color-base-darkest'),
479
+ component('usx-tooltip-text', '#ffffff', 'text-inverse'),
480
+ // Real USWDS only colors icon-list icons via opt-in BEM modifier classes;
481
+ // give it a themeable default (color-primary) instead.
482
+ component('usx-icon-list-icon-text', '#005ea2', 'color-primary'),
483
+
484
+ // Real USWDS bakes the date-picker toggle button's calendar icon as a
485
+ // fixed-color background-image at build time, so it never follows the
486
+ // theme; repainted via mask-image instead (see _date-picker.scss).
487
+ component('usx-date-picker-button-icon-color', 'var(--usx-text-ink)'),
488
+ // Hover and active share one token/color by design (see themePresets.js's
489
+ // Midnight/Carbon/Borealis overrides for why dark presets need their own
490
+ // hardcoded value here instead of just chaining to color-base-light).
491
+ component('usx-date-picker-button-hover-active-bg', '#a9aeb1', 'color-base-light'),
492
+
493
+ // Real USWDS's compiled CSS hardcodes the combo-box list's selected-option
494
+ // highlight to a literal `color("primary")` (never a runtime var), so it
495
+ // stayed USWDS default blue under every preset. Unlike the range
496
+ // slider/date-picker-icon below, this one IS just the ordinary brand
497
+ // primary color (not a fixed system gray), so it should track the theme —
498
+ // chain it to color-primary instead of leaving it static.
499
+ component('usx-combo-box-selected-bg', '#005ea2', 'color-primary'),
500
+
501
+ // Real USWDS bakes the range slider's track/thumb to fixed "base-lightest"/
502
+ // "base-darker" grays regardless of theme. Unlike most components, this
503
+ // one intentionally stays that way — text inputs/selects/textareas also
504
+ // keep a static white surface + gray border under every preset (see
505
+ // $usx-input-bg/-text/-border in _variables.scss, always null), so the
506
+ // range slider matches that same "form controls don't flip surfaces"
507
+ // convention instead of chasing the surface-1/2/3 abstraction.
508
+ component('usx-range-slider-track-bg', '#f0f0f0'),
509
+ component('usx-range-slider-track-border', '#3d4551'),
510
+ component('usx-range-slider-thumb-bg', '#f0f0f0'),
511
+ component('usx-range-slider-thumb-border', '#3d4551'),
512
+ component('usx-range-slider-focus', 'var(--usx-color-focus)'),
513
+
514
+ // Real USWDS resolves the in-page-nav card's background/text/link/current/
515
+ // bar colors from compile-time $theme-in-page-nav-* settings (all
516
+ // "default"), so none of them follow a runtime theme override on their
517
+ // own. Chain each role to the same shared tokens already used elsewhere
518
+ // (text, link-text, border, primary) — see _in-page-nav.scss. Background
519
+ // defaults transparent (opts back into a filled surface-2 card only on
520
+ // presets with a dark shell — see themePresets.js/Theme.stories.jsx).
521
+ component('usx-in-page-nav-bg', 'transparent'),
522
+ component('usx-in-page-nav-text', 'var(--usx-text-ink)'),
523
+ component('usx-in-page-nav-border', 'var(--usx-color-border)'),
524
+ component('usx-in-page-nav-link-text', 'var(--usx-link-text)'),
525
+ component('usx-in-page-nav-link-text-hover', 'var(--usx-link-text-hover)'),
526
+ component('usx-in-page-nav-current-text', 'var(--usx-text-ink)'),
527
+ component('usx-in-page-nav-bar', 'var(--usx-color-primary)'),
528
+
529
+ // Real USWDS hardcodes breadcrumb text/background to fixed
530
+ // #1b1b1b/#fff regardless of theme (nav/wayfinding element — should
531
+ // theme dynamically, unlike static form controls). Background defaults
532
+ // transparent so it inherits whatever surface it's placed on; text/link
533
+ // chain to the shared text/link tokens.
534
+ component('usx-breadcrumb-bg', 'transparent'),
535
+ component('usx-breadcrumb-text', 'var(--usx-text-ink)'),
536
+ component('usx-breadcrumb-link-text', 'var(--usx-link-text)'),
537
+ component('usx-breadcrumb-link-text-hover', 'var(--usx-link-text-hover)'),
538
+ component('usx-breadcrumb-current-text', 'var(--usx-text-ink)'),
539
+
540
+ // Real USWDS hardcodes process-list heading/number-circle text to
541
+ // #1b1b1b and the connecting line + circle border to fixed light-blue/
542
+ // gray-cool literals, none of which follow a runtime theme override.
543
+ component('usx-process-list-heading-text', 'var(--usx-text-ink)'),
544
+ component('usx-process-list-border', '#d9e8f6', 'color-primary'),
545
+ component('usx-process-list-counter-text', 'var(--usx-text-ink)'),
546
+ component('usx-process-list-counter-border', 'var(--usx-text-ink)'),
547
+ component('usx-process-list-counter-ring', 'var(--usx-surface-1)'),
548
+
549
+ // Real USWDS never sets a color on collection meta/description — they
550
+ // inherit ambient text (the heading link is already themed via .usx-link).
551
+ component('usx-collection-meta-text', 'var(--usx-text-subtle)'),
552
+ component('usx-collection-description-text', 'var(--usx-text-ink)'),
553
+
554
+ // Real USWDS's compiled CSS hardcodes pagination link/button text and the
555
+ // current-page underline to literal `color("primary")`/`color("primary-
556
+ // vivid")` — nav/wayfinding element, should track the theme.
557
+ component('usx-pagination-link-text', 'var(--usx-link-text)'),
558
+ component('usx-pagination-link-text-hover', 'var(--usx-link-text-hover)'),
559
+ component('usx-pagination-current-bg', 'var(--usx-color-primary)'),
560
+ component('usx-pagination-current-text', '#ffffff', 'text-inverse'),
561
+ // Real USWDS also hardcodes the `.usa-pagination` nav itself to a static
562
+ // white bg / #1b1b1b text, which floats as an opaque box over a themed
563
+ // (e.g. dark) page background instead of blending into it.
564
+ component('usx-pagination-bg', 'transparent'),
565
+ component('usx-pagination-text', 'var(--usx-text-ink)'),
566
+ // Real USWDS hardcodes the button border to rgba(27,27,27,.2), a black-
567
+ // based translucent border that's drowned out on dark surfaces.
568
+ component('usx-pagination-button-border', 'var(--usx-color-border)'),
569
+
570
+ // Real USWDS hardcodes the modal surface to a static white background
571
+ // with auto-contrast (effectively text-ink) body text, and the close
572
+ // button to color("base") / color("ink") on hover — none of which follow
573
+ // a runtime theme override.
574
+ component('usx-modal-bg', '#ffffff', 'surface-1'),
575
+ component('usx-modal-text', '#1b1b1b', 'text-ink'),
576
+ component('usx-modal-close-text', '#71767a', 'text-subtle'),
577
+ component('usx-modal-close-text-hover', '#1b1b1b', 'text-ink'),
578
+
579
+ // ── Component tokens: Language Selector (custom; defaults baked in SCSS) ──
580
+ // Real USWDS hardcodes the language-menu dropdown to color("primary-darker")
581
+ // bg / white text — a fixed dark nav-dropdown treatment, chained here to the
582
+ // theme's primary-darker shade instead of a static hex.
583
+ component('usx-language-selector-menu-bg', 'var(--usx-color-primary-darker)'),
584
+ component('usx-language-selector-menu-text', '#ffffff', 'text-inverse')
585
+ ];
586
+
587
+ export default themeManifest;
@@ -0,0 +1,3 @@
1
+ // Theme Sass entry point. This emits no component or utility selectors.
2
+
3
+ @forward './variables';