Foundations
Width
There is no mobile version, no tablet version and no desktop version. There is one layout with one named change in it, and everything else grows or stops growing by itself. This page is what the change is, why it is where it is, and what a component is allowed to do about width at all.
Drag the edge of this window while you read. Every example below is live and none of them is a picture.
measuring
Three ways, and the point comes last
Read top down. Take the fluid answer; if it cannot work, take a container; only if that cannot work either, write a point, and put the reason in the audit. Two of this product's three original width numbers did not survive that question.
| Fluid | Container | Point | |
|---|---|---|---|
| The question | will it stretch by itself? | is the line too long? | is the behaviour different? |
| How | clamp(), %, minmax, flex-wrap | max-width and a measure in ch | @media for the shell, @container for a component |
| How many here | most of the system | five, and one of them is the measure | one |
| What proves it | drag the width and nothing breaks | the measure, counted in characters | a line of the audit saying why fluid could not |
Six boxes with a basis and permission to wrap. Nothing here counts columns, and there is no number to update when a width nobody planned for arrives. The number of columns is never a token in this system, which is the one place it deliberately does less than the shape of a design system suggests.
container-type is declared by whoever places the component, never by the component itself: where a thing stands is not a property of the thing. In this product that means a pattern file or the page frame in base.css.
The point, and there is one
--bp-split-panes · 80rem, which is 1280px at the default root.
At and above it the split is a split: the list and the detail pane stand side by side, the queue row keeps its seven tracks, a dialog is anchored beside the pane rather than covering it, and the shift brief is a brief. Below it none of that is true, and what renders instead is the single column rendering that stage 04 designed: no pane, the row as one track, a line where the log would be.
Three numbers came into this stage and one left it. The census found 900px, 1560px and 1400px, none named, none in a token, all in px, each written where a defect had been found. 1560 was the pane giving up sixty pixels to the list; that is a clamp, and it is one now. 1400 let the annunciator wrap; measured from 1280 to 2560 the strip is the same height either way, so the query was a decoration on a declaration. A query whose effect cannot be observed is not a breakpoint.
The split has an arithmetic minimum and nobody had added it up. The queue row's seven tracks need 646px, the pane's floor was 320, and this case study's own documentation panel takes 236: the split cannot exist below about 1200. The point stood at 900. Between 910 and 1200 the product rendered a split whose row did not fit its own column and whose cells ran past the edge, and it had been doing that since stage 04, because everybody looks at 360, at 1440 and sometimes at 1280, and nobody looks at 1040.
It is 1280 rather than 1210, and that is not arithmetic. CLAUDE.md declares the platform as desktop first with a minimum of 1280. The split now begins exactly where the product says it needs to begin. A half screen window on a two monitor desk is 960 wide, and this product's own analyst is the person most likely to open one.
The name says the change and not a device. --bp-tablet and --bp-desktop are forbidden here: a tablet is a different width every year, and the word desktop puts three separate versions back into the reader's head even when the code has none.
Why the token and the query hold the same number twice. @media cannot read var(): a query is resolved before the cascade of custom properties, so @media (min-width: var(--bp-split-panes)) produces no error and simply never fires. The literal stands in the query and the token is the register. That is not two sources of truth, it is one source and its application, and a check counts every query in the system against it.
The queries are written max-width: 1279.98px rather than 1280, because a max-width query includes its own value and both sides would otherwise be true at exactly 1280.
The 236px in that arithmetic is this case study's panel and not the product's. A deployment of Harrier has no documentation column beside it and its split fits from about 1000. The point is set for the artefact as it is published, because that is the thing anybody can open, and the product's own number is named here so the difference is a decision rather than a confusion.
The pane's floor moved too, and the sweep is what found it. It was 20rem, the 320px the pane had always been, which made the pane at exactly 1280 the narrowest the case ever gets: narrower than a phone, where the case takes the whole 360. The latitude ladder's reason column is 224px and does not fit beside its mark at 320, so on thirteen screens it stuck twelve pixels out of the pane between 1280 and about 1300. Every way of making it fit inside 320 also changed the 360 rendering. It is 22.5rem now, which is 360px: the pane is never narrower than the phone gives the same content.
The container, and the measure
--measure is 66ch. It has been in tokens.css since stage 08 with the words "0 uses" beside it, and it was in the backlog as a token waiting for a screen. This stage is the screen.
A refresh token issued to this user is in use from an ASN the tenant has never seen, and an inbox rule was created from it four minutes later. Clerk found no second signal on the endpoint, and the identity is what moved.
A refresh token issued to this user is in use from an ASN the tenant has never seen, and an inbox rule was created from it four minutes later. Clerk found no second signal on the endpoint, and the identity is what moved.
It only bites above the declared band, and that is the point. The pane is the widest place prose stands in this product, and until this stage it stopped growing at 380 so nothing ever ran long. With the pane fluid it reaches 461 at 1920 and 544 at 2560, and the narrative measured 69 characters and then 82. Between 1280 and 1600 it measures 46 to 57 and the rule changes nothing at all.
It is on the text and not on its container. The container holds blocks that should fill the pane: rules, stamps, rows. A measure is a property of a line somebody reads across, and only four things in this product are that.
Type is fluid, and it never switches
The scale was in px from stage 04 to stage 09. It is in rem now, which at a 16px root is the same number and for a reader who set a larger font is the difference between a layout that answers and one that ignores them. WCAG 1.4.4 asks that text reach 200% without loss, and px is how a layout quietly refuses.
--size-xl · 18 waiting across 12 of 40 tenants--size-lg · C-4417 · Larkfield Logistics--size-md · 14px, fixedClerk proposes compromised identity--size-sm · 12px, fixedToken replay from a new ASN--size-xs · 11px, fixed34 of 36 accepted, 30 daysmeasuring the two fluid sizes at this width
The two ends of each clamp are the product's own two widths. 1280 is the declared minimum and 1920 the top of the declared primary band, so the type grows across exactly the band the analyst sits in and is stage 04's value everywhere below it. Above 1920 it stops: a heading that keeps growing becomes a banner.
The three small sizes do not grow, and that is a decision. Design principle 5 makes density the feature: on a wider monitor this analyst wants more rows, not larger ones. What makes dense text readable is the measure, and the measure is capped above.
A font-size is never switched at a point. Switching a size at a number puts a jump in the middle of somebody dragging a window and leaves widths on either side where the heading is wrong. The middle term of each clamp carries a rem addend rather than being pure vw, because pure vw ignores the reader's own font size and takes the page out of zoom entirely.
The first draft of both clamps was wrong and a measurement said so. The middle terms were solved for 360 rather than for 1280, so the floor stopped winning at about 414px and the declared minimum rendered at 19.4px instead of 17. The 360 rendering was identical either way, which is exactly why it took a second width to see it.
The shell
Three questions to the navigation model of stage 03a, and the answers give the form. The form is not a taste and the navigation model is not reopened.
| The question | The answer |
|---|---|
| How many items at the top level? | Three. Queue, Shift, Log. Clients arrives with cluster 7, and the fleet never gets one: it is the resting state of the queue's pane, so it costs zero taps |
| Is there a second level that must stay visible? | No. The scope bar is per screen and belongs to the list, not to the navigation |
| Does any screen take side space for new behaviour? | No. There is no new behaviour at this stage, and the split it would have introduced has been the product's shape since stage 03b |
The shell does not change form, and it never had a second form to change from. This product has no tab bar and never had one: three items have lived in the top bar at every width since stage 04. What the stage changed is not the form but the behaviour, and it took a measurement to see it was wrong.
The bar was three lines tall from 320 all the way to 1279. Two elements claimed a whole line each with width:100% the moment the window was narrower than the point, which is a phone's bar rendered on a 1200px screen. Both are fluid now: the annunciator takes a basis of 22rem and wraps when there is not enough, the navigation fills whatever line it lands on. Measured: 181px at 360, 116 at 600, 65 at 768, 55 from 1024 up, and unchanged at 360 to the pixel.
Exactly one navigation carrier at every width, verified by computed style at 49 widths rather than by writing it down. There is no hidden second copy in the accessibility tree, which is the usual form of this defect: three items visible, three items in the tree, at 360 and at 1920 alike.
What the components do
Seventy three components and four patterns, every one with a verdict in the "behaviour at width" column of the inventory. Fifty two stand on at least one screen the audit calls wider; eleven stand only inside a modal layer, a door or a system state and are deliberately unchanged; and the shell zones are the only things in the system that measure the viewport at all.
The row is the one that measures its place rather than the window. A queue row does not know whether it stands in a 724px column at the declared minimum or a 1223px one at 1920, and its two prose tracks now take a ceiling counted in characters instead of a fraction: 32ch for what the case is and 36ch for the verdict. Below the ceiling nothing changed at all, which is why nothing moved at or under 1600.
What the patterns do
Four of them, and this is where the stage pays for the one before it. A pattern adapts once, in its own file, and the rule reaches every screen it stands on, including the ones that do not exist yet and will be built at stage 12. That is why the compositions were pulled out before the width work rather than after it: what has to adapt is a composition, not twenty copies of one.
| Pattern | What width does to it |
|---|---|
| queue-list | Below the point the log variant is not rendered at all and says so in one line. Above it, the column takes what the pane does not, and the row's prose stops growing |
| case-pane | Below the point the case becomes the whole page and the pane's own chrome goes. Above it, this is where the extra width goes: 360 at the declared minimum, 461 at 1920, 544 at 2560 |
| fleet | Below the point it is not rendered: the pane at rest is a desk instrument, and node 3.1 settles that selecting at a narrow width opens the case rather than a pane |
| shift-brief | Below the point the brief and the keyboard strip are replaced by a line saying the handover is a desk reading |
New behaviour
None, and it is an answer rather than a gap.
The usual answer at this stage is split-view: a list and a detail side by side instead of one after the other. Harrier has been split-view since stage 03b. It is the chosen UX pattern of the whole product, it is what the two zones of the split are, and it stands on 48 of the 62 screens at every width above the point. There is nothing a wider screen can reveal that this product does not already show at its declared minimum.
Three things were considered and each is a job away from being real. A third column of tenant context has no job: the annunciator already carries it in the top bar at every width, which is cheaper than a column that is empty most of the time. The fleet and a case at once would be the "empty case" reading of the pane that CLAUDE.md forbids in as many words. The log's reading pane beside the queue would put two scopes on one screen, and the readout can make only one counted claim.
The census and the audit
Both tables in full, with what moved and what stayed, are in docs/responsive.md. The two numbers worth carrying away:
| Coming in | Going out | |
|---|---|---|
| Distinct width values in a query, anywhere in the product | 3, none named, none a token, all in px | 1, named for its change, in rem, with the reason written beside it |
| Queries in the file of a screen | 0 | 0, and now it is a rule rather than a habit |
| Screens the audit calls wider | 39 of 62, all by one mechanism: the width goes to the pane and the row's prose stops growing | |
| Screens the audit calls unchanged | 23 of 62: a modal layer, a door, an outage, a case on its own address | |
| Widths where something broke and no point was near | every width from 910 to 1200, on every screen carrying a queue. None now | |