Getting started
Add one plugin to your Vitest config, run your tests and open the viewer. This page does it for browser mode and for jsdom.
Requirements #
- Node
-
What your Vite and Vitest need. Vite 8 needs
^20.19.0 || >=22.12.0. Thedescribe-meCLI needs^20.16.0 || >=22.4.0. - Vitest
- 4 or 5 (
^4.0.0 || ^5.0.0) - Vite
- 6.4 and later, 7 or 8 (
^6.4.0 || ^7.0.0 || ^8.0.0) - React
- 18 or 19
- Test library
-
vitest-browser-react2 for browser mode,@testing-library/react16 for jsdom
TypeScript, for props docs, and @testing-library/user-event are optional.
Supported versions lists every range.
Install #
pnpm add -D describe-me @describe-me/vitest @describe-me/react
With npm or yarn:
npm install -D describe-me @describe-me/vitest @describe-me/react
yarn add -D describe-me @describe-me/vitest @describe-me/react vite
Yarn needs vite in the list because it is a peer of describe-me
and yarn does not install peers. Keep all describe-me packages on one version, as
One version for all packages explains.
Package managers has the details for
each.
Browser mode #
-
Add the browser packages Skip the ones your project already has, then
install Chromium.
pnpm add -D vitest-browser-react @vitest/browser-playwright playwright pnpm exec playwright install chromium -
Add the plugin Put
describeMe()into the plugins of your Vitest config.vitest.config.ts
import { defineConfig } from 'vitest/config' import react from '@vitejs/plugin-react' import { playwright } from '@vitest/browser-playwright' import { describeMe } from '@describe-me/vitest/plugin' // describe-me export default defineConfig({ plugins: [react(), describeMe()], // describe-me test: { browser: { enabled: true, provider: playwright(), instances: [{ browser: 'chromium' }], }, }, }) -
Run the tests They write the frames to
.describe-me/.
What to commit says what to keep out of git.pnpm vitest run -
Open the viewer Start it, then open the URL it prints.
By default that ispnpm describe-me devhttp://localhost:6006. When the port is taken, the viewer takes the next free one.
Your existing tests need no change. The plugin redirects render from
vitest-browser-react to a recording one, as
What the plugin changes describes.
jsdom #
-
Add the jsdom packages Skip the ones your project already has.
@testing-library/domis a peer of@testing-library/react.pnpm add -D @testing-library/react @testing-library/dom jsdom pnpm add -D @testing-library/user-event # optional -
Add the plugin Put
describeMe()into the plugins of your Vitest config.vitest.config.ts
import { defineConfig } from 'vitest/config' import react from '@vitejs/plugin-react' import { describeMe } from '@describe-me/vitest/plugin' // describe-me export default defineConfig({ plugins: [react(), describeMe()], // describe-me test: { environment: 'jsdom' }, }) -
Run the tests They write the frames to
.describe-me/.pnpm vitest run -
Open the viewer Start it, then open the URL it prints.
pnpm describe-me dev
The plugin redirects render from @testing-library/react and
turns on test.css unless you set it, so imported stylesheets reach the
snapshots. See jsdom for the details and
Which one to use to compare the two. What jsdom
cannot do inside a test is in
Limitations in jsdom.
Your first story #
Any test that renders a component is already a story. step() names a part of
it:
src/Counter.test.tsx
import { describe, expect, it } from 'vitest'
import { render } from 'vitest-browser-react'
import { step } from '@describe-me/vitest'
import { Counter } from './Counter'
describe('Counter', () => {
it('does not go below the minimum', async () => {
const screen = await render(<Counter initial={1} />)
await step('go down to the minimum', async () => {
await screen.getByRole('button', { name: 'decrement' }).click()
})
await expect.element(screen.getByLabelText('value')).toHaveTextContent('0')
})
})
In the viewer, Counter heads its file's block in the sidebar and the test is
a story in it. Its timeline starts with the render frame. The click records a frame, and
the step gives it its name, because its body produced that DOM. A closing frame follows
only when the DOM changed after the last frame.
Open the step frame in the browser example.
In jsdom the same test imports render from
@testing-library/react, as
the Disclosure test in the jsdom example
does. See What records a frame and
Naming steps with step().
Open the viewer #
Run describe-me dev next to Vitest in watch mode. Use two terminals, or one
script like the examples:
package.json
"dev": "vitest & describe-me dev --data .describe-me; wait"
The viewer updates whenever the reporter rewrites the manifest. --data points
it at another output directory.
The sidebar lists files, describe blocks and tests. The middle shows the
stage and the frame timeline. The inspector shows the test status, the component, its
props and the current frame. Finding your way walks
through it, and The CLI lists the commands.
describe-me dev also starts the live preview from your Vitest config. Once it
is ready, a test opens in Live, mounted from a fresh start.
The Live toggle or L switches to its recorded frames.
--no-live starts the viewer without
it.
See your design system #
When the run found tokens, the viewer has a Foundations entry above the test files. It
shows them: custom properties on :root and other root-like selectors in the
CSS the tests load, and the Tailwind 4 theme. See the
colors of the shadcn/ui example.
A JS theme, such as a Material UI or styled-components theme, needs the
foundations.theme option. It names the module that exports the theme:
vitest.config.ts
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import { describeMe } from '@describe-me/vitest/plugin'
export default defineConfig({
plugins: [react(), describeMe({ foundations: { theme: 'src/theme.ts' } })],
})
Where the tokens come from lists every source, and
foundations describes the
option.
Publish the docs #
pnpm describe-me build
It writes a static site, the viewer with your data copied in, to
describe-me-dist/. --out picks another directory. Serve it over
HTTP, because it does not open from disk.
describe-me build --live adds Live to the
site, which then holds the compiled code of your components and tests.
Build the docs in CI shows the CI steps. Hosting the static site covers where to put it, and Fonts in the static site what the build downloads.
Next steps #
Writing stories
Frames, step(), naming and the component overview.
Configuration
The plugin and its options.
Environments
Browser mode and jsdom, and how to choose.
Viewer and CLI
Everything the viewer shows, and the commands.
Foundations
The tokens of your design system, where they come from and the presets.
Troubleshooting
Diagnostics, warnings and common symptoms.