How a screen organises itself according to the available width. The thresholds follow the Material 3 window size classes; vps-admin is the reference implementation.
Width classes
| Class | Width | Navigation | Content |
|---|---|---|---|
| Compact | under 600 px (phone) | bottom bar, 3 to 5 destinations, the rest in a "More" sheet | one column, 16px margins |
| Medium | 600 to 839 px (portrait tablet, open fold) | left rail (96 px) | one wide column or two panes |
| Expanded | 840 to 1199 px | left rail | two panes (list and detail) or a card grid |
| Large | 1200 to 1499 px | left rail | full-width bento (overview) or content up to 1340 px |
| Extra-large | 1500 px and above | left rail | full-width bento, or content up to 1340 px and a support pane on the right (300px widget bar) |
- The breakpoint is a choice: 600 px (M3 window size classes) or 760 px when navigation has more than four destinations or the content needs width. Both are accepted, but pick one per app; vps-admin and the Pelote site use 760 px.
- Never a bottom bar on desktop, never a rail on a phone in portrait.
- The snackbar and FABs sit above the bottom bar; with the rail, the snackbar centres on the content area.
Width on desktop: no dead space
The rule is not "never use the full width" but "never leave dead space". Two ways, depending on what the screen shows.
1. Full-width bento: when the screen is an overview
Dashboard, home, profile, project page: the content is a collection of modules.
- The grid takes the whole width (24 to 32px side margins) with tiles of different sizes according to importance: the main metric in 2×2, charts in 2×1, small values in 1×1.
- A 4-column grid in expanded, 6 in large, 8 to 12 in extra-large; tiles keep a column width between 160 and 320px and change size rather than stretch.
- Rows fill without a hole: choose sizes so the grid lands exactly (
grid-auto-flow: dense). - Text inside a tile stays short; a module with long text opens a detail.
2. Bounded width + pane: when reading or editing
List, detail, form, document, settings: the main content reads continuously.
- Maximum width: body text 65 to 70 characters, form 640 px, list or table 1340 px (vps-admin).
- The remaining space holds a useful pane that does not repeat the content: widget bar (vps-admin, from 1500 px, chosen and collapsible by the person), detail pane next to a list, preview next to a form.
- If nothing useful can fill the space, the content centres. Never fill with decor.
In every case
- Never identical cards stretched over 2560 px: either they change size (bento), or the content is bounded.
- The decorative background (portfolio dot grid, shapes) may take the full width.
- An app can mix both: vps-admin's overview can switch to a full-width bento, its detail pages keep the bounded width and the widget bar.
Foldable screens
- Closed fold: compact, like a phone.
- Open fold, flat (about 700 to 900 px): medium or expanded. Left rail and two panes, one on each side of the hinge.
- Hinge: no text, button or important image crosses it. Web:
@media (horizontal-viewport-segments: 2)andenv(viewport-segment-width 0 0)to place the panes on either side. Flutter:MediaQuery.displayFeaturesandDisplayFeatureSubScreen. - Tabletop posture (half folded, horizontal): content to read on top, controls at the bottom, exactly like the 3DS two-screen pattern (
pz-dualin Plaza). - Moving from one posture to another keeps state: no reload, the selection stays selected.
Empty space
- A screen with no content yet shows an empty state that guides (what to do, how long it takes), never a large white area.
- In a card, no short element alone on its line (see UX rules).
- In a grid, cards in the same row have the same content height; no empty cell dangling at the end of the grid: the column adapts to the number of items.
Height and scrolling
- The navigation bar (rail or bottom bar) stays fixed; the content scrolls.
- The support pane scrolls independently (vps-admin: widget bar in
position: sticky, screen height). - On mobile, respect the safe areas (
env(safe-area-inset-*)): the bottom bar adds the bottom inset to its padding.