Sizing, Spacing & Overflow (Desktop)
Sizing caps
Every dimension that can grow unbounded should have a stated cap, decided deliberately rather than left to accident:
- Line length: cap body text at roughly 60–80 characters per line even in a wide pane — text stretching across a maximized ultrawide window becomes hard to track line-to-line. Use a max-width on the text container, not the whole layout.
- Content column max-width: for single-column content areas (settings pages, detail views), cap around 720–840px and let extra window width become margin, not stretched content.
- Component size caps: modals, dropdowns, and cards should all have a stated max-width/height (see
desktop-ui-surfaces-overlays) — "however big the content makes it" is not a size decision.
- Density modes: if the app serves both casual and power users, consider a comfortable/compact density toggle (row height, padding) rather than picking one density for everyone — but don't build this unless there's a real need; it's easy to over-engineer.
Empty (negative) space
Negative space is a tool for creating hierarchy and rhythm, not a default to maximize everywhere:
- Group related elements with less space between them, and separate unrelated groups with more — the size of the gap should communicate relatedness. This is often called proximity, and it's doing more hierarchy work in a dense app than font-weight alone.
- Don't pad content out with empty space just to "look clean" if the result is a screen that could show 3x the useful information in the same space — desktop users often want density (see the anti-slop "fake centering" tell in foundations). A sparse settings page with one option per screen-height is a red flag, not minimalism.
- Conversely, don't cram controls edge-to-edge with no breathing room — the fix for "too sparse" is never "zero space," it's "space that matches the content's actual grouping."
Overflow limits & dynamism
Decide, explicitly, what happens when content exceeds its container — never leave this to whatever the framework does by default:
| Content type |
Default behavior |
| Single-line text in a fixed-width area (list row, tab label, table cell) |
Truncate with an ellipsis; show the full value on hover via tooltip if it's meaningfully longer |
| Multi-line text with a natural length limit (a note, a description) |
Wrap normally, no truncation, container grows with content up to a max-height, then scrolls |
| Long lists (100+ items) |
Virtualize (render only visible rows) rather than mounting every item — this matters even more on the low-end hardware this app family targets |
| A container at its max-height with more content |
Scroll within that container (see desktop-ui-scrolling-navigation), don't let it push the rest of the layout down indefinitely |
| Numeric values that can grow very large |
Format for readability (1.2K, 3.4M) rather than printing raw digits that can blow out a fixed-width column |
Responsive behavior at real window sizes
A desktop window is resized far more often than a phone screen changes orientation, and it happens during use, not just at load:
- Test every layout at the app's defined minimum window size (see
desktop-ui-layout-navigation), not just a comfortable default size.
- When space gets tight, prefer this order of degradation: collapse secondary panels → shrink non-essential padding → hide (not truncate into illegibility) the lowest-priority controls behind an overflow menu → only as a last resort, truncate text.
- Never let a resize operation cause a layout reflow that's visually jarring (content jumping, reflowing repeatedly mid-drag) — debounce expensive relayout work during an active resize if needed, and settle on the final layout once the drag ends.
Accessibility & internationalization
- On any device where touch is a plausible input (touchscreen laptops, 2-in-1s), give interactive targets a minimum 24×24px hit area even when the visible control is smaller — pad the hit area invisibly rather than enlarging the artwork itself. See
desktop-ui-accessibility-i18n.
- Size labels for translated text, not the English string — German and Finnish commonly run 30–40% longer, and a layout with zero headroom is the first thing to break in translation.
Review format
| Before |
After |
Why |
| Settings page with one control per full screen-height section |
Related settings grouped with tight internal spacing, unrelated groups separated by a larger gap |
Space should communicate relationships, not just fill the screen |
| A 500-item list rendering all rows into the DOM at once |
Virtualized list, rendering only visible rows |
Prevents jank and high memory use, especially on the 8GB-RAM/2GB-VRAM hardware this app targets |
| Table cell showing raw overflowing text pushing the column wide |
Fixed column width, ellipsis truncation, full value in a tooltip on hover |
Keeps the table scannable regardless of content length |
1---2name: desktop-ui-spacing-sizing3description: Use whenever setting sizing caps, spacing/density, empty (negative) space, or deciding overflow/dynamism behavior — what happens when content exceeds its container, when a window is resized small, or when a list gets very long. Depends on desktop-ui-foundations for the base spacing scale. Trigger on "sizing," "spacing," "too cramped/too sparse," "overflow," "truncation," "responsive," or "empty space."4---56# Sizing, Spacing & Overflow (Desktop)78## Sizing caps910Every dimension that *can* grow unbounded should have a stated cap, decided deliberately rather than left to accident:1112- **Line length**: cap body text at roughly 60–80 characters per line even in a wide pane — text stretching across a maximized ultrawide window becomes hard to track line-to-line. Use a max-width on the text container, not the whole layout.13- **Content column max-width**: for single-column content areas (settings pages, detail views), cap around 720–840px and let extra window width become margin, not stretched content.14- **Component size caps**: modals, dropdowns, and cards should all have a stated max-width/height (see `desktop-ui-surfaces-overlays`) — "however big the content makes it" is not a size decision.15- **Density modes**: if the app serves both casual and power users, consider a comfortable/compact density toggle (row height, padding) rather than picking one density for everyone — but don't build this unless there's a real need; it's easy to over-engineer.1617## Empty (negative) space1819Negative space is a tool for creating hierarchy and rhythm, not a default to maximize everywhere:2021- Group related elements with **less** space between them, and separate unrelated groups with **more** — the size of the gap should communicate relatedness. This is often called proximity, and it's doing more hierarchy work in a dense app than font-weight alone.22- Don't pad content out with empty space just to "look clean" if the result is a screen that could show 3x the useful information in the same space — desktop users often want density (see the anti-slop "fake centering" tell in foundations). A sparse settings page with one option per screen-height is a red flag, not minimalism.23- Conversely, don't cram controls edge-to-edge with no breathing room — the fix for "too sparse" is never "zero space," it's "space that matches the content's actual grouping."2425## Overflow limits & dynamism2627Decide, explicitly, what happens when content exceeds its container — never leave this to whatever the framework does by default:2829| Content type | Default behavior |30|---|---|31| Single-line text in a fixed-width area (list row, tab label, table cell) | Truncate with an ellipsis; show the full value on hover via tooltip if it's meaningfully longer |32| Multi-line text with a natural length limit (a note, a description) | Wrap normally, no truncation, container grows with content up to a max-height, then scrolls |33| Long lists (100+ items) | Virtualize (render only visible rows) rather than mounting every item — this matters even more on the low-end hardware this app family targets |34| A container at its max-height with more content | Scroll within that container (see `desktop-ui-scrolling-navigation`), don't let it push the rest of the layout down indefinitely |35| Numeric values that can grow very large | Format for readability (1.2K, 3.4M) rather than printing raw digits that can blow out a fixed-width column |3637## Responsive behavior at real window sizes3839A desktop window is resized far more often than a phone screen changes orientation, and it happens *during* use, not just at load:4041- Test every layout at the app's defined minimum window size (see `desktop-ui-layout-navigation`), not just a comfortable default size.42- When space gets tight, prefer this order of degradation: collapse secondary panels → shrink non-essential padding → hide (not truncate into illegibility) the lowest-priority controls behind an overflow menu → only as a last resort, truncate text.43- Never let a resize operation cause a layout reflow that's visually jarring (content jumping, reflowing repeatedly mid-drag) — debounce expensive relayout work during an active resize if needed, and settle on the final layout once the drag ends.4445## Accessibility & internationalization4647- On any device where touch is a plausible input (touchscreen laptops, 2-in-1s), give interactive targets a minimum 24×24px hit area even when the visible control is smaller — pad the hit area invisibly rather than enlarging the artwork itself. See `desktop-ui-accessibility-i18n`.48- Size labels for translated text, not the English string — German and Finnish commonly run 30–40% longer, and a layout with zero headroom is the first thing to break in translation.4950## Review format5152| Before | After | Why |53|---|---|---|54| Settings page with one control per full screen-height section | Related settings grouped with tight internal spacing, unrelated groups separated by a larger gap | Space should communicate relationships, not just fill the screen |55| A 500-item list rendering all rows into the DOM at once | Virtualized list, rendering only visible rows | Prevents jank and high memory use, especially on the 8GB-RAM/2GB-VRAM hardware this app targets |56| Table cell showing raw overflowing text pushing the column wide | Fixed column width, ellipsis truncation, full value in a tooltip on hover | Keeps the table scannable regardless of content length |