Star on GitHub
npm install react-driftkit
Active
off · ⌘⇧C toggles
Box model
on
Colors + contrast
Spacing
A11y (role + name + state)
Turn on the picker, 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 picker is on.
Prop Type Default
active
Controlled active state. Omit for uncontrolled.
boolean
defaultActive
Uncontrolled initial active state.
boolean false
on
Event handlers: activeChange, select, hover. All optional.
InspectorBubbleEvents
on.activeChange
Fires whenever active toggles — click-select, Escape, or the hotkey.
(active: boolean) => void
on.select
Fires on click with the selected element and its info snapshot.
(el: Element, info: ElementInfo) => void
on.hover
Fires whenever the hovered element changes while active.
(el: Element | null, info: ElementInfo | null) => void
behavior
Behavior knobs: hotkey, ignoreSelector, exitOnSelect, exitOnEscape.
InspectorBubbleBehavior
behavior.hotkey
Keyboard shortcut to toggle active, e.g. "cmd+shift+c". Supports cmd/meta, ctrl, shift, alt/option + key.
string
behavior.ignoreSelector
CSS selector for elements the picker skips. Elements with [data-inspector-bubble-ignore] are always skipped.
string
behavior.exitOnSelect
Deactivate after a successful click-select.
boolean true
behavior.exitOnEscape
Deactivate when Escape is pressed.
boolean true
highlight
Highlight layer: boxModel, outline, colors.
InspectorBubbleHighlight
highlight.boxModel
Render the 4-layer DevTools box model (margin / border / padding / content).
boolean true
highlight.outline
Render a single outline around the element instead of the box model.
boolean !boxModel
highlight.colors
Override overlay colors: margin, border, padding, content, outline.
InspectorBubbleColors DevTools-like defaults
bubble
Info bubble: enabled, fields, render.
InspectorBubbleBubble
bubble.enabled
Render the info bubble. Set false to show only the highlight.
boolean true
bubble.render
Full escape hatch — replaces the default bubble content.
(info: ElementInfo) => ReactNode
bubble.fields
Per-field toggles for the default bubble.
InspectorBubbleFields all true
bubble.fields.tag
Lowercase tag name.
boolean true
bubble.fields.selector
Short CSS selector (#id, [data-testid], or tag.class1.class2).
boolean true
bubble.fields.dimensions
Rendered width × height.
boolean true
bubble.fields.font
Font size, rendered family (first loaded font from the declared list), and weight.
boolean true
bubble.fields.colors
Foreground + effective background swatches and WCAG contrast ratio.
boolean true
bubble.fields.spacing
Padding and margin values (T R B L).
boolean true
bubble.fields.role
ARIA role (explicit or implicit from the tag).
boolean true
bubble.fields.accessibleName
Computed accessible name (aria-labelledby → aria-label → alt → <label> → text content).
boolean true
bubble.fields.a11yState
tabindex, focusable, disabled, hidden, expanded/pressed/checked/selected when set.
boolean true
zIndex
z-index for the overlay and bubble.
number 2147483647
style
Inline styles merged with the default bubble wrapper.
CSSProperties {}
className
CSS class added to the default bubble wrapper.
string ''

Overlay elements carry data-inspector-bubble-ignore, so the picker never highlights itself. Add this attribute to your own UI (toolbar buttons, the toggle that controls the picker, etc.) to exempt it from selection.

tsx
import { useState } from 'react';
import { InspectorBubble } from 'react-driftkit';

function App() {
  const [active, setActive] = useState(false);

  return (
    <>
      <button
        data-inspector-bubble-ignore
        onClick={() => setActive(a => !a)}
      >
        {active ? 'Stop inspecting' : 'Inspect element'}
      </button>

      <InspectorBubble
        active={active}
        behavior={{ hotkey: 'cmd+shift+c' }}
        on={{
          activeChange: setActive,
          select: (el, info) => console.log('selected', el, info),
        }}
      />
    </>
  );
}
typescript
interface ElementInfo {
  element: Element;
  tag: string;
  selector: string;
  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;
  backgroundColor: string;
  contrastRatio: number | null;
  padding: { top: number; right: number; bottom: number; left: number };
  margin:  { top: number; right: number; bottom: number; left: number };
  border:  { top: number; right: number; bottom: number; left: number };
  a11y: {
    role: string | null;
    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;
  };
}

interface InspectorBubbleProps {
  active?: boolean;
  defaultActive?: boolean;

  on?: {
    activeChange?: (active: boolean) => void;
    select?: (element: Element, info: ElementInfo) => void;
    hover?: (element: Element | null, info: ElementInfo | null) => void;
  };

  behavior?: {
    hotkey?: string;
    ignoreSelector?: string;
    exitOnSelect?: boolean;   // default true
    exitOnEscape?: boolean;   // default true
  };

  highlight?: {
    boxModel?: boolean;       // default true — 4-layer DevTools model
    outline?: boolean;        // defaults to !boxModel
    colors?: {
      margin?: string;
      border?: string;
      padding?: string;
      content?: string;
      outline?: string;
    };
  };

  bubble?: {
    enabled?: boolean;
    fields?: {
      tag?: boolean;
      selector?: boolean;
      dimensions?: boolean;
      font?: boolean;
      colors?: boolean;
      spacing?: boolean;
      role?: boolean;
      accessibleName?: boolean;
      a11yState?: boolean;
    };
    render?: (info: ElementInfo) => ReactNode;
  };

  zIndex?: number;
  className?: string;
  style?: CSSProperties;
}

Enjoying react-driftkit?

Star the repo on GitHub to help more devs discover it.

Star on GitHub