Find out what won’t fit before your users do.

wontfit shows your running web app at phone and tablet widths side by side, in the browser you already have open. It names the element that is too wide for a 375px screen and fails your build when a layout breaks.

pipx install wontfit
Runtime deps
0
Python
3.9+
Binds to
127.0.0.1
License
MIT
wontfit check --pages /,/pricing,/terms --widths se --fail-on overflow
pagesizeoverflowsmall tapstext<12px
/375x800+401px (img.shot)91
/pricing375x800+583px (table.compare)31
/terms375x800fits310
FAIL (overflow)/ @ 375: overflow +401px (img.shot)/pricing @ 375: overflow +583px (table.compare)exit 1
Real output against the Ledgerly demo site that ships with wontfit.

One column per page and width.

Pick pages and widths, or presets from iPhone SE to iPad. Each frame carries its own verdict, and the layout lives in the URL, so a review is a link you can paste into a pull request.

Three copies of a landing page at iPhone SE, iPhone 15 and Pixel 8 widths, each flagged for horizontal overflow by 364 to 401 pixels

What it catches.

Four checks run inside every frame. They are heuristics, kept few and explainable on purpose.

Overflow, with the culprit named

Not just a red frame. The pricing table is 583px too wide, and the footer says table.compare and its bounds. Only the outermost offender is listed, so a wide table does not report every row inside it.

A pricing page at 375px wide flagged with overflow of 583 pixels, culprit table.compare

Tap targets under 44px

Press t to outline every interactive element smaller than 44 by 44 CSS pixels inside the frame, the size WCAG 2.5.5 and Apple's guidelines ask for.

Dashed outlines drawn around small buttons and links on a phone-width page

Inspect across every frame

Press i and hover an element in one frame. It is highlighted with its size in every other frame, so you see the same heading at 375, 393 and 412 at once.

The same heading highlighted with its dimensions in three phone frames

Landscape

Swap width and height with one toggle. Here the app's fixed header grows to two rows and covers the numbers it was meant to introduce.

Two landscape phone frames where a fixed header covers the dashboard cards

Then it gates the pull request.

The same diagnostics run headlessly. wontfit check exits 1 on overflow and writes a JSON report; wontfit shoot writes a PNG per page and width plus a contact sheet for the review.

pip install 'wontfit[shoot]'
playwright install --with-deps chromium

wontfit check --pages /,/pricing \
  --widths se,iphone15,pixel8 \
  --fail-on overflow --json report.json

wontfit shoot --pages /,/pricing \
  --widths se,iphone15,pixel8 --out shots

A complete GitHub Actions job, a pre-push hook and recipes for twelve stacks are in the docs.

A contact sheet tiling six screenshots: three pages at two phone widths

Why not the tools you already have?

ToolGood atWhere wontfit differs
DevTools device modeA quick look at one page, one size, in the tab you are in.Three pages at three widths after every save, and it names the element that is 40px too wide.
Responsively, PolypaneDedicated browsers with synced scrolling, device frames and much more.Runs in the browser you already use, installs in seconds, and the same checks run in CI.

If you need synced scrolling, device bezels or emulated touch, use one of those. If you want something you can install and forget, and the same checks in CI, this is it.

What it deliberately does not do.

  • WebSockets. An upgrade request gets a 501; the docs show how to keep your framework’s hot reload pointed at the dev server.
  • Rewrite bodies. HTML, JS and JSON pass through byte for byte.
  • Listen on a network. It strips framing protection from whatever you point it at, so it binds to 127.0.0.1 only and always will.