# FR Desk > Read one row of a layout and see where the width actually went. `1fr` is not a share > of the container - it is a share of what is LEFT after the gutters and the fixed > tracks, and a bare `1fr` is `minmax(auto, 1fr)`, so it has a floor it never declared. > Hiding a part is the whole row re-resolved, not a redistribution. And `flex: 1` is > not `flex-grow: 1`. Free browser-side arithmetic, five paid lanes. Live at https://fr-desk.skillsafe.ai/ · API at https://fr-desk.skillsafe.ai/api.html Derived from the `agent-sessions-layout` skill in `microsoft/vscode` (https://github.com/microsoft/vscode) and the `tailwind-design-system` skill in `wshobson/agents` (https://github.com/wshobson/agents). Not affiliated with or endorsed by Microsoft, by wshobson or by the CSS Working Group. ## The one thing to know **One `1fr` is a share of what is left, and the gutters come off the top.** ```text 200px 1fr 1fr in a 1000px container, 24px gap gutters 48px <- (3 - 1) x 24px fixed 200px free 752px <- what the fr tracks actually divide one 1fr is 376px an equal share of the container 333.3px an equal share of what is left 400px ``` Both of the wrong answers are the ones people reach for, and a track costs a gutter as well as a share - so the gap grows with every part added. **A bare `1fr` is `minmax(auto, 1fr)`, so its minimum is the content.** ```text the fr's share is 376px, its min-content is 420px it gets 420px <- clamped UP, past its share the other fr 332px <- pays for it ``` Once every `fr` in a row has clamped up there is nothing left to absorb anything and the row overflows by an exact number: on a real four-part row, 1108px in 900px. `minmax(0, 1fr)` removes the floor and lets the content scroll instead. **Hiding a part is the whole row re-resolved, not a redistribution.** ```text sidebar main aside 240px 356px 356px <- nothing hidden - 488px 488px <- sidebar hidden 240px - 736px <- main hidden 240px 736px - <- aside hidden ``` A hidden track returns its own width AND its gutter, and that total is divided among whatever is still flexible - so the per-track gain depends on how many `fr` tracks are left, not on the hidden track alone. Hide the last flexible track and the space is claimed by nothing at all. **And `flex: 1` is not `flex-grow: 1`.** ```text three items, a 900px row, every one of them grow:1 content 120px 300px 80px flex: 1 300px 300px 300px <- basis 0 flex-grow: 1 253.3px 433.3px 213.3px <- basis auto ``` `flex: 1` expands to `flex: 1 1 0%`, so every item starts at zero and equal weights finish equal. `flex-grow: 1` leaves `flex-basis: auto`, so every item starts at its own content and equal weights finish unequal by exactly the content differences - a 220px spread where the other spelling gives 0px. ## Where the width goes | Tracks | Gutters take | Fixed takes | Free space | One `1fr` is | An equal share of the container | An equal share of what is left | | --- | --- | --- | --- | --- | --- | --- | | 2 | 24px | 200px | 776px | **776px** | 500px | 800px | | 3 | 48px | 200px | 752px | **376px** | 333.3px | 400px | | 4 | 72px | 200px | 728px | **242.7px** | 250px | 266.7px | | 5 | 96px | 200px | 704px | **176px** | 200px | 200px | | 6 | 120px | 200px | 680px | **136px** | 166.7px | 160px | A 1000px container, a 24px gap, one 200px fixed track and the rest `1fr`. **The last two columns are the two answers people actually reach for**, and neither is ever right: the first ignores the fixed track and the gutters, the second ignores the gutters. Adding a track costs a gutter as well as a share, which is why the free-space column falls faster than the track count rises. ## What a floor costs | The track's min-content | Its share | What it gets | What the other fr gets | The row is | Against the container | | --- | --- | --- | --- | --- | --- | | 0px | 376px | **376px** | 376px | 1000px | exact | | 120px | 376px | **376px** | 376px | 1000px | exact | | 300px | 376px | **376px** | 376px | 1000px | exact | | 376px | 376px | **376px** | 376px | 1000px | exact | | 420px | 376px | **420px** | 332px | 1000px | exact | | 560px | 376px | **560px** | 192px | 1000px | exact | `200px 1fr 1fr` in a 1000px container with a 24px gap, so each `fr` share is 376px. **A bare `1fr` is `minmax(auto, 1fr)`** — its minimum is the content's min-content width, not zero. Below the share nothing happens; above it the track takes what it needs and the other `fr` pays, until there is nothing left to pay with and the row overflows. `minmax(0, 1fr)` removes the floor and lets the content overflow or scroll instead, which is almost always what was meant. ## Hiding one part | Hidden | `sidebar` | `main` | `aside` | Unfilled | | --- | --- | --- | --- | --- | | _nothing_ | 240px | 356px | 356px | — | | `sidebar` | — | 488px | 488px | — | | `main` | 240px | — | 736px | — | | `aside` | 240px | 736px | — | — | Each row is the whole layout **re-resolved without that track**, not a redistribution of its width. The gains are `sidebar` → +132px to `main`, `main` → +380px to `aside`, `aside` → +380px to `main` — three different numbers, because hiding a part removes a gutter too and because where its space goes depends entirely on what is still flexible. Hiding the only flexible track leaves the space **unclaimed**. ## The two flex spellings | Item | Content width | `flex: 1` (basis 0) | `flex-grow: 1` (basis auto) | Difference | | --- | --- | --- | --- | --- | | item1 | 120px | 300px | **253.3px** | -46.7px | | item2 | 300px | 300px | **433.3px** | +133.3px | | item3 | 80px | 300px | **213.3px** | -86.7px | | **spread** | 220px | 0px | **220px** | +220px | A 900px row, three items, every one of them with `flex-grow: 1`. **`flex: 1` expands to `flex: 1 1 0%`** — every item starts at zero, the free space is the whole row, and equal weights finish equal. **`flex-grow: 1` on its own leaves `flex-basis: auto`** — every item starts at its own content width, only 400px is free, and equal weights finish unequal by exactly the content differences. Two declarations that read as the same rule, and a 220px spread between them. ## Constants and thresholds | Constant | Value | What it decides | | --- | --- | --- | | `STARVED_PX` | 80px | a resolved track narrower than this is worth naming | | `BIG_SPREAD` | 25% | a flex spread past this share of the widest item is worth naming | | `TIGHT_SHARE` | 15% | how close to its own floor a track has to be before it is worth naming | | `DEFAULT_WIDTH` | 1000px | assumed container, and every figure is a share of it | | `DEFAULT_GAP` | 24px | assumed gutter, subtracted before anything is divided | | `DEFAULT_ROOT_PX` | 16px | assumed root for `rem` track lengths | Only the sizing algorithm is CSS's; everything else is this page's threshold for when a difference is worth saying. The widths, the free space and the toggle figures are measurements, not preferences — the thresholds decide only what gets called a warning. ## Sheet grammar A sheet is a header of `KEY: value` lines and one block: `TRACKS:` for a grid row, or `FLEX:` for a flex row. ```text JOB: what this row is (optional, echoed back) WIDTH: 1000 (px; the container's content box, gutters included. Assumed 1000) GAP: 24 (px; subtracted before anything is divided. Assumed 24) ROOT: 16 (px; multiplies every rem track length. Assumed 16) TRACKS: 240px | 25% | 1fr | 2.5fr | auto | min-content | max-content minmax(, |fr) 1fr min= (the track's min-content width) 240px hidden=1 (declared, but not currently shown) FLEX: grow= shrink= basis=|auto content= min= flex= ``` **Write the tracks in the order they appear in the row.** Every figure on this page is positional: which gutter belongs to which track, what a hidden part gives back and to whom, and which neighbour pays when a floor bites all depend on the order. **`min=` is the one number this page cannot compute for you.** It is the track's min-content width — the widest thing in it that cannot be broken. Leave it off and the track is treated as `minmax(0, 1fr)`, which is the answer most people wanted anyway; supply it and this page will tell you what it costs the tracks beside it. `hidden=1` keeps a track in the sheet without giving it width or a gutter, which is how a workbench's closed panel is written. Every visible track is also resolved once more with itself hidden, so the toggle figures are the real layout rather than an estimate. Anything the reader cannot place is listed as a problem rather than skipped. A track that quietly vanished would make every width on the page a division among the wrong list, and a duplicate name would make two rows of the table describe one track. ## Lanes | Lane | What it produces | | --- | --- | | `plan` | Turn a layout brief into a row that fits | | `audit` (primary) | What each track actually resolves to, and which one is taking the room | | `space` | The division: gutters, fixed tracks, and what one fr is worth | | `toggle` | The toggle: what hiding each part does to the rest | | `decide` | Decide what changes: a floor, a size, or the number of parts | ## A worked sheet ```text JOB: the workbench row - a sidebar and two flexible parts WIDTH: 1000 GAP: 24 TRACKS: sidebar 240px main 1fr min=420 aside 1fr min=200 ``` | Track | Written as | Its share | Its floor | What it gets | Hiding it gives | | --- | --- | --- | --- | --- | --- | | `sidebar` | `240px` | — | 240px | **240px** | +196px to `aside` | | `main` | `1fr` | 356px | 420px | **420px** (clamped up) | +444px to `aside` | | `aside` | `1fr` | 356px | 200px | **292px** | +316px to `main` | The sheet in the worked example. 3 tracks in 1000px with a 24px gap, 712px free, 356px per fr, 1 clamped up by their floor. **Read the third and fifth columns together**: a track that gets more than its share took it from the others, and the column that says why is the fourth. ## Findings | Finding | Severity | Scope | What it says | | --- | --- | --- | --- | | `NO-ROW-TO-RESOLVE` | error | sheet | The sheet declares no tracks and no flex items | | `EVERY-TRACK-IS-HIDDEN` | error | toggle | Nothing in the row is visible | | `A-TRACK-REFUSES-TO-SHRINK` | warning | floor | A track is wider than its share because its content will not fit | | `THE-ROW-OVERFLOWS-ITS-CONTAINER` | warning | space | The tracks add up to more than the container | | `THE-ROW-DOES-NOT-FILL-ITS-CONTAINER` | warning | space | The tracks leave space unused | | `A-TRACK-IS-STARVED` | warning | floor | A track resolves narrower than anything can usefully be | | `A-FLEX-ITEM-GROWS-FROM-ITS-CONTENT` | warning | basis | flex-grow without a basis makes equal weights produce unequal widths | | `THE-FLEX-ROW-SPREADS` | warning | basis | Items that look equally weighted end up very different widths | | `A-FLEX-ITEM-WILL-NOT-SHRINK` | warning | floor | An item is wider than its computed size because of min-width: auto | | `THE-SHEET-HAS-LINES-THIS-PAGE-COULD-NOT-READ` | warning | sheet | Some lines were not readable | | `WHAT-ONE-FR-IS-ACTUALLY-WORTH` | note | space | The free space, and what one unit of weight buys | | `THE-TWO-ANSWERS-PEOPLE-EXPECT` | note | space | Both intuitive answers, next to the real one | | `GAPS-COME-OFF-BEFORE-ANYTHING-IS-DIVIDED` | note | space | The gutters take their share first | | `WHAT-HIDING-EACH-TRACK-WOULD-DO` | note | toggle | Every part toggled, and what the others become | | `A-TRACK-HAS-NO-FLOOR-BY-DESIGN` | note | floor | A track is written so it can shrink to nothing | | `A-TRACK-IS-FIXED-BY-DESIGN` | note | floor | A track takes a set width and does not participate | | `THE-SAME-ROW-WRITTEN-THE-OTHER-WAY` | note | basis | What these items would be with the other flex-basis | | `WHAT-EACH-ITEM-STARTS-FROM` | note | basis | Every item's basis, before any growing happens | | `THE-CONTAINER-WIDTH-WAS-NOT-DECLARED` | note | sheet | The sheet does not say how wide the row is | | `THE-GAP-WAS-NOT-DECLARED` | note | sheet | The sheet does not say how wide the gutters are | 20 findings: 2 errors, 8 warnings and 10 notes. **Nothing that fires on every usable sheet is a warning.** Gutters always come off the top, one `fr` is always worth something, and every part can always be toggled — so those are notes. What gets a warning is a track that refuses to shrink, a row that does not fit its container, or a width small enough that nothing can be usefully put in it. ## What this page cannot do This page reads a description of a row, not your stylesheet. It never parses CSS at large, never lays anything out and never measures a font, so: - **It is the WIDTHS and nothing else.** Whether the result reads well, whether the sidebar is the right size for its content, and whether the row should have been a grid at all are design questions, and none of them changes what the algorithm returns. What is exact here is the number. - **The min-content floor is the number YOU supply.** A real `min-content` width is the widest unbreakable thing inside the track — the longest word, an image's intrinsic width, a `white-space: nowrap` run, a table's own minimum — and only a browser with your content and your fonts can measure it. Write `min=` with a figure you have measured; assumed 0, which is the same as `minmax(0, 1fr)`. - **One row, one axis.** Wrapping (`flex-wrap`, `auto-fill`, `auto-fit`), row sizing, `align-*`, spanning items and nested grids are all outside what this page models. A `repeat(auto-fit, minmax(200px, 1fr))` is a different algorithm and is reported rather than guessed at. - **`auto` is not `1fr`, and this page treats them differently.** An `auto` track takes its content's width and then shares leftover space only if no `fr` track wants it; `min-content` and `max-content` do not grow at all. If your sheet says `auto` where the stylesheet says `1fr`, every number below is about a different row. - **Percentages resolve against the container, not against what is left.** `50%` of a 1000px container is 500px whether or not the gutters fit, which is why a row of percentages that sums to 100% and has any gap at all always overflows. - **Sub-pixel widths are real and this page keeps them.** A browser rounds to device pixels at paint time and the rounding is not distributed evenly, so a track reported at 376.7px can measure a pixel either side of that in the DOM. - **Nothing here says whether the layout is any good.** It says what every track resolves to, where the width went, what changes when a part is hidden, and which declarations cannot mean what they appear to mean. ## API `POST https://api.skillsafe.ai/v1/app-api/run` with an app session token. The body IS the input object — never wrapped in an `input` key. Fields: `task` (one of `plan`, `audit`, `space`, `toggle`, `decide`), `rules`, `prescan`, plus the lane's own fields. `POST .../estimate` with the same body returns `hold_credits` and costs nothing. ## Provenance Every table and every figure above is generated from `tracks.js` by `build-skill.js`. Nothing is typed twice, so nothing can drift.