Lil' Debugger

A tiny dev tool · on npm

Pin debug data to any element

Add a data-debug attribute to any element. Hold the keys and the page shows you its value. It works with any framework, or no framework. No dependencies.

Hold them now. This whole page is the demo. No keyboard? Tap Turn on at the top.

Try it

Turn it on, then hover the cards. The panel at the bottom left shows the label of the element, then the labels of its parents. Alt+click any of them to copy its value.

AL

Ada Lovelace

Analytical Engine team

Basket

  • Punched cards£13.50
  • Brass gear set£120.00

£133.50

Newsletter

Beta Alt+click the badge to copy its flag.

Install

npm install -D @mrmartineau/lil-debugger

Turn it on

Call lilDebugger() once, in the browser. It adds its own styles.

import { lilDebugger } from "@mrmartineau/lil-debugger";

if (import.meta.env.DEV) lilDebugger();

Load it only in development. This example uses the Vite import.meta.env.DEV flag. Change it to the flag of your build tool.

Add debug info

Put any string in a data-debug attribute:

<div data-debug="user:42">…</div>
<section data-debug='{"plan":"pro","flags":["beta"]}'>…</section>

From a component, pass any value as a string:

<Card data-debug={JSON.stringify({ id, status })} />

Controls

Do thisWhat happens
Hold Ctrl+ShiftShow the debug info. Let go to hide it.
Ctrl+Shift+LKeep it on. Press again to turn it off.
EscTurn it off, even when it is locked on. To show it again, let go of Ctrl+Shift and hold them again.
Hover a debug elementThe panel shows its label and the labels of its parents.
Alt+clickCopy the value of the element to the clipboard.
Add ?lil-debug to the URLStart with it locked on. Good for sharing a link.

What the panel shows

  • The value. JSON objects and arrays show on many lines.
  • The element: tag, id, classes and size, for example <button#save.btn> 120×40.
  • When you hover nothing: how many debug elements are on the page.
  • All debug elements get a dashed outline. The innermost hovered one gets a solid outline.
  • It turns off if the window loses focus while you hold the keys, so it never gets stuck on.
  • The panel uses textContent, so a value can never inject HTML.

Options

const debug = lilDebugger({
  attribute: "data-debug", // the attribute to read
  lockKey: "KeyL", // a KeyboardEvent.code value for Ctrl+Shift+<key>
  injectStyles: true, // set to false to bring your own CSS
});

debug.toggle(); // lock or unlock from code
debug.destroy(); // remove all listeners, the panel, the styles and the root class

Pick a lockKey that your browser does not already use with Ctrl+Shift.

Theming

Set four custom properties in your own CSS. Pick one here, then turn the debugger on to see it.

:root {
  --lil-debugger-accent: #7c3aed;
  --lil-debugger-tint: rgb(124 58 237 / 0.12);
  --lil-debugger-panel-bg: #1e1b2e;
  --lil-debugger-panel-fg: #fff;
}

Bring your own CSS

Turn off the default styles:

lilDebugger({ injectStyles: false });

Then copy this CSS into your own stylesheet and change it. The tool adds the lil-debugger class to <html> when it is on, and the panel has the lil-debugger-panel class. If you use a different attribute, change [data-debug] to match.

:root {
  --lil-debugger-accent: #7c3aed;
  --lil-debugger-tint: rgb(124 58 237 / 0.12);
  --lil-debugger-panel-bg: #1e1b2e;
  --lil-debugger-panel-fg: #fff;
}
.lil-debugger [data-debug] {
  outline: 1px dashed var(--lil-debugger-accent) !important;
  outline-offset: -1px;
  background-color: var(--lil-debugger-tint);
}
.lil-debugger [data-debug]:hover:not(:has([data-debug]:hover)) {
  outline-style: solid !important;
  outline-width: 2px !important;
}
.lil-debugger-panel {
  position: fixed;
  bottom: 1rem;
  left: 1rem;
  z-index: 2147483647;
  max-width: min(60ch, calc(100vw - 2rem));
  max-height: 50vh;
  overflow: hidden;
  padding: 0.5rem 0.75rem;
  border-radius: 0.75rem;
  background: var(--lil-debugger-panel-bg);
  color: var(--lil-debugger-panel-fg);
  font: 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
  text-align: left;
  pointer-events: none;
}
.lil-debugger-panel[hidden] { display: none; }
.lil-debugger-panel pre {
  margin: 0;
  font: inherit;
  font-weight: bold;
  white-space: pre-wrap;
}
.lil-debugger-panel small { opacity: 0.7; }
.lil-debugger-panel > div + div {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid rgb(255 255 255 / 0.2);
}
.lil-debugger-panel[data-clipped]::after {
  content: "… Alt+click to copy the full value";
  position: sticky;
  bottom: 0;
  display: block;
  background: var(--lil-debugger-panel-bg);
  color: var(--lil-debugger-accent);
}
.lil-debugger-panel[data-copied]::before {
  content: "Copied ✓";
  display: block;
  color: var(--lil-debugger-accent);
}

Frameworks

lilDebugger() returns a destroy() function. Each framework calls it when the component unmounts.

Plain HTML
<!-- No build step: load it from a CDN, and only on the pages you debug -->
<script type="module">
  import { lilDebugger } from "https://esm.sh/@mrmartineau/lil-debugger@1";

  lilDebugger();
</script>
Astro
<script>
  import { lilDebugger } from "@mrmartineau/lil-debugger";

  if (import.meta.env.DEV) lilDebugger();
</script>
React
import { useEffect } from "react";
import { lilDebugger } from "@mrmartineau/lil-debugger";

export function LilDebugger() {
  useEffect(() => lilDebugger().destroy, []);
  return null;
}
Vue
<script setup>
import { onMounted, onUnmounted } from "vue";
import { lilDebugger } from "@mrmartineau/lil-debugger";

let debug;
onMounted(() => (debug = lilDebugger()));
onUnmounted(() => debug?.destroy());
</script>
Svelte
<script>
  import { onMount } from "svelte";
  import { lilDebugger } from "@mrmartineau/lil-debugger";

  onMount(() => lilDebugger().destroy);
</script>
Solid
import { onCleanup, onMount } from "solid-js";
import { lilDebugger } from "@mrmartineau/lil-debugger";

export function LilDebugger() {
  onMount(() => onCleanup(lilDebugger().destroy));
  return null;
}

On the server there is no document. There, lilDebugger() does nothing and returns no-op functions, so it is safe to call anywhere. It also puts its panel and styles back after a client-side router (Astro's ClientRouter, Turbo, htmx) swaps the page.