Proportion system
Layout and size
Panes that share the screen by golden proportions, fixed 16 px sizing, and the size of every common element.
The golden section
Split a length so the whole is to the larger part as the larger part is to the smaller, and the parts are 61.8 percent and 38.2 percent:
A rectangle whose sides are in this ratio loses its largest square and keeps the same shape. That is why the spiral on the Spacing page can go on forever.
On a phone, use it for vertical structure: the main content takes the upper 61.8 percent and secondary content the lower 38.2 percent, and bottom sheets snap at those heights. Cards that need an aspect ratio use 1.618 to 1 (landscape) or 1 to 1.618 (portrait).
Proportional layout
The size of things is fixed, and the arrangement is liquid. Every spacing, radius, type size and element size is a multiple of one unit, written --u, which is the 16 px base on every screen. A button is the same size on a phone and on a wide desktop, and text is the same size too.
What flexes is how much room things share. Panes take fractions of the screen, and a column of text is limited by how many characters it holds rather than by a width. Nothing is scaled up or down, so the layout always fills the screen and the elements stay familiar.
Panes and measure
The documentation panes share the screen by Fibonacci proportions: sidebar, article and outline are 233 : 610 : 144. They are fractions of the free width, not pixels, so the layout always fills the screen. Since 233 plus 377 is 610, the article is itself two golden parts.
Prose is capped by its character count, not by a width. The measure is 76 characters, which is the upper end of comfortable reading, and because it is in ch it follows the type size. Text stays readable on a very wide screen while demos, tables and images fill the column.
Breakpoints
Breakpoints are Fibonacci numbers too, and each step is about phi wider than the one before: 377 is a large phone, 610 a small tablet, 987 a landscape tablet or a narrow desktop and 1597 a wide desktop. They are the only absolute widths in the system. Use them to change structure, such as dropping a pane, and let proportions handle everything else. Design for the smallest width first.
Element sizes
Every element dimension is a Fibonacci number from the spacing scale, in pixels.
| Element | px | Token | Rule |
|---|---|---|---|
| Border | 1 | f1 | Hairline |
| Focus ring | 2 | f2 | Outline width |
| Icon, small | 13 | f13 | Inline with caption text |
| Icon | 21 | f21 | Default |
| Icon, large | 34 | f34 | Empty states, feature rows |
| Control, compact | 34 | f34 | Drawn height. The hit area is still 55 |
| Control | 55 | f55 | Drawn height of primary controls |
| Touch target | 55 | f55 | Minimum. Platforms ask for 44 (iOS) and 48 (Android) |
| Mark, avatar | 34 | f34 | Small. 55 regular, 89 large |
Rules
- Draw compact, hit large. A control can be drawn at 34 px tall but its tappable area must reach 55 px, by padding the hit area and never by enlarging the drawn control.
- Cap text by characters. Reading text is at most 76 characters wide, never a pixel width, and a view has one reading column.
- Panes divide by proportion. If a view has two panes, the larger takes 61.8 percent. Three panes share the width as Fibonacci fractions. Never give a pane a pixel width.
- Never write a pixel size that is not on the scale. Positions and sizes are multiples of the unit from the scale. There is no separate column grid to keep in sync.
- Keep dark and glass surfaces legible. Size and spacing never replace contrast. Text on a translucent surface must still meet contrast after compositing.
Usage
<!-- Tailwind --><main class="grid grid-cols-[233fr_610fr_144fr] gap-f21 p-f21"> <p class="max-w-[var(--measure)]">…</p> <div class="aspect-golden rounded-f21"></div></main>/* the equivalent plain CSS */.screen { display: grid; grid-template-columns: 233fr 610fr 144fr;}.prose { max-width: var(--measure); }.card { aspect-ratio: var(--aspect-golden); }@theme static { --u: 1rem; --phi: 1.618; --phi-inverse: 0.618; --phi-inverse-squared: 0.382; --measure: 76ch; --container-f233: calc(var(--u) * 14.5625); --container-f377: calc(var(--u) * 23.5625); --container-f610: calc(var(--u) * 38.125); --container-f987: calc(var(--u) * 61.6875); --breakpoint-f377: 23.5625rem; --breakpoint-f610: 38.125rem; --breakpoint-f987: 61.6875rem; --breakpoint-f1597: 99.8125rem; --aspect-golden: 1.618 / 1; --aspect-golden-portrait: 1 / 1.618;}/// Astr layout and size constraints: Fibonacci numbers and the golden section.////// Mirrors `scale.css`. Sizes are fixed Fibonacci numbers in logical pixels and/// the arrangement is liquid: panes share the screen by [panes] proportions/// (`Expanded` with these as `flex`) and reading text is capped in characters./// Widths and breakpoints are Fibonacci numbers, and the three documentation/// panes are consecutive terms (233, 377 and 610), so each neighbouring pair is/// one golden ratio apart.abstract final class AstrLayout { // --- Golden section -------------------------------------------------------- static const double phi = 1.618033988749895; /// 1 / phi, the larger part when a length is split in the golden ratio. static const double major = 0.618; /// 1 / phi^2, the smaller part. static const double minor = 0.382; /// Width over height of a landscape golden rectangle (use 1 / this for portrait). static const double goldenAspect = 1.618; // --- Widths and breakpoints (logical pixels) ------------------------------- static const double w233 = 233; static const double w377 = 377; static const double w610 = 610; static const double w987 = 987; /// Reading measure in characters. It follows the type size, so it never /// needs a pixel value: `ConstrainedBox` with `maxWidth: measureChars * size / 2` /// is a close fit for Inter. static const int measureChars = 76; /// Pane proportions of the documentation layout (sidebar : article : outline). static const List<double> panes = <double>[233, 610, 144]; static const double bp377 = 377; static const double bp610 = 610; static const double bp987 = 987; static const double bp1597 = 1597;}/// Element size constraints, all Fibonacci numbers (logical pixels).abstract final class AstrSize { /// Hairline borders and focus rings. static const double hairline = 1; static const double focusRing = 2; /// Icons. static const double iconSmall = 13; static const double icon = 21; static const double iconLarge = 34; /// Control heights. A compact control must still reach [touchTarget] with an /// invisible hit area, never by growing its drawn size. static const double controlCompact = 34; static const double control = 55; /// Minimum touch target. Platform guidance is 44 pt on iOS and 48 dp on /// Android; 55 is the next Fibonacci number above both. static const double touchTarget = 55; /// Marks and avatars. static const double markSmall = 34; static const double mark = 55; static const double markLarge = 89;}