react-devtool
v0.4.1
Published
Easy solution to have your own internal devtool, unleash the DX of React
Maintainers
Readme
React Devtool
A lightweight embeddable devtool for React apps. Drop a <Devtool> component into your app to get an in-page toolbar, component render inspection, performance signals, and a small set of dark developer UI primitives you can compose into your own internal tooling.
Try the hosted playground at react-devtool.com.
What it gives you
- Embeddable devtool shell - mount one React component and render custom tools inside the devtool panel.
- Component inspection - inspect rendered React components and their props/state-like data from the in-page toolbar.
- Render/performance monitoring - highlight component activity and inspect render-related performance information while developing.
- Data inspection - render object trees with the bundled
Inspectorcomponent. - Feature flag helpers - create subscribable flag containers and read them with
useFlag. - Developer UI kit - use small primitives such as buttons, inputs, toggles, groups, dividers, sections, tabs, radio groups, and code blocks.
Installation
npm install react-devtool
# or
pnpm add react-devtool
# or
yarn add react-devtool
# or
bun add react-devtoolReact Devtool is published as an ES module package and expects React and React DOM from your application.
Quick start
Render Devtool once near the root of your app. The component initializes the devtool and renders nothing directly into your app tree.
import { Devtool } from "react-devtool";
export function App() {
return (
<>
<YourApp />
<Devtool>
<div>Internal tools go here</div>
</Devtool>
</>
);
}Add custom tools
Import UI primitives from react-devtool/ui and pass them as children to Devtool.
import { Devtool } from "react-devtool";
import { Button, CodeBlock, Divider, Group, Input, Tab, Tabs } from "react-devtool/ui";
export function App() {
return (
<>
<YourApp />
<Devtool>
<Group title="Debug actions" description="Controls for the current debug session.">
<Input label="User id" placeholder="user_123" />
<Divider />
<Button onClick={() => console.log("refresh cache")}>Refresh cache</Button>
</Group>
<Tabs defaultValue="request">
<Tab label="Request" value="request">
<CodeBlock language="json">{JSON.stringify({ page: "home" }, null, 2)}</CodeBlock>
</Tab>
<Tab label="Response" value="response">
<CodeBlock language="json">{JSON.stringify({ ok: true }, null, 2)}</CodeBlock>
</Tab>
</Tabs>
</Devtool>
</>
);
}Feature flags
Use flags to create a subscribable flag object. Read individual values with useFlag, then render the flag object in the devtool panel with FeatureFlags.
import { Devtool, flags, useFlag } from "react-devtool";
import { FeatureFlags } from "react-devtool/ui";
const featureFlags = flags({
newCheckout: false,
verboseLogs: true,
});
function Checkout() {
const newCheckout = useFlag(featureFlags, "newCheckout");
return newCheckout ? <NewCheckout /> : <LegacyCheckout />;
}
export function App() {
return (
<>
<Checkout />
<Devtool>
<FeatureFlags name="Features" values={featureFlags} />
</Devtool>
</>
);
}Flags are in-memory by default. Pass persist as the second argument to opt into localStorage-backed flags that survive reloads:
const featureFlags = flags(
{
newCheckout: false,
verboseLogs: true,
},
{
persist: "checkout",
},
);The shorthand stores values under react-devtool:flags:checkout, merges persisted values with current defaults, ignores deleted/stale flag keys, and keeps the existing useFlag and FeatureFlags APIs unchanged.
For more control, pass a persistence object:
const featureFlags = flags(
{
newCheckout: false,
verboseLogs: true,
},
{
persist: {
key: "checkout",
version: 1,
syncTabs: true,
debounceMs: 100,
},
},
);
featureFlags.reset();
featureFlags.clearPersisted();
featureFlags.hydrate();Agent-friendly feature flag development
Feature flags are a practical guardrail for agent-built changes. Keep PRs and CI for code quality, then use flags to control runtime exposure while the new path is still being tested.
Recommended loop:
- Ask the agent to build the new behavior behind a default-off flag.
- Merge the change only after review, type checks, and tests pass.
- Use React Devtool to toggle the flag locally, in QA, or during demos.
- Expose the flag to a small group first, then widen the rollout as bugs are fixed.
- Delete the flag and the old path once the feature is fully shipped.
import { Devtool, flags, useFlag } from "react-devtool";
import { FeatureFlags } from "react-devtool/ui";
const rolloutFlags = flags(
{ agentSearch: false },
{ persist: "agent-rollouts" },
);
function SearchPage() {
const agentSearch = useFlag(rolloutFlags, "agentSearch");
return agentSearch ? <AgentSearch /> : <ClassicSearch />;
}
export function App() {
return (
<>
<SearchPage />
<Devtool>
<FeatureFlags name="Agent rollouts" values={rolloutFlags} />
</Devtool>
</>
);
}Flag hygiene matters: keep defaults safe, test both paths, avoid nesting too many flags, and remove temporary flags after rollout so agent work does not leave permanent branches in the codebase.
Inspect data
Use values for subscribable data and Inspector for object-tree rendering.
import { Devtool, values } from "react-devtool";
import { Inspector } from "react-devtool/ui";
const session = values({
user: { id: "u_123", role: "admin" },
settings: { theme: "dark" },
});
export function App() {
return (
<>
<YourApp />
<Devtool>
<Inspector data={session.value} expandLevel={2} />
</Devtool>
</>
);
}API
react-devtool
| Export | Description |
| --- | --- |
| Devtool | Initializes the in-page devtool and syncs its children into the devtool UI. |
| values(initialValue) | Creates a subscribable signal-like value container. |
| flags(initialFlags, options?) | Creates a subscribable signal-like flag container, with optional persistence. |
| useFlag(flags, key) | React hook that subscribes to one flag value. |
react-devtool/ui
| Export | Description |
| --- | --- |
| FeatureFlags | Displays a subscribable boolean map in the devtool UI. |
| Inspector | Object and DOM-like value inspector powered by react-inspector. |
| Button | Button primitive with default, outline, ghost, and destructive variants. |
| Toggle | Accessible switch primitive. |
| Input | Text input with optional label, help text, and error text. |
| Select | Select input with optional label, placeholder, help text, and error text. |
| Radio, RadioGroup | Radio input primitives. |
| ButtonGroup | Horizontal or vertical button grouping. |
| Divider | Horizontal or vertical separator with an optional label. |
| Group | Card-like feature section for grouping related devtool controls. |
| Section | Titled section with optional collapse behavior. |
| Tabs, Tab | Controlled or uncontrolled tabs. |
| CodeBlock | Small code block display component. |
Local development
This repository uses Bun for the lockfile, but the npm scripts are standard package scripts.
bun install
bun run dev # start the Vite playground on http://localhost:1234
bun run build # build the library into dist/
bun run build:playground # build the hosted playground into playground-dist/
bun run typecheck # run TypeScript project checks
bun run knip # check for unused files/dependenciesThere is no package-level test script yet. Unit tests live under tests/unit, component tests live under tests/ct, and the repository includes Vitest and Playwright component-test configuration.
Package exports
import { Devtool, flags, useFlag, values } from "react-devtool";
import { Button, FeatureFlags, Group, Inspector, Tabs } from "react-devtool/ui";The package builds src/devtool.tsx and src/ui.tsx as library entry points.
Acknowledgments
React Devtool builds on ideas and implementation patterns from earlier React render debugging tools. Thanks to the open-source React debugging community for their work.
Contributing
Contributions are welcome. A typical local loop is:
- Create a feature branch.
- Make the smallest focused change.
- Run
bun run typecheckandbun run build. - Open a pull request with a short description and verification notes.
License
Apache-2.0 © React Devtool
