# Cards, panels and native dialogs — M5 checkpoint

[Live surface reference](../cards.html) · [Source audit](wizard-cards-audit.md). This is a local review checkpoint, not an application migration. Source captures and the shared examples have different content; compare surface roles and measured geometry rather than treating them as identical screens.

## Reusable CSS and Sass

```html
<link rel="stylesheet" href="theme.css">
<link rel="stylesheet" href="surfaces.css">
<section class="kb-card" aria-labelledby="overview-title">
  <div class="kb-card-body"><h2 id="overview-title">Overview</h2></div>
  <footer class="kb-card-footer">Related actions</footer>
</section>
```

```scss
@use 'path/to/kb/scss' as kb;
@include kb.theme();
@include kb.surfaces();
```

Importing the API alone emits nothing. Theme output remains variables/scopes only; surfaces.css is an optional named component layer. Consumers own typography loading, content, behavior and application state. Add fields.css/actions.css only when those components are used. Run build:theme, build:surfaces and build:examples; check generated drift with check:theme/check:surfaces. Editable sources are scss/_tokens.scss and scss/_surface-styles.scss, not generated CSS.

## Static surface roles

- `.kb-card`: white light / white-10 dark, 1px source border, dark blur 40px, no shadow. Radius 16px below 640px / 24px above. Body padding 16px / 24px at 640px / 32px at 1024px.
- `.kb-panel`: 14px radius; white light / white-5 dark, no shadow/blur. Header padding 16px block/24px inline, themed fill and separator. Body padding 24px. It does not inherit main-card padding.
- `.kb-inset`: 14px radius and 16px padding; gray-50 light / white-5 dark. Separate inset border role.
- `.kb-card-footer`: inset fill, top separator and responsive 16px/24px padding, 32px inline above 1024px.
- `.kb-surface-description`: uses the semantic surface-text-muted color. The new dark role is #e0e0e0 after #d1d5db measured below 4.5:1 on some nested panels. Existing global text/field/action tokens are unchanged. Field helper text inside these new cards/panels uses the readable surface role too.

Static cards have no tabindex, pointer cursor, automatic hover lift or click handler. Headings/values use the accepted type roles; the wrapper is not a replacement for a correctly structured document. Do not apply interactive semantics merely because a surface has a border.

## Native modal families

Use a real `<dialog class="kb-dialog">` with aria-labelledby pointing to a visible heading and aria-describedby for useful context. Call showModal(), not just set open. Native modality places it in the top layer and makes the rest of the document inert; CSS cannot supply those behaviors by itself.

Dense default: max width 672px, 16px radius, white/light or source gray-900/95 dark; dark blur 24px; source 0 25px 50px -12px black/25 shadow in both themes. Scrim black/60 with 8px blur. Width leaves 16px outer gutters; max height 90dvh.

`.kb-dialog--glass`: max width 896px, mobile radius 14px / desktop16px, white/light or white-10 dark; dark blur40px. Source shadow in light, none in dark. Scrim black/50 with 8px blur. Gutters8px mobile /16px desktop; max height90dvh mobile /85dvh desktop.

Structure the header/body/footer as `.kb-dialog-header`, `.kb-dialog-body`, `.kb-dialog-footer`. Only the body scrolls; actions/title remain available. Header padding matches the dense/glass source variants. R022 follows source dividers explicitly: dense header/footer and glass footer use gray-200 in light and white/10 in dark. The glass header retains the source white/10 separator in both themes; its subtle light-mode appearance is deliberate source fidelity. Dynamic viewport units are a mobile refinement over source vh units.

The reference uses named Lucide close icons, explicit initial focus, Tab/Shift+Tab edge wrapping, native Escape cancellation, close-to-trigger focus restoration, background scroll locking with prior host-style restoration, and native radio choices. Scope consumer lifecycle carefully; a stylesheet alone does not implement this contract. The local JS is demonstration code, not a backend or mandatory runtime library.

Backdrop clicks intentionally do not dismiss. Escape/Cancel/Close reset this disposable local example, as its description states. A production editor must explicitly handle dirty data and cancellation before discarding work; do not copy the demo's reset policy into a real editor without that decision. The local Check example validates required fields and sends nothing. Use example only updates local status and closes.

## Choice cards

