Intro
What flawless-react is — point it at a component or a glob, pick which checks to run, and get back a runnable suite plus file-and-line findings
What it is
flawless-react is a multi-task quality kit for React components. You point it at a .tsx file or a glob, pick which of seven tasks to run, and it gives back a runnable Playwright + Vitest suite, an accessibility scan and a React correctness scan, with a file, line and column for every finding.
The distinction worth holding on to is that the output is not a report you then have to act on by hand. Four of the tasks write test code you run, commit and edit. The other three write findings, and those findings are pinned to exact source positions rather than described in prose. A small <Component>.flawless.json is dropped next to each source file so issues surface where the code lives.
Every file matched by the positional becomes an entry component in its own right, and each one runs against every selected task:
flawless-react src/Dashboard.tsx
flawless-react "src/**/*.tsx"
If the positional resolves to an existing path it is used directly; otherwise it is treated as a glob relative to the working directory.
Why it exists
Wiring up a Playwright + <Profiler> + react-scan harness for a non-trivial component takes a couple of hours of fiddly boilerplate: bundle the component for the browser, host it, expose a JS API to drive renders, write the test scaffolding, define budgets, hand-pick prop variations, polyfill Node globals for CJS dependencies, set up provider context, mock the backend. None of that work is interesting, and none of it is where the bugs are.
The tool itself is a thin scaffolder. The intelligence is the static analysis that decides what to import, what props to vary and which children to walk into. It reads the component’s type signature, discovers child components by walking the JSX — locally-imported ones only, to a bounded depth, cycle-guarded — and produces the scaffolding mechanically. You get a runnable suite in seconds, and from there you edit it like normal test code.
Nothing is measured by the CLI itself. Runtime numbers come from React’s built-in <Profiler> for per-component renderCount and renderTime, from react-scan for unnecessary rerenders, and from Chrome DevTools Protocol heap snapshots for retention. The CLI never imports React or Playwright and never boots a server; it only reads and writes files.
Two kinds of task
The seven task ids are perf-harness, unit-browser, perf-static, a11y, react-rules, e2e and load. They split into tasks that scaffold and tasks that scan.
Scaffolding tasks — they write code you own
perf-harness— a Playwright +<Profiler>+ react-scan performance suite: a host bundle that mounts your components, one spec file per analysed component, and abudgets.jsonof per-component mount and update ceilings.unit-browser— Vitest +@testing-library/reactunit tests, plus Playwright functional browser specs.
Both write into the output directory (./.flawless by default), each into its own slice of it. What lands there is ordinary test code: it does not import flawless-react, and it runs without the tool present.
Scanning tasks — they report and change nothing
perf-static— reads the parsed component tree and flags rerender risks: a component that renders three or more child components while using none ofuseMemo,useCallback,memooruseRef, and props the analyser could not synthesise a default for (the generated tests would passundefined).a11y— accessibility and web-standards rules over the whole targeted tree, run through biome.react-rules— a curated set of functionally important React and hooks rules, also through biome:useExhaustiveDependencies,useHookAtTopLevel,useJsxKeyInIterable,noArrayIndexKeyand friends. It runs against a config the tool generates, so the project’s ownbiome.jsonis bypassed and the result does not depend on how that project happens to be linted.e2e— one Playwright spec per app-router page, driving the target’s real dev server: the route loads with no console or page errors, plus up to three interactions taken from that page’s own component tree. Needs--nextjsor--expo(expo-router): the route list is derived from the router’s file layout, and nothing else has one.load— crust load pipes per route and per API operation. The first run writes baselines; later runs compare against them.
Each scanning task writes a JSON report per component under reports/ in the output directory. Findings also print to stdout grouped by file, and every run ends with a per-task summary table and a reports/summary.json.
These three read your source and never rewrite it. There is no fix mode, and nothing outside the output directory changes except the inline <Component>.flawless.json summaries, which you can turn off.
The suite belongs to you
Once the files are written, the tool’s job is done. Commit the suite, lint it, put it in CI, pin its dependencies — treat it as a fresh project. The generated budgets warn rather than fail on the first runs, because you do not yet know what “fast enough” means for your component; turning those warnings into assertions once you have a baseline is a one-line edit per spec.
The one thing to avoid is regenerating over a suite you have started shaping. The tool is the source of initial truth and you are the source of evolved truth, so hand-added variants, custom interactions and replaced fixtures will not survive a forced re-run.
The interactive menu
Run the binary with no positional argument in a terminal and it prompts instead of failing:
flawless-react
Three questions, in order:
- Component path or glob. Pre-filled with a detected default —
src/App.tsx,src/index.tsxorsrc/main.tsxif one of them exists, otherwise the single.tsxfile insrc/, orsrc/*.tsxwhen there are several. - Output directory. Defaults to
./.flawless. - Which tasks to run. A checkbox list of all seven: space toggles, enter confirms.
perf-harnessandperf-staticare selected by default.
If a scaffolding task ran and nothing failed, it then offers to run the tests it produced. Cancelling at any prompt exits without writing anything.
The menu only appears when stdin is a TTY. In a script or CI, or with the flag that disables prompting, pass the positional and the task list explicitly instead — see the CLI reference for the flags.
Installing
Two channels, and unlike most tools they genuinely ship different things. Pick by whether the target machine has Bun.
Standalone binary — nothing to install alongside it:
curl -fsSL https://raw.githubusercontent.com/lariocpt/flawless-react/main/install.sh | bash perf-harness, unit-browser, perf-static, e2e and load work anywhere with this. The binary is self-contained Bun, but it cannot carry biome inside itself, so a11y and react-rules shell out to it: they look for biome in the project being scanned (node_modules/.bin/biome), every ancestor workspace root, then on PATH — and it has to be biome 2, because the sweeps write a biome 2 linter config and parse its JSON reporter. A candidate of another major is walked past in favour of one that can read it; if none can, the task fails naming the version and path it refused, since a foreign reporter cannot produce findings those tasks read. Any React codebase that already lints with biome 2 satisfies that for free.
Source package — needs Bun 1.1 or newer at runtime, and every task works with no further setup because @biomejs/biome comes along as a real dependency:
bun add -g @lariocpt/flawless-react The install page covers both channels in full.
Where to go next
- Quickstart — generate a suite and run it.
- CLI reference — every flag, with defaults.
- What gets generated — the emitted suite, file by file.
- Architecture — how the analyse and scaffold phases fit together, and what is intentionally absent.