Writeup

When you squeeze a webpage, do the boxes move in a pattern?

×

The question

I was trying to find out something small. When you drag a browser window narrower, the boxes on the page move. Headings wrap. Columns squash. Sidebars drop. Is that movement random, or does it have a pattern a model could learn, the way physics learns how a ball falls?

Taste Labs asked for coordinates for websites the way physics has coordinates for objects. A model looking at an interface has no frame to reason in. I picked the smallest version of that question I could actually measure. If I squeeze the window, is there structure in how the boxes move, and can a model guess the next layout on a page it has never seen?

How I measured it

I left each page open in a headless browser, a browser with no window on screen, and swept the width from 320 pixels, a phone, to 1600 pixels, a wide monitor, in 20 pixel steps. At every width I wrote down every visible box. The window width plays the role of time, so each box becomes a path. Twenty five pages went in: seven from this site, seventeen components from my design library, and one page with many components at once. That is 3,056 box paths, 65 snapshots each.

I left a page open and slowly made the window narrower, writing down every box each time.

  1. 01Hold a page open25 pages in a headless browser
  2. 02Squeeze the window320px to 1600px, 20px steps
  3. 03Log every box3,056 paths × 65 widths
The collector. One page stays open while the viewport is swept in 20px steps, and every visible box is recorded at every width.

Is there a pattern?

First question. How many numbers does the movement actually need? I stacked each page's left edge and width into a table and asked how much of the motion lives in the first shared pattern. That first pattern carries a median 84.7% of the movement, and three patterns reach 99.9%. So yes, reflow, the way a page rearranges when you squeeze it, is low dimensional. A page full of hundreds of boxes is really one or two numbers moving, and everything else riding along.

Each bar is a page. Taller means more of that page's boxes are doing the same move. Hover one to read the percent.

0255075100one bar per page, tallest firstshare of box movement in one motion (%)

Hover a bar to read a page

  • one shared motion100.0%
  • median across pages84.7%
Share of layout variance held by the first component, one bar per page. Accent bars are pages from this site.Hover, tap, or use the arrow keys. Click a name to hide it.

Can a model predict it?

That result is real, but it is not the interesting half. If the motion is one shared pattern, the next question is whether a model can learn it well enough to predict a page it has never seen. I set that up as leave one page out: train on 24 pages, guess the boxes on the 25th. The model never sees the page name or the element name. All it gets is one box measured at 1600 pixels, a few CSS facts about the element, and the width you are asking about.

A one-line rule beat my model

Then the baselines, which is where this went sideways in a useful way. The dumbest one keeps the box exactly as it was. The next one scales left edge and width by the ratio of the two window widths and leaves top and height alone. That is one line of arithmetic. I also hand wrote a clamp rule, because `width: min(42rem, 100% - 2.5rem)` is what my own layout actually does, so I encoded it: keep your width until the window squeezes you, then take what is left minus the gutter, and stay centred if you were centred.

The scoreboard

The one line rule won against ridge, a linear model that fits a straight relationship and shrinks big weights. Median error across held out pages was 26.7 pixels for scaling with the window, against 44.1 pixels for the fitted linear model. It won 14 of 25 pages outright when those were the only learned options, the clamp rule took 6, and ridge took 5. I gave ridge a second chance by handing it the baseline for free and asking it to predict only the correction, with the penalty tuned on a validation split that never touched the test page. It came back at 45.2 pixels, meaning it could not even improve on a correction of zero.

Trees fixed it

That is the point where I should have stopped calling ridge the model. Histogram gradient boosting is a tree model that learns jumps, which is exactly what a CSS breakpoint looks like, the width where a layout suddenly changes. I trained it on the same leave one page out split, the same features, and the same residual, meaning it predicts the correction to the one-line rule, not the box from scratch. Median error fell to 7.3 pixels. A second version that could also see the two hand rules as extra columns came in at 6.8 pixels. Together they beat the better hand rule on 19 of 25 pages. On boxes that actually move, the gap is larger still: 7.8 pixels against 33.4 pixels for scaling.

Lower is better. This is how many pixels off each guess was, at each window width. Trees are the bright line.

0204060500750100012501500viewport width (px)mean error (px), lower is better

Hover the chart to read a width

  • trees20.5px
  • scale37.8px
  • ridge67.6px
Prediction error against viewport width on pages the model never saw. Accent is the tree model. Click a name to hide it.Hover, tap, or use the arrow keys. Click a name to hide it.

Was it just more training?

I also checked whether this was just more training of a weak net. A small neural net with two hidden layers kept improving out to 200 steps and still sat at 32.3 pixels on the capacity pages, next to the rule at 36.0 pixels and the trees at 8.5 pixels. More boosting rounds help until about 64, then flatten, and 256 is slightly worse. Five percent of the training rows already beat the rule. So the first result was the wrong kind of model, not too few epochs, and not too little data.

