# Reusable theme CSS and Sass — foundation checkpoint

The reusable foundation now includes accepted wizard themes plus opt-in field, action and surface layers. Start with the [current adoption guide](adoption.md) and integrated CSS/Sass consumers. Historical foundation examples below illustrate theme scoping; their neutral-only description is superseded. No reference application was migrated.

## Files and build commands

- `scss/_tokens.scss`: the editable theme maps and six wizard typography roles, including responsive overrides. This is the value source of truth.
- `scss/_index.scss`: reusable `theme()` and `type()` mixins; importing it emits no CSS.
- `scss/theme.scss`: default theme entry point.
- `theme.css`: generated, checked-in CSS. Do not hand-edit it.
- `examples/theme/css.html`: a CSS-only consumer; no Sass or page JavaScript required.
- `examples/theme/scoped.scss` / `scoped.html` / generated `scoped.css`: a scoped Sass consumer with explicit demo overrides.

```sh
npm ci
npm run build:theme
npm run check:theme
npm run build:examples
npm run test:theme
```

Sass is pinned at 1.93.2 in devDependencies with a lockfile. Consumers of the generated CSS don't need Node, npm or Sass. `check:theme` compiles in memory and fails if the committed CSS differs; it doesn't rewrite files. The contract checks cover import isolation, partial scoped overrides with responsive roles, and unknown-role rejection.

The existing `npm run dev` still starts the static site. Open typography.html or the example paths through that server. No root Sass build applies styles to the old index page; that page only links to the new typography checkpoint.

## CSS-only consumption

Copy theme.css into your app's assets and load it before your component styles:

```html
<link rel="stylesheet" href="/assets/kb/theme.css">
```

The default scope is `:root` (light), with `:root.dark` for dark. Your app owns preference/persistence and root-class activation; this stylesheet contains no JavaScript and doesn't inspect OS preferences.

```css
/* These selectors are your component, not a reset shipped by the theme. */
.application {
  font-family: var(--kb-font-family-ui);
  background: var(--kb-surface-page);
  color: var(--kb-text-primary);
}
.application-title {
  font-size: var(--kb-type-page-title-size);
  line-height: var(--kb-type-page-title-line-height);
  font-weight: var(--kb-type-page-title-weight);
  letter-spacing: var(--kb-type-letter-spacing);
}
```

Adding class="dark" to the root changes the variables and native color-scheme. Root scope affects inherited tokens and browser-native color scheme; if that is inappropriate, generate a scoped theme with Sass instead. There are no global body/input/button rules, font downloads, asset URLs or CSS resets in theme.css. A background/color/font only applies when your component consumes the variables.

## Sass consumption

Copy the whole scss directory so relative modules remain together. Example within this repository:

```scss
@use '../../scss' as kb;

@include kb.theme(
  $selector: '.example-theme',
  $dark-selector: '.example-theme.dark',
  $light-overrides: (brand-primary: #123456),
  $dark-overrides: (brand-primary: #abcdef)
);

.example-title { @include kb.type(page-title); }
.example-value { @include kb.type(value); }
```

The example's override hex values are test/demo values, not new company colors. Only those keys are replaced; the rest of the maps remain intact. The mixin also accepts `$font-family` when a consumer needs an approved alternate fallback chain. Don't emit both the default theme.css and an equivalent root Sass theme; choose one path to avoid duplicate output.

Place your scoped light and dark classes on the same host element (as in the example). The dark selector needs to match that host or a descendant that inherits the light variables; it does not emit another full typography set. Generated media rules target the light host selector at 640px and 1024px, while dark overrides remain color-only. Theme selectors are an integration contract: changing your React state alone doesn't activate CSS.

`kb.type(role)` sets font-family, font-size, line-height, font-weight and letter-spacing using runtime variables. Unknown roles fail Sass compilation. The mixin does not set color, margins, padding, radius, borders or shadows. Runtime custom-property overrides continue to work inside the chosen scope. Sass APIs/maps are available through the index, but changing source maps is a deliberate standard update requiring regeneration and review, not a way to edit generated CSS by hand.

## Current public token roles

All variables use the `--kb-` prefix to avoid the reference applications' generic token names.

