npm install layout-inspectorimport { LayoutInspector } from 'layout-inspector';| Prop | Type | Default |
|---|---|---|
active Controlled active state. Omit for uncontrolled. | boolean | — |
defaultActive Uncontrolled initial active state. | boolean | false |
on | LayoutInspectorEvents | — |
behavior Hotkey, ignore selector, and exit-on-select / exit-on-escape toggles. See Types. | LayoutInspectorBehavior | — |
highlight Box-model layers vs. single outline, plus per-layer color overrides. See Types. | LayoutInspectorHighlight | — |
bubble Info-bubble visibility, per-field toggles, and a full render escape hatch. See Types. | LayoutInspectorBubble | — |
zIndex z-index for the overlay and bubble. | number | 2147483647 |
className CSS class added to the default bubble wrapper. | string | '' |
style Inline styles merged with the default bubble wrapper. | CSSProperties | {} |
Overlay elements carry data-layout-inspector-ignore, so the inspector never highlights itself. Add this attribute to your own UI (toolbar buttons, the toggle that controls the inspector, etc.) to exempt it from selection.
import { useState } from 'react';
import { LayoutInspector } from 'layout-inspector';
function App() {
const [active, setActive] = useState(false);
return (
<>
<button
data-layout-inspector-ignore
onClick={() => setActive((a) => !a)}
>
{active ? 'Stop inspecting' : 'Inspect element'}
</button>
<LayoutInspector
active={active}
behavior={{ hotkey: 'cmd+shift+c' }}
on={{
activeChange: setActive,
select: (el, info) => console.log('selected', el, info),
}}
/>
</>
);
}interface LayoutInspectorProps {
active?: boolean; // controlled
defaultActive?: boolean; // uncontrolled (default false)
on?: LayoutInspectorEvents;
behavior?: LayoutInspectorBehavior;
highlight?: LayoutInspectorHighlight;
bubble?: LayoutInspectorBubble;
zIndex?: number; // default 2147483647
className?: string;
style?: CSSProperties;
}
interface LayoutInspectorEvents {
activeChange?: (active: boolean) => void;
select?: (element: Element, info: ElementInfo) => void;
hover?: (element: Element | null, info: ElementInfo | null) => void;
}
interface LayoutInspectorBehavior {
hotkey?: string; // e.g. 'cmd+shift+c'
ignoreSelector?: string; // CSS selector to skip
exitOnSelect?: boolean; // default true
exitOnEscape?: boolean; // default true
}
interface LayoutInspectorHighlight {
boxModel?: boolean; // default true — 4-layer DevTools model
outline?: boolean; // defaults to !boxModel
colors?: LayoutInspectorColors;
}
interface LayoutInspectorColors {
margin?: string;
border?: string;
padding?: string;
content?: string;
outline?: string;
}
interface LayoutInspectorBubble {
enabled?: boolean; // default true
fields?: LayoutInspectorFields;
render?: (info: ElementInfo) => ReactNode; // escape hatch
}
interface LayoutInspectorFields {
tag?: boolean;
selector?: boolean;
dimensions?: boolean;
font?: boolean;
colors?: boolean;
spacing?: boolean;
role?: boolean;
accessibleName?: boolean;
a11yState?: boolean;
}
interface ElementInfo {
element: Element;
tag: string; // 'button'
selector: string; // 'button.btn[data-testid="x"]'
rect: DOMRect;
font: {
family: string; // declared font-family list
rendered: string; // first family the browser actually has loaded
size: string;
weight: string;
lineHeight: string;
};
color: string; // computed text color
backgroundColor: string; // walks ancestors for first opaque bg
contrastRatio: number | null;
padding: BoxEdges;
margin: BoxEdges;
border: BoxEdges;
a11y: A11yInfo;
}
interface BoxEdges {
top: number;
right: number;
bottom: number;
left: number;
}
interface A11yInfo {
role: string | null; // explicit attr or implicit from tag
explicitRole: boolean;
accessibleName: string | null;
ariaLabel: string | null;
ariaLabelledBy: string | null;
ariaDescribedBy: string | null;
tabIndex: number | null;
focusable: boolean;
disabled: boolean;
hidden: boolean;
expanded: boolean | null;
pressed: boolean | 'mixed' | null;
checked: boolean | 'mixed' | null;
selected: boolean | null;
}What is layout-inspector?
layout-inspector is a small React component that drops a Chrome-DevTools-style element picker into your own app. Hover any element to see its tag, selector, dimensions, font, foreground/background colors with WCAG contrast, ARIA role, accessible name, and a11y state. Click to select. Useful for design QA, a11y audits, and pair-debugging without leaving the page.
Does layout-inspector support React 19?
Yes. layout-inspector declares peer dependencies of react@^18 || ^19 and react-dom@^18 || ^19, so it works with React 18 and React 19 without changes.
How is this different from Chrome DevTools?
It runs in your app, not in the browser panel — so designers, PMs, and QA can use it without opening DevTools. The bubble surfaces high-signal info in one glance (size, font, contrast, role, accessible name) and you can wire on.select to build your own design-token / a11y workflows on top.
How do I install layout-inspector?
Install with npm install layout-inspector, yarn add layout-inspector, or pnpm add layout-inspector. It has zero runtime dependencies and ships at ~6 kB gzipped.
Does it interfere with my own UI?
No. The overlay carries data-layout-inspector-ignore so it never picks itself. Add the same attribute to your own toolbar / toggle button to exempt it from selection, or pass behavior.ignoreSelector with a CSS selector for everything you want skipped.
Enjoying layout-inspector?
Star the repo on GitHub to help more devs discover it.