A keyboard-triggered command palette for React apps — active during development and staging, a genuine no-op in production.
Press / anywhere in your app to open the palette. Type to filter commands. Press Enter to open a panel. Press Escape to close.
# From GitHub
yarn add git+https://github.com/<your-org>/dev-console.gitimport{DevConsoleProvider}from'dev-console'importtype{DevCommand}from'dev-console'constcommands: DevCommand[]=[{name: 'state',description: 'Inspect current app state',panel: StatePanel,},]exportfunctionApp(){return(<DevConsoleProvidercommands={commands}><YourApp/></DevConsoleProvider>)}These are always registered — no configuration needed:
| Command | Description |
|---|---|
/log | View entries written via useDevLog() |
/env | Inspect all import.meta.env variables |
import{useDevLog}from'dev-console'functionMyComponent(){const{ log }=useDevLog()useEffect(()=>{log('Component mounted',{id: 42})},[log])}log(message, data?) — data is optional and displayed as formatted JSON in the /log panel.
In dev mode, console.log, console.warn, and console.error are automatically captured and forwarded to the /log panel. Each entry shows a coloured level badge (log / warn / error). The original console methods still work normally — capture is purely additive.
By default panels close on Escape and backdrop click. Set persistent: true on a command to keep the panel open — the app remains fully interactive behind it, and a left/right dock toggle appears in the panel header so you can move it out of the way.
constcommands: DevCommand[]=[{name: 'state',description: 'App state',panel: StatePanel,persistent: true},]The built-in /log command is persistent by default. /env is not.
A command is a { name, description?, panel, persistent? } object where panel is any React component that takes no props:
importtype{DevCommand}from'dev-console'constcommands: DevCommand[]=[{name: 'state',description: 'App state',panel: StatePanel},{name: 'flags',description: 'Feature flags',panel: FlagsPanel},]Pass the array to <DevConsoleProvider commands={commands}>. Built-in commands always appear first. If you register a command with the same name as a built-in, your command is silently dropped.
All visual tokens are CSS custom properties. Override any of them on :root or an ancestor element:
:root {
--dc-bg:#ffffff;
--dc-border:#e0e0e0;
--dc-text:#111111;
--dc-text-muted:#666666;
--dc-accent:#0070f3;
--dc-overlay:rgba(0,0,0,0.3);
--dc-radius:8px;
--dc-font: system-ui, sans-serif;
}DevConsoleProvider is safe to import unconditionally. The library checks the environment internally:
- Active when
import.meta.env.DEV === true(Vite dev server) - Active when
import.meta.env.VITE_ENABLE_DEV_CONSOLE === 'true'(explicit opt-in) - Otherwise: renders only
{children}— no listeners, no overlay, no bundle weight
Staging setup — enable in a production build without touching source code:
VITE_ENABLE_DEV_CONSOLE=true vite buildOr set the variable in your CI/staging environment.
cd example
yarn install
yarn dev| Prop | Type | Description |
|---|---|---|
commands | DevCommand[] | Consumer-defined commands to register |
children | ReactNode | Your app |
typeDevCommand={name: string// shown in palette, used for filteringdescription?: string// shown as subtitle in palettepanel: React.ComponentType<object>// rendered when command is activated, must take no propspersistent?: boolean// if true, panel stays open; app stays interactive behind it}Returns { log: (message: string, data?: unknown) => void }. No-op in production.