···4949work.
50505151See [`TESTING.md`](./TESTING.md#visual-regression-tests) for how to write a visual test.
5252+5353+## Test every theme and mode
5454+5555+Migrated components use the shared appearance matrix for Machined edge and ELMO in explicit light
5656+and dark modes. Pass each appearance to `renderVisual`, then capture it with
5757+`captureVisualAppearance`:
5858+5959+```tsx
6060+import { test } from 'vite-plus/test';
6161+import {
6262+ captureVisualAppearance,
6363+ renderVisual,
6464+ visualAppearances,
6565+} from '../test-utils/render-visual.js';
6666+6767+test.each(visualAppearances)('theme matrix: $theme $mode', async (appearance) => {
6868+ const scene = renderVisual(<Button>Continue</Button>, appearance);
6969+7070+ await captureVisualAppearance(scene, 'button/theme-matrix', appearance);
7171+});
7272+```
7373+7474+The helper appends the selected appearance to the literal base ID. The example creates these stable
7575+capture IDs:
7676+7777+- `button/theme-matrix-machined-edge-light`
7878+- `button/theme-matrix-machined-edge-dark`
7979+- `button/theme-matrix-elmo-light`
8080+- `button/theme-matrix-elmo-dark`
8181+8282+Use one literal base ID for the matrix. The visual runner expands it during duplicate-ID validation,
8383+so each look remains independently reviewable without repeating theme setup. Existing tests that
8484+call `renderVisual(node)` continue to render in Machined edge light.
8585+8686+Theme identity and colour mode stay separate. To cover nested mode, put `data-color-mode="dark"` or
8787+`data-color-mode="light"` on a descendant inside the rendered scene. Do not add a nested theme
8888+identity because identity classes are not nestable.
8989+9090+For a portalled surface, render the real component with the selected appearance, open it through
9191+`userEvent`, and capture the portal or `document.body`. The component carries the identity class and
9292+explicit colour mode from its trigger. Do not copy theme classes onto a test-only portal wrapper.