Designing a screen

Controls do not make a screen. A panel built by mapping every fact to a label and every verb to a button, stacked in one column at one row height, is a terminal with buttons: it passes the letter of "real widgets" and fails everything a person actually uses a screen for. This page is the procedure from "these facts and these verbs" to a laid-out screen, in the order the decisions have to be made, with the rule each step enforces and the published source for that rule. It is a method, not taste: every rule below is checkable, and most are checkable by a test over the panel's own reported rectangles and strings.

The sources are the ordinary ones — Nielsen Norman Group's research articles, the Windows, GNOME, and Apple human-interface guidelines, and Material Design's published scales. They are linked where they apply and collected at the end so an agent can read the primary text rather than this page's summary of it.

1. Regions before widgets

The first decision is not a control. It is the division of the window into regions, each with one purpose and its own ground. The primary content — the map, the document, the scene — owns the largest region. Supporting content — the inspector, the sheet, the library — is docked beside it in its own region, never floated over it.

The reason is perceptual, not aesthetic. People group what is near (proximity: "items close together are likely to be perceived as part of the same group") and what shares a boundary (common region: "items within a boundary are perceived as a group and assumed to share some common characteristic or functionality"). A panel floating over the map is inside the map's region, so it reads as part of the map — and its position in that space reads as a claim about hierarchy that nobody intended. Docking it gives it its own region, and the boundary between the two says "these are two things."

Both desktop guideline sets describe the same shape. GNOME's utility panes "display additional controls, locations or information alongside the main window view," placed "on the left if they influence the main view, or on the right when they serve a subordinate role." Material's supporting-pane layout is the same division with the same left/right rule.

Concretely:

  • Decide the regions on paper first: primary view, supporting pane(s), a header band if identity belongs to the whole screen. Give each a fixed or proportional width and a minimum. A supporting pane is a fixed logical width (a few hundred pixels), not a fraction of the window: a pane that is 60% of a 3000-pixel window is a wall.
  • The primary view's viewport is its region, not the window. "Fit to view" fits the region. Nothing about the supporting pane is the primary view's business; it is not told to avoid a rectangle.
  • Content that is itself a separate thing gets a separate region: an ascendancy sub-tree drawn over the main tree's nodes is one region carrying two things, and the reader cannot tell them apart. Give it an inset, a second viewport, or a mode — a boundary of its own.
  • Transient UI (a picker, a menu) is the one thing allowed to overlay, and it overlays its own region's content, is light-dismissed, and is gone when answered (WinUI expander notes draw exactly this line between transient and pushing content).

2. Inventory and rank the facts

Before any control is picked, list every fact the screen shows and every verb it offers, and rank them. Who or what this is (identity) comes first; the numbers the whole screen exists to show come second; everything else is secondary or machinery. The rank decides placement, size, and words.

  • Identity goes in the header band, top-left, in full. Eye-tracking is unambiguous: "first lines of text on a page receive more gazes than subsequent lines" and "first few words on the left of each line receive more fixations than subsequent words" (F-pattern). Put the name, the class, the level — whatever the reader would say first when describing the thing — where the eye lands first, at the largest size on the screen.
  • One place per fact. A fact shown twice makes the reader check whether the two agree. Nielsen's aesthetic and minimalist design: "interfaces should not contain information that is irrelevant or rarely needed." A sheet that restates the class, ascendancy, and name the header already shows is repeating itself.
  • Full words, the domain's words. Nielsen's second heuristic: "use words, phrases, and concepts familiar to the user, rather than internal jargon." GNOME's writing style: "a three-word label providing clear information surpasses a one-word label that's ambiguous." An abbreviation is allowed only when it is the domain's own word (DPS is; Light for lightning is not; Asc is not). Truncating a value — a resistance that falls off the row's end — is never allowed: the value is the point of the row. Widen, wrap, or reflow.
  • Define the hierarchy before drawing. NN/g's visual hierarchy page says it directly: "before beginning a design, take a step back from the visuals and define the hierarchy of the content and the key point(s) you want the user to take away." The levers are scale, colour/contrast, and grouping. If every row is the same size and weight there is no hierarchy, whatever the order.

3. Choose the control by the shape of the choice

A control is chosen by what kind of choice it presents, and the guideline sets agree on the thresholds closely enough to state them as one table. N is the number of options; "attention" is whether every option deserves to be seen every time.

The choiceControlRule and source
Binary, on/offToggle or checkboxTwo radio buttons for one yes/no is wrong (WinUI radio buttons)
One of 2–5, all deserve attentionRadio group, or a segmented controlRadio "when users need to see all options before they make a selection" (WinUI); segmented "no more than about five to seven segments in a wide interface" (Apple HIG), equal widths, short labels
One of 3–8, one is the current/default, others secondaryDropdown (pop-up showing the current value)"Use a combo box when the selection items are of secondary importance … draws the user's attention to the selected item" (WinUI combo box); GNOME: never with fewer than three items (drop-downs)
One of more than 8, staticDropdown"If there are more than eight options, use a combo box" (WinUI radio buttons)
One of many, dynamic or longList with a filter field, 3–9 rows visibleListbox "ideal range of items … is 3 to 9"; "don't use a listbox if it forces users to scroll excessively" (NN/g listbox vs dropdown, WinUI)
A verb that belongs to one row of a listA quiet button at that row's right end, reported with the row it belongs toA per-row remove is what every list of the reader's own entries wants; binding the verb to the selected row instead costs a select first, and ranks every row's verb as a primary action if it is filled
A number in a rangeNumeric field sized to the number, or a sliderField "about the same size as the expected input" (NN/g form design)
A commandButton; … when it asks for more before actingGNOME writing style: ellipsis "if further input or confirmation is required"
Parallel content sets, one viewed at a timeTabs, one row, 1–2 word labels"Tab labels should usually be 1-2 words"; "prominently highlight the selected tab" (NN/g tabs)
Secondary detail under an always-visible primaryExpander"Some primary content should always be visible, but related secondary content may be hidden until needed" (WinUI expander)
Content the reader needs most ofNot an accordion"Accordions should be avoided when your audience needs most or all of the content on the page" (NN/g accordions)
A value whose meaning the reader may not carryTooltip, sections divided by a rule, wrapped at a reading measureHover explains, in a neat measured box, after a dwell
An explanation of whatever the pointer is resting on in a listA tooltip anchored on the row rectangle the list itself reports for the hovered rowThe list knows which row the pointer is on and where that row is; a second computation of that geometry is wrong the moment the list scrolls under a pointer that has not moved
A refusal or a confirmationToast region, one per screen, coloured by severityNotices have one place, one severity colour each (never the accent), and leave on their own after a few seconds
A split between two regions the reader setsSplitter, two lit pixels over a generous stripKeep an affordance the reader has learned; the pointer says what a drag will do, so the cursor changes over the strip
A task the reader opened and must finish or leaveDialog, a titled plate over the screen, resized from its own edgesA modal is a place, so it says where it is: a name at the top, a rule under the name, and a body that is the reader's to resize when what is in it does not fit
A group of controls raised over the primary viewPopover, drawn after the content it covers, light-dismissedA setting is a control, not a file; a pop-up takes priority over what it covers by where it stands in the draw order, never by a draw layer

Two consequences worth stating because they are the common mistakes:

  • A stack of expanders is an accordion, and an accordion whose sections the reader needs all of — or which, all open, does not fit the region — is the wrong control. Content the reader consults constantly is pinned; the rest are tabs, because tabs are parallel and one-at-a-time by construction and never overflow when "all open."
  • A dropdown shows the current value closed, which is the whole reason to use one for "current choice, other options enumerated." A list of options standing open all the time for a single current choice is a listbox, and a listbox with a highlighted first row while the model holds no selection is a lie the reader will act on.

4. Space and density

Density is set by a spacing scale, not by a row height. Material's 8-pixel grid is the convention most systems share: spacing tokens are multiples of 4 and 8 (4, 8, 12, 16, 24, 32), and every gap on the screen is one of them.

  • Row height is derived, from the type size plus vertical padding, not one constant applied to every row. A section heading and a body row are different heights because they are different sizes.
  • Inside a group, gaps are small; between groups, gaps are large — proximity again, and the whole trick to making five blocks read as five blocks. Whitespace first; a border or a rule only when whitespace cannot do it (common region "can overpower other grouping principles," so it is the strong tool, used sparingly).
  • Controls are sized to their content. A field is as wide as its expected input. A button is its label plus padding, with a minimum width, and a row of buttons is not three equal thirds of the column. A list shows 3–9 rows and is never a tall empty box: zero items get one line of empty-state text.
  • Targets are big enough and near enough. Fitts's law: "the larger the target, the shorter the movement time," and related controls sit next to each other so the pointer does not cross the screen between steps. A minimum hit height of 24–32 logical pixels; frequent controls at a region edge, where the edge stops the pointer.

5. Type and alignment

  • A type scale with named roles. At minimum title, heading, body, and caption/label, each a distinct size and weight. Material's published scale is the reference (title 22/16, body 16/14, label 14/12 in pixels at 1×; type scale tokens), and GNOME's guidance for using it: "smaller and/or lighter text for less important information, and heavier/darker text to attract attention" (typography). No all-caps, no italics, no hard-coded one-off sizes.
  • A proportional face for UI text. Monospace is for code and for columns of numbers, and columns of numbers get it through tabular figures in the UI face, not by setting the whole screen in a code font. Aligning columns by counting characters and padding strings with spaces is printf, and it is why a screen reads as terminal output no matter what draws it.
  • One alignment rule per column. Text left-aligned to one edge per column; numbers right-aligned in their column so magnitudes line up; a label beside its field is left-aligned and adjacent to it — "labels should be close to the fields they describe," never right-aligned or equidistant between two fields (NN/g form design).
  • Button labels are centred, horizontally and vertically, and a single glyph is optically centred: measure the ink box of the glyph, not the advance width of the character cell, or a + sits low and left of where the eye says centre is.
  • Nothing is cut. A string that does not fit its slot is a layout problem, not a string problem. Names may elide with an ellipsis only when the full form is one hover away; values never elide.

6. One meaning per visual token

Every colour role and shape means one thing across the whole screen — Nielsen's consistency heuristic: "users should not have to wonder whether different words, situations, or actions mean the same thing."

RoleMeansNever also means
Accent fillthe primary actiona selected row, a selected segment, a disclosure
Selectionthe current item in a list or a seta button
Hover / pressedpointer feedback, as overlays over any role—
Disabledalpha over the rolea different colour
Disclosurea small chevron beside the titlea large filled button
Category inkwhat a word names — its tier, its element, its kinda plate behind the row, or the accent

A theme that has one accent and one text size cannot express this table, which is the honest reason a panel built on it ends up with gold meaning four things. The roles have to exist in the theme before a consumer can keep them apart.

7. Review

A screen is reviewed against the steps above, in order, before it is judged on looks. The review is a heuristic evaluation in Nielsen's sense — a walk through named rules, each producing a finding or a pass — and every item below is phrased so a reviewer can answer it yes or no. Where the panel reports its own rectangles and strings (as a query kind), most items are also a test.

  1. Regions: the supporting pane never overlaps the primary view at the reference window; the primary view fits to its region; separate things have separate regions.
  2. Identity: name, kind, and the two or three facts a person would say first are in the header band, top-left, at the largest type on the screen, in full words.
  3. One place per fact: no fact appears twice.
  4. Words: no abbreviation outside the domain's own vocabulary; no value is truncated; every command that asks for more ends in ….
  5. Control fit: each choice uses the control the table in §3 gives for its N and attention; a dropdown shows its current value; no list shows a selection the model does not hold; no accordion carries content the reader needs all of.
  6. Space: every gap is a token from the scale; gaps inside a group are smaller than gaps between groups; row heights vary with type role.
  7. Sizing: fields match expected input; buttons are label plus padding; lists show 3–9 rows and have an empty state.
  8. Type: at least three distinct type roles are visible; UI text is proportional; numbers are tabular.
  9. Alignment: one left edge per text column; numbers right-aligned; labels adjacent to their fields; button labels centred, glyphs optically centred.
  10. Tokens: the accent appears only on primary actions; selection uses the selection role; disclosure is a chevron.
  11. Squint test: with the screen blurred, the biggest and brightest shape is the most important fact.

Rules learned from use

The steps above are the design pass. The rules below came out of watching a person use a screen built by them; each is stated so a reviewer can check it, and each is the general form of a specific complaint.

  • Native where the platform has it. Menus, cursors, the application's name in the platform's own chrome, the window title: use the platform's mechanism on a chassis that has one (a native menu bar on the desktop), and draw an in-window version only where there is none. A person expects the platform's shape and finds the in-window copy strange, however correct. (Nielsen: consistency and standards.)
  • Every text control honours the platform's editing conventions. Select-all on the platform's modifier (Cmd on macOS, Ctrl elsewhere), key repeat on a held key, word-wise movement, cut / copy / paste. A control that takes typing and drops one of these is a bug, not a wish.
  • A control that cannot complete an action does what it can and says what it could not. Allocating a path with too few points allocates up to the budget and reports how far it got; it does not refuse the whole path. (Nielsen: user control; error prevention over error refusal.)
  • The pointer says what a drag will do. A resizable edge shows a resize cursor along its axis; a movable thing shows a move cursor; a control that will not take the press shows not-allowed. Signifiers precede action.
  • Group by category with a divider between groups, never within. Attributes together, resistances together, pools together; one rule between groups. A list of fifteen rows with no grouping is a table nobody can scan. (Gestalt: proximity and common region.)
  • Names are specific. "Rotation time", not "Time"; "Energy shield", not "Shield". A generic word beside a specific number tells the reader nothing about which number it is.
  • No jargon without its sentence. A hint like "dim = not counted yet" is either a full sentence a newcomer understands or it is not shown.
  • Diagnostics live behind a debug mode. Node ids, mailbox ids, frame counters: useful to whoever is fixing the screen, noise to whoever is using it. A View → Debug toggle, off by default.
  • Notices have one place. Refusals and confirmations appear in a single dedicated region the reader learns once, coloured by severity (info, warning, error), and leave on their own after a few seconds. A message that appears wherever there was room, and stays, is read as part of the screen.
  • Search is a first-class control. When a screen holds more than a reader can scan, the search field is visible without opening anything, at the top of the region it searches.
  • Numeric inputs carry steppers. A number a person adjusts by one gets an up and a down beside the field; typing is the other way in, not the only way.
  • Hover explains. A value with context the reader may not carry (which act a level is, what a stat is, where a number came from) explains itself on hover in a tooltip that is a neat measured box: wrapped at a maximum width, sections divided, never broken mid-thought.
  • A focus ring marks keyboard focus only. A control the reader just clicked does not also grow a box; Tab traversal does show one. (The platform focus-visible rule.)
  • Padding is symmetric. A label centred in its button has the same space on both sides; a control inside a plate sits at least two spacing units from the plate's edge; a glyph used as a control (+, −) is optically centred.
  • Hide what the context disables. A node, start, or option that only applies under a state the build is not in (another class's start, an ascendancy-gated passive) is not drawn until that state holds. Clutter is every element that cannot currently matter.
  • Signify only what is not obvious. A resize cursor belongs on a splitter, where the affordance is hidden; a move cursor over a map that every reader knows pans is noise. A signifier on the default gesture is intrusive, and it dilutes the ones that matter.
  • A control's parts are one element. A number field and its steppers share one frame and one fill; a field and its clear button likewise. Two adjacent boxes read as two controls fighting.
  • Empty collapses. A container with nothing chosen (an inset for an ascendancy that is not picked) starts collapsed and collapses when its content goes; an expanded empty box is a promise the screen is not keeping.
  • One colour, one state, everywhere. The allocated colour appears on allocated things only. A ring drawn in the "taken" gold before anything is taken says the opposite of the truth.
  • A result says why it matched. A search that lights a node shows, on that node, the line that matched; a hit with no visible reason reads as a mistake.
  • Own versus derived, separated. What this node gives and what the path to it costs are two facts; a tooltip that blends them makes the reader do the subtraction.
  • A setting is a control, not a file. If a person can change it, the screen offers the control; the file is the store behind it, never the interface.
  • Labels stay complete under a heading. "Fire resistance", not "Fire" under a "Resistances" heading the reader may not see at the same time.
  • Keep an affordance the reader has learned unless they ask for it to go; when they ask for its look to change, change the look.
  • A box is sized from measured text, never from a character count. A plate, a tooltip, a toast, a list cell: its width comes from the face's real advances (the font metrics aether.render reports), not from characters times an average. Over a proportional face the average is wrong by a glyph every few words, and the text walks out of the box the estimate drew. If the measure is not available yet, draw nothing rather than a guessed box. (Owner, round 3, live: "the text is going outside the bounds of the box.")
  • Buttons are ranked, and only one per region is filled. Material's emphasis ladder — filled, filled tonal, outlined, text — exists because a region of five equally loud verbs has no primary verb. One filled verb per region (the thing the reader came to do); the verbs beside it are tonal or outlined; the ones a reader rarely wants are text. A verb that throws work away — delete, discard, reset — is outlined in the error colour, not filled, so the loudest thing on the screen is never the destructive one. A dialog's action row is one filled confirm and a text cancel. The rank changes the look and nothing about the size or the target. A rank is only a rank if its face reads: measure each one against the surface it stands on (WCAG 1.4.11's 3.0), because a quiet verb whose plate is a fifth of a step off the plate behind it is not quiet, it is invisible — which is a Cancel a reader cannot find on a dialog. (Owner, round 8: "a single yellow button for everything is kinda meh." Owner, round 11: "Cancel button is blending in with the background of the new item modal.")
  • Colour that carries meaning is measured, not judged by eye. Any colour a reader has to see — a control's own face, a name written in its tier's ink, a severity — is picked against a contrast ratio, and against every background it can land on rather than the one it was drawn over while it was being chosen. WCAG 2.2 gives the two numbers: 4.5 for body text, 3.0 for a control's face and for large text. A list row is the case that catches people out — it draws four fills (plain, pointed at, chosen, chosen and pointed at), so an ink chosen on the plain one can be two thirds of the way to invisible on the brightest. Compute the ratio from the two colours' relative luminance, so this is a number in a test rather than an opinion in a review. (Owner, round 11: "Cancel button is blending in with the background of the new item modal.")
  • A vocabulary told apart by colour needs colours, not a colour. When a screen writes a set of tags — damage types, factions, kinds — and draws them all in one ink, the colour is decoration and the reader gets nothing from it; the ink has to be what the word names, mapped in one function so the same tag is the same colour everywhere. Tags are inks on words, never plates: a row of four plates fights the selection for the row it sits in. The colours are measured like every other. (Owner, round 12: "Spell tags are all the same colour regardless of tag.")
  • Controls bound to one row are one block, and the block ends where the row does. Two verbs on a row sit edge to edge with nothing between them and the last one ends on the row's own edge — not on its inner pad, which leaves the last control floating short of the end and reads as a mistake. Touching faces are separated by a hairline where their emphasis does not already separate them. The general form: a group of controls that act on one thing looks like one thing. (Owner, round 12: "the buttons for the skill remove and skill change aren't flush with each other (touching) … and the 'x' button isn't touching the end of the entry.")
  • Generate what can be generated. A name, a summary line, a default: when the data implies one ("Wander of Kinetic Blast Ranger" from weapon, skill, and class), offer it rather than an empty field.

What the engine provides for this method

The engine ships no control set at present; the widget crate that held one was removed and is to be rebuilt. Until then a screen is drawn by its own component:

  • Shapes: plates, rules and control faces are aether.render.draw_shapes boxes, rounded, stroked or shadowed, under an optional clip (Rendering & camera).
  • Text: runs are aether.render.draw_text draws, and aether.render.font_metrics reports real font metrics (FontMetrics, GlyphAdvance), so a screen measures a string instead of counting characters (Text).
  • Input: pointer, key and window-size mail come from a subscription on aether.window (Input).

The type scale, the spacing scale, the colour roles and every control in §3 are therefore the screen's own code, and the review in §7 is how they are checked. The UI face carries no tabular-figure selection, so a numeric column is aligned by layout rather than by the face.

Sources