debug-css-overflow
v1.0.3
Published
Zero-dependency dev utility that detects horizontal overflow (overflow-x), highlights offending elements, and shows a small Shadow DOM widget.
Maintainers
Readme
debug-css-overflow
Zero-dependency dev utility that detects horizontal overflow (overflow-x), highlights the offending elements, and shows a small floating widget — fully isolated inside Shadow DOM.
- 🎯 Detects whole-page overflow (
scrollWidth > innerWidth) and individual offenders (offsetWidth/getBoundingClientRect().rightbeyond the viewport, with a 1px subpixel tolerance). - 🔦 Hotkey toggles outline highlights on offending elements and layout containers — works even when the auto-status is "OK".
- 📦 Zero runtime dependencies; framework-agnostic; tree-shaken out of production bundles (no DOM access until
initDebugCssOverflow()is called). - 🧩 Widget, tooltip, and modal live in a Shadow Root — host CSS cannot leak in, widget CSS cannot leak out.
- 📡 Event-driven scanning:
resize+MutationObserver+ResizeObserver(debounced), nosetIntervalpolling. - 🎛️ UI preferences (position, accent color, minimized state, badge visibility) persist in
localStorage.
Install
Add it as a dev dependency — it's meant for development, and it's tree-shaken out of production builds.
# npm
npm install -D debug-css-overflow
# pnpm
pnpm add -D debug-css-overflow
# yarn
yarn add -D debug-css-overflow
# bun
bun add -d debug-css-overflowQuick Start
With a bundler (Vite, webpack, etc.)
import { initDebugCssOverflow } from "debug-css-overflow";
// Dev-only, e.g.:
if (import.meta.env.DEV) {
initDebugCssOverflow();
}Plain HTML (no build step)
Drop the pre-built bundle in with a <script> tag — either from a CDN or your own node_modules copy:
<script src="https://unpkg.com/debug-css-overflow/dist/index.global.js"></script>
<!-- or: https://cdn.jsdelivr.net/npm/debug-css-overflow/dist/index.global.js -->
<script>
DebugCssOverflow.initDebugCssOverflow();
</script>With options
import { initDebugCssOverflow } from "debug-css-overflow";
const detector = initDebugCssOverflow({
position: "top-right",
offset: { top: 64 }, // clear a fixed top navbar (52px + 12px gutter)
accentColor: "#ff0055",
onChange: (state) => console.log("overflow state:", state),
});initDebugCssOverflow() returns a controller (or null when enabled: false) and dispatches a debug-css-overflow:change CustomEvent on document whenever the overflow state changes.
// From the controller:
detector?.toggleHighlight(); // toggle container outlines
detector?.setMinimized(true); // collapse the widget to a dot
detector?.destroy(); // remove the widget and stop scanningOptions
| Option | Type | Default | Description |
| ----------------- | --------------------------------- | ------------- | -------------------------------------------------------- |
| enabled | boolean | true | false → nothing is created at all. |
| minimized | boolean | false | Start collapsed into a dot. |
| position | "top-right" \| "top-left" \| "bottom-left" \| "bottom-right" | "top-right" | Widget corner. |
| offset | { top?, right?, bottom?, left? } (px) | {} | Displace the widget from the viewport edge, overriding the default 12px gutter for the edges given — e.g. { top: 64 } clears a fixed top navbar. Only edges matching the current position are applied. |
| accentColor | string | "#ff0055" | Highlight + widget accent color. |
| showMetricsBadge| boolean | true | Show the compact metrics badge when minimized. |
| hotkeys | { toggleHighlight?, toggleMinimize? } | { toggleHighlight: "Alt+O", toggleMinimize: "Ctrl+Alt+O" } | Keyboard shortcuts. |
| storagePrefix | string | "dcso:" | Prefix for localStorage keys. |
| onChange | (state: OverflowState) => void | — | Called whenever the overflow state changes. |
Hotkeys
- Alt+O — toggle container outline highlights.
- Ctrl+Alt+O — collapse / expand the widget to a dot.
Development
pnpm install
pnpm typecheck # strict TypeScript check
pnpm test # vitest (unit geometry + E2E widget tests)
pnpm build # tsup → dist/index.mjs, dist/index.cjs, dist/index.global.js (+ .d.ts)prepublishOnly runs pnpm build && pnpm test so unbuilt or failing code can never be published.
Playground & screenshots
tests_plugin/ is a Vite multi-page playground that showcases and visually tests the widget across five stacks — all mounting the same fixed top navbar and the same intentional overflow edge cases (width: 120vw, an un-scrolled wide <table>, white-space: nowrap, left: 110%):
| Route | Stack |
| ----- | ----- |
| /vanilla/ | Plain HTML + TypeScript |
| /nunjucks/ | Nunjucks (vite-plugin-njk-frontmatter) + HTMX (dynamic DOM insertion) |
| /svelte/ | Svelte 5 (scoped CSS) |
| /vue/ | Vue 3 (scoped CSS) |
| /react/ | React 19 |
cd tests_plugin && pnpm dev # http://localhost:5173The navbar's Toggle Overflow button flips body.layout-broken, which activates or removes every overflow trigger.
pnpm test:screenshot (alias pnpm capture) drives the whole playground headlessly with Puppeteer: it boots the Vite dev server, visits every route at 1440x900 (plus a 375x812 mobile overflow pass), and captures four states — overflown, highlighted, minimized, and clean — into .github/assets/:
| State | Preview |
| ----- | ------- |
| demo-danger.png — widget reports offenders (OVERFLOW) |
|
| demo-highlight.png — container outlines on |
|
| demo-minimized.png — collapsed dot + metrics badge |
|
| demo-clean.png — no overflow, status OK |
|
Per-route copies live in .github/assets/<route>/, and mobile danger shots in .github/assets/mobile/.
The suite never downloads a browser binary: it uses puppeteer-core, launches the system Chrome/Chromium, and the test:screenshot / capture scripts set PUPPETEER_SKIP_DOWNLOAD=true so no browser is ever fetched. Point it at a specific binary when needed:
PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome-stable pnpm test:screenshot
# also honored: CHROME_PATH, CHROMIUM_PATHThe playground, screenshot scripts, and generated assets are dev-only and excluded from the published package via the files: ["dist"] allowlist.
License
MIT © vinyardrip
