Star on GitHub

LayoutInspector

A Chrome-DevTools-style layout inspector for design QA. Hover any element to see its tag, selector, dimensions, typography, effective colors with WCAG contrast, and box model — plus a full a11y snapshot. Click to select, Escape or a hotkey to exit.

npm install layout-inspector
import { LayoutInspector } from 'layout-inspector';
Active
off · ⌘⇧C toggles
Box model
on
Colors + contrast
Spacing
A11y (role + name + state)
Turn on the inspector, then hover any of these elements — including this text. The overlay shows the box-model layers and the bubble reports the tag, selector, dimensions, font, color + background + WCAG contrast, and padding/margin.
card.accent
card.dark
Nothing selected yet. Click an element while the inspector is on.
Prop Type Default
active
Controlled active state. Omit for uncontrolled.
boolean —
defaultActive
Uncontrolled initial active state.
boolean false
on
Event callbacks: activeChange, select, hover. See Types for the full shape.
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.

tsx
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),
        }}
      />
    </>
  );
}
typescript
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.

Star on GitHub