| Group | Variables / values |
|---|---|
| Family | `--kb-font-family-ui`: existing WSDMM family chain; no font files are bundled |
| Typography | `--kb-type-{role}-{size,line-height,weight}` for page-title, step-title, section-title, description, label and value |
| Letter spacing | `--kb-type-letter-spacing`: normal |
| Neutral surfaces | `--kb-surface-page`, `--kb-surface-content` |
| Neutral text | `--kb-text-primary`, `--kb-text-secondary` |
| Neutral border | `--kb-border-default`: a color, not a border shorthand or a focus style |
| Brand anchor | `--kb-brand-primary`: #00457e in both themes; no button variant implied |
| Link text | `--kb-text-link`: the previous review page's #00457e / #a3c9ff |

The neutral values are the R002 review canvas, unchanged by this extraction: light #fafafa/#ffffff surfaces, #202020/#626262 text, #dedede border; dark #161616/#1c1c1c surfaces, #f1f1f1/#b6b6b6 text, #424242 border. These are foundation review values, not approval to replace palettes in other apps or a certification for every control-state contrast combination.

Responsive type values are the wizard baseline, not globally scaled guide sizes. At640px page title/description switch to desktop values and step title becomes 20px/28px; at 1024px step title becomes 24px/32px. Label/value/section roles retain their sizes. See [typography-baseline.md](typography-baseline.md) and the preserved measured reference.

R004 adds field-only validation colors and an optional fields.css/Sass mixin, documented in fields-usage.md. It also adds field geometry tokens. These do not establish the general button/card/status palette.

No status palette is emitted yet: company swatches and application semantic colors conflict, and those choices belong to the badge/state checkpoint. M2 initially emitted no radius/spacing/component tokens; R004 now emits source-based field geometry with fields.css. Card/shadow and other component tokens remain deferred.

## Font and distribution boundary

The theme only declares the font-family token. typography.css separately loads the same Google Fonts source used in the wizard, so font loading remains a reference-site choice. The CSS-only example makes no external requests and may use a locally installed Noto Sans or its fallback. Include approved licensed font assets separately if a consumer requires offline operation; font binaries, license notices and cross-platform coverage still need release review.

This is a checked-in file/Sass API, not a published npm package. Publishing, versioned package exports and final license/asset packaging are later handoff work. Don't infer new-task restoration or a published environment from a successful local build.

## Verification for R003

- Generated theme.css exactly matches the Sass source.
- Three Sass API contracts passed: import-only isolation, scoped partial overrides/responsive output, unknown-role failure.
- Browser consumer checks passed at 1440px, 768px and 390px: CSS-only page without page JavaScript, both themes, separate scoped light/dark tokens, partial consumer overrides, isolated runtime type override, and no leakage to outside/root content.
- Typography baseline checks still match all six wizard roles at desktop/mobile and source breakpoints at tablet, in both themes. Theme control/persistence, source-image switching and narrow-screen reflow pass.
- These checks don't certify all component states, offline fonts, accessibility or distribution. Those areas remain on the roadmap.

Browser results: [consumer checks](reference/theme-consumer-verification.json) and [typography regression checks](reference/theme-typography-verification.json). The original KB functional smoke suite also passed after this extraction. The initial neutral canvas is retained, not a broader palette migration.

## Wizard surface roles — R010

The default theme now follows the wizard surface direction. `--kb-surface-page` remains a color fallback; apply `background-image: var(--kb-background-page)` for the full gradient. Cards consume `--kb-surface-content`, `--kb-surface-border` and `backdrop-filter: blur(var(--kb-surface-backdrop-blur))`. Chrome and inset roles are separate; no global card/body selectors are emitted by theme.css. See fields-review.css for opt-in usage. Dark alpha fills depend on the background and cannot be replaced by flat swatches. The reference glows are static presentation effects, not mandatory application behavior. Text, status and action roles were not recolored in this step.

## Optional action layer — R015

`actions.css` / `@include kb.actions()` consumes the new action-* and badge-* color roles without global resets. See [action usage](actions-usage.md) for source geometry and explicit contrast refinements. This is separate from fields.css; neither component layer is required when consuming theme variables alone.