I gave the model more practice, then more examples. It got better fast, then stopped. The neural net never caught the trees.

01020304050100150200250boosting roundsmedian error (px)

Hover the chart to read a value

  • more practice10.0px
  • one-line rule36.0px
Switch between more practice, more examples, and a small neural net. The flat line is the one-line CSS rule on those same pages.Hover, tap, or use the arrow keys. Click a name to hide it.

Where it still breaks

Trees do not win everything. The clamp rule still takes 6 pages, and on those pages its error is already about 0 pixels because the component is the CSS I encoded. On clocks the trees are worse than scaling, 32.0 pixels against 16.8 pixels, because they add jumps where the layout does not have any. The one multi-component preview page is still hard for everyone: 95.5 pixels for boosting against 140.0 pixels for scaling. Library components, which is the transfer that is not just my site talking to itself, go from 24.5 pixels under the rule to 7.8 pixels under the trees.

Checking the collector

I checked the collector the same way as before. Holding out alternate widths on the same page instead of whole pages gives 0.189 pixels of error and 98.5% of coordinates inside 2 pixels, so the traces are clean. Drawing a straight line per box from narrow windows to wide ones is worse than not moving the box at all, 43.2 pixels against 38.9 pixels, because the layout does not travel in straight lines.

The layout jumps

It jumps. Every page has a median of five widths where a chunk of boxes move together, and they cluster in two places: 340 to 400 pixels, where small phone layouts give up, and around 700 pixels. That second one is not a media query, a CSS rule that fires at a named width. My content column is `min(42rem, 100% - 2.5rem)`, and 42rem plus the gutters lands right there. The largest layout event on my own site is a clamp switching which side of the min() it is on, and I did not know that until the sweep told me.

One heading and one paragraph as you squeeze the window. Hover to see how wide each one actually is.

0200400600500750100012501500viewport width (px)element width (px)

Hover the chart to read a width

  • <h1>520px
  • <p>520px
Two real elements from the home page. Flat stretches are the clamp holding a width. Dashed lines are widths where many boxes jump together.Hover, tap, or use the arrow keys. Click a name to hide it.

What I'd claim, and what I wouldn't

So the honest reading. Claim one holds: web reflow is low dimensional and measurable, and 3,056 paths is a real environment you can put a model in. Claim two was a linear result dressed as a layout result. A tree that can put a jump at a breakpoint transfers. The remaining difficulty is the pages where a one-line clamp is already exact, and the pages where many components interact, not the smooth scaling in between.

What I wouldn't claim

What I would not claim. This is still 25 pages, most of them mine, and my CSS habits are all over the dataset. The library split is the better check and it still favours the trees, but it is one author's components. I did not need a transformer or a GPU job for this. It is tabular box regression, guessing numbers in a table, and boosting is the boring default that I skipped the first time.

Run it yourself

The code is in `research/reflow-dynamics`. The collector is Playwright. The linear numbers are numpy in `evaluate.py` writing `results.json`. The tree numbers are sklearn in `train_stronger.py` writing `results_stronger.json`. Every figure on this page is generated from those files, and re-running them re-draws the figures. If a figure disagrees with what I wrote here, the file wins.

Reproduce it

Run these in order from the research folder. Each step writes the file the next one reads.

The files

I put the files under the writeup. Take one, or take the zip.

writeup.md · 61 lines

