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