`.kb-choice` is a label wrapping a real radio in a named group/fieldset, not a click-enabled static card. Device-choice border/padding follow the source geometry: 2px, 14px radius, 16px padding. Add `kb-choice--template` for the source template variant: 1px border, same radius/padding. These use dedicated source-derived choice tokens, not the stronger field borders. Rest, hover, selected borders and template selection rings are distinct; keyboard focus remains an independent visible outline. The native radio communicates selection, and a wrapper outline communicates keyboard focus. Selection has no raised shadow. This is an explicit semantic/contrast refinement over source buttons without a radio-group contract.

## Verified evidence and remaining limits

[Geometry/behavior](reference/surfaces-verification.json) · [modal/font/scroll checks](reference/surfaces-modal-checks.json) · [sampled text contrast](reference/surfaces-contrast.json). Comparisons: [dense dark](reference/surfaces-comparison-dark-dense.png), [dense light](reference/surfaces-comparison-light-dense.png), [glass dark](reference/surfaces-comparison-dark-glass.png), [glass light](reference/surfaces-comparison-light-glass.png).

Both themes at 1200/768/390px: main-card widths match 1088/720/366px, responsive radius/padding and dialog widths/scrim/blur match measured source families. Native modal state, initial focus, Tab containment, Escape, focus restoration, local radio/form/reset behavior and body scrolling passed. Supplementary checks cover stable onscreen header/footer positions, explicit scrim policy, actual Noto Sans glyph rendering, dialog accessible name, 320px reflow, forced-color fallback, background lock restoration and scoped backdrop-token overrides. No write requests or page exceptions occurred in the main browser check.

Sampled title/value/description/selected-choice contrast exceeds 4.5:1 at checked points; minimum is 4.69:1 on the mobile dark inset description. Samples use actual screenshot backgrounds and resolved foregrounds, not flat swatch assumptions. This is not a minimum over every gradient pixel or a general WCAG certification. Recheck consumer backdrops, content, zoom, screen readers, supported browsers, dialog stacking and virtual-keyboard behavior before release. Populated source item-card states were visible in captures but not individually measured; they are not a separate verified standard.

M3 fields and M4 actions are reused; button elevation remains independent of dialog/card shadows. Reference repositories are unchanged, nothing is pushed, and M5 remains ready for visual review rather than complete.

### R023 native dialog compositing correction

The source nested overlay and native top-layer dialog do not composite translucent panels identically. A controlled Chromium comparison on the same solid underlay reproduced the brighter/bluer native panel. The native panel now paints its existing scrim token beneath its existing translucent fill, using two CSS gradient layers. The backdrop remains responsible for page dimming and blur. This preserves reusable alpha tokens instead of replacing them with a sampled solid color. Dense and glass families each consume their own scrim and fill; opaque light panels remain white.

[Controlled color test](reference/dialog-composite-verification.json): nested RGB (26,42,58), original native (26,59,89), corrected native (25,41,56), at the checked center pixel. Maximum corrected channel difference is 2. This is Chromium evidence, not a guarantee across rendering engines or every underlying page. Source and demo content differ. [Current dark glass comparison](reference/color-comparison-dark-glass.png) · [light glass](reference/color-comparison-light-glass.png) · [dark dense](reference/color-comparison-dark-dense.png) · [light dense](reference/color-comparison-light-dense.png).

[Current dialog regression](reference/surface-color-regression.json) and [updated sampled contrast](reference/surface-color-contrast.json) pass in both themes. Native modality, geometry, focus/Tab/Escape/return, local forms, body scrolling and 320px reflow remain verified. All 44 sampled text cases exceed 4.5:1; minimum remains 4.69:1. M5 remains review ready pending review of the corrected visuals.

## R022 border correction

The earlier checkpoint accidentally reused field-border (dark white/65) for both choice families and flattened template cards to the device choice's 2px contour. Corrected defaults: device gray-200 light / white-20 dark at 2px; template gray-200 light / white-10 dark at 1px. The source-specific hover and selected border/ring colors are now separate tokens. Dialog dividers use their actual source roles rather than the outer-shell border. Main card/panel/shell geometry already matched and is unchanged.

[Rendered border checks](reference/surface-border-verification.json) · [Dialog regression checks](reference/surface-border-regression.json). Native radio and keyboard focus semantics remain. The accepted M3 field and M4 action contrast borders are unchanged. Resting source borders are subtle; this correction is fidelity evidence, not a claim that every contour independently meets 3:1. The brighter/bluer glass composite was subsequently corrected in R023, above.