# When you squeeze a webpage, do the boxes move in a pattern?
## The question
I was trying to find out something small. When you drag a browser window narrower, the boxes on the page move. Headings wrap. Columns squash. Sidebars drop. Is that movement random, or does it have a pattern a model could learn, the way physics learns how a ball falls?
Taste Labs asked for coordinates for websites the way physics has coordinates for objects. A model looking at an interface has no frame to reason in. I picked the smallest version of that question I could actually measure. If I squeeze the window, is there structure in how the boxes move, and can a model guess the next layout on a page it has never seen?
### How I measured it
I left each page open in a headless browser, a browser with no window on screen, and swept the width from 320 pixels, a phone, to 1600 pixels, a wide monitor, in 20 pixel steps. At every width I wrote down every visible box. The window width plays the role of time, so each box becomes a path. Twenty five pages went in: seven from this site, seventeen components from my design library, and one page with many components at once. That is 3,056 box paths, 65 snapshots each.
## Is there a pattern?
First question. How many numbers does the movement actually need? I stacked each page's left edge and width into a table and asked how much of the motion lives in the first shared pattern. That first pattern carries a median 84.7% of the movement, and three patterns reach 99.9%. So yes, reflow, the way a page rearranges when you squeeze it, is low dimensional. A page full of hundreds of boxes is really one or two numbers moving, and everything else riding along.
### Can a model predict it?
That result is real, but it is not the interesting half. If the motion is one shared pattern, the next question is whether a model can learn it well enough to predict a page it has never seen. I set that up as leave one page out: train on 24 pages, guess the boxes on the 25th. The model never sees the page name or the element name. All it gets is one box measured at 1600 pixels, a few CSS facts about the element, and the width you are asking about.
## A one-line rule beat my model
Then the baselines, which is where this went sideways in a useful way. The dumbest one keeps the box exactly as it was. The next one scales left edge and width by the ratio of the two window widths and leaves top and height alone. That is one line of arithmetic. I also hand wrote a clamp rule, because `width: min(42rem, 100% - 2.5rem)` is what my own layout actually does, so I encoded it: keep your width until the window squeezes you, then take what is left minus the gutter, and stay centred if you were centred.
### The scoreboard
The one line rule won against ridge, a linear model that fits a straight relationship and shrinks big weights. Median error across held out pages was 26.7 pixels for scaling with the window, against 44.1 pixels for the fitted linear model. It won 14 of 25 pages outright when those were the only learned options, the clamp rule took 6, and ridge took 5. I gave ridge a second chance by handing it the baseline for free and asking it to predict only the correction, with the penalty tuned on a validation split that never touched the test page. It came back at 45.2 pixels, meaning it could not even improve on a correction of zero.
## Trees fixed it
That is the point where I should have stopped calling ridge the model. Histogram gradient boosting is a tree model that learns jumps, which is exactly what a CSS breakpoint looks like, the width where a layout suddenly changes. I trained it on the same leave one page out split, the same features, and the same residual, meaning it predicts the correction to the one-line rule, not the box from scratch. Median error fell to 7.3 pixels. A second version that could also see the two hand rules as extra columns came in at 6.8 pixels. Together they beat the better hand rule on 19 of 25 pages. On boxes that actually move, the gap is larger still: 7.8 pixels against 33.4 pixels for scaling.
### Was it just more training?
I also checked whether this was just more training of a weak net. A small neural net with two hidden layers kept improving out to 200 steps and still sat at 32.3 pixels on the capacity pages, next to the rule at 36.0 pixels and the trees at 8.5 pixels. More boosting rounds help until about 64, then flatten, and 256 is slightly worse. Five percent of the training rows already beat the rule. So the first result was the wrong kind of model, not too few epochs, and not too little data.
## Where it still breaks
Trees do not win everything. The clamp rule still takes 6 pages, and on those pages its error is already about 0 pixels because the component is the CSS I encoded. On clocks the trees are worse than scaling, 32.0 pixels against 16.8 pixels, because they add jumps where the layout does not have any. The one multi-component preview page is still hard for everyone: 95.5 pixels for boosting against 140.0 pixels for scaling. Library components, which is the transfer that is not just my site talking to itself, go from 24.5 pixels under the rule to 7.8 pixels under the trees.
### Checking the collector
I checked the collector the same way as before. Holding out alternate widths on the same page instead of whole pages gives 0.189 pixels of error and 98.5% of coordinates inside 2 pixels, so the traces are clean. Drawing a straight line per box from narrow windows to wide ones is worse than not moving the box at all, 43.2 pixels against 38.9 pixels, because the layout does not travel in straight lines.
## The layout jumps
It jumps. Every page has a median of five widths where a chunk of boxes move together, and they cluster in two places: 340 to 400 pixels, where small phone layouts give up, and around 700 pixels. That second one is not a media query, a CSS rule that fires at a named width. My content column is `min(42rem, 100% - 2.5rem)`, and 42rem plus the gutters lands right there. The largest layout event on my own site is a clamp switching which side of the min() it is on, and I did not know that until the sweep told me.
## What I'd claim, and what I wouldn't
So the honest reading. Claim one holds: web reflow is low dimensional and measurable, and 3,056 paths is a real environment you can put a model in. Claim two was a linear result dressed as a layout result. A tree that can put a jump at a breakpoint transfers. The remaining difficulty is the pages where a one-line clamp is already exact, and the pages where many components interact, not the smooth scaling in between.
### What I wouldn't claim
What I would not claim. This is still 25 pages, most of them mine, and my CSS habits are all over the dataset. The library split is the better check and it still favours the trees, but it is one author's components. I did not need a transformer or a GPU job for this. It is tabular box regression, guessing numbers in a table, and boosting is the boring default that I skipped the first time.
## Run it yourself
The code is in `research/reflow-dynamics`. The collector is Playwright. The linear numbers are numpy in `evaluate.py` writing `results.json`. The tree numbers are sklearn in `train_stronger.py` writing `results_stronger.json`. Every figure on this page is generated from those files, and re-running them re-draws the figures. If a figure disagrees with what I wrote here, the file wins.

Credits

KindWriteup
DateSep 2026
TagsLayout, Eval, Taste Labs