Open source · MIT · Node 18+

Find the layout bugs between your breakpoints.

starchy sweeps your page across every width from 1920px to 320px, finds the exact pixel where the layout changes, and flags the changes that are bugs.

npx starchy your-site.com
Read the docs
starchy sweep width 1920px
media query fluid change problem

    Output

    What a run looks like

    Screenshots at 375, 768 and 1440 miss the bug that only shows at 912px. starchy checks the whole range and gives you the exact pixel, the element, and whether the change was on purpose.

    ~/my-site
    $ npx starchy test/fixture.html
    starchy file:///…/test/fixture.html
      widths 1920→320px, heights 900px
      media-query breakpoints found: 1024, 768
    
      height 900px
       1049px  problem
       1024px  breakpoint
       1000px  problem
        900px  problem
        847px  fluid
        768px  breakpoint
        685px  problem
        614px  fluid
        …
    
      16 layout changes · 7 problems (9 elements) · 11.4s
      report → starchy-report/report.html
    The starchy HTML report: 7 problems, each with the width range where it happens and the element that causes it, above a timeline of every layout change
    starchy-report/report.html

    Checks

    What counts as a problem

    Every problem comes with the width range where it happens and the element that causes it. Identical problems on repeated elements are grouped into one row.

    page-overflow

    Horizontal scroll

    The page is wider than the screen. starchy names the element that causes it: it hides each candidate and checks if the scroll width shrinks.

    overlap

    Overlap

    In-flow sibling elements overlap each other.

    container-overflow

    Escapes container

    An element sticks out of its parent's box.

    small-range

    Small-range layout

    A layout holds for 5px or fewer between two changes. This is usually a media-query off-by-one, such as min-width: 769px next to max-width: 767px.

    text-clipped

    Clipped text

    Text is cut off by overflow: hidden.

    link-wrapped

    Wrapped link

    A link or button label of 5 words or fewer breaks onto 2 or more lines.

    tiny-text

    Tiny text

    Text is smaller than --min-font, 12px by default.

    How it works

    Sweep, bisect, classify

    starchy drives headless Chromium through Playwright. It compares how elements sit in rows and boxes, so font rendering noise cannot make it flaky.

    1. Fingerprint

      Load at 1920px. Record how each container's children sit in rows, e.g. 3+3+1, what is visible, and any problems.

    2. Sweep

      Step down 8px at a time, and stop on both sides of every @media breakpoint. When the fingerprint changes, binary-search to the exact pixel.

    3. Capture

      Take before and after screenshots with the changed elements outlined.

    4. Classify

      Match the width against the @media breakpoints in your CSS.

    5. Report

      Write report.html and a fingerprint.json to compare against later.

    Media queryThe width matches an @media breakpoint. Expected.
    Fluid changeNo media query: flex-wrap, auto-fit or content size. Check it.
    ProblemSomething broke at this width.

    CI

    Run it on every pull request

    Run starchy once on a known-good build and commit its fingerprint.json. On every pull request, compare against it.

    .github/workflows/layout.yml
    name: layout
    on: pull_request
    jobs:
      starchy:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v4
            with: { node-version: 20 }
          - run: npx playwright install --with-deps chromium
          - run: >-
              npx starchy https://staging.example.com
              --baseline .starchy/fingerprint.json
              --fail-on new
          - uses: actions/upload-artifact@v4
            if: always()
            with: { name: starchy-report, path: starchy-report }
    • --fail-on new exits 1 on a new problem or a new layout change. --fail-on problems exits 1 on any problem.
    • The report lists new problems, fixed problems, and layout changes that appeared or disappeared.
    • Pages match by URL. One page compares against a one-page baseline even when the URLs differ, so staging can compare with local.
    • Check several URLs or a whole sitemap.xml in one report. Problems shared by many pages show once.
    • Pages behind a login: pass a Playwright storage state with --storage-state.

    Reference

    Options

    starchy <url-or-file>... [options]. A bare domain gets https://, and localhost gets http://.

    FlagDefaultDescription
    --max / --min1920 / 320Width range to sweep
    --step8Coarse step before bisecting. Lower is slower but more thorough.
    --heights900Comma-separated viewport heights, each swept separately
    --dpr1Device pixel ratio (affects images and hairlines, not layout)
    --min-font12Smallest acceptable text size in px
    --ignoreSelectors to skip, e.g. .carousel,.ad-slot
    --settle0Extra ms to wait after each resize, for JS-driven layouts
    --outstarchy-reportOutput folder
    --baselineAn earlier fingerprint.json to compare against
    --fail-onnoneproblems or new makes starchy exit 1
    --no-screenshotsFaster runs, report without images
    --problem-shotsAdd a screenshot of each problem at its first failing width, with the element outlined
    --stressDouble the text in repeated items (lists, cards, grids) to find layouts that only hold with short content
    --storage-stateA Playwright storage state file, for pages behind a login

    Sweep your page.

    npx starchy your-site.com