Visual Design Foundations

Hierarchy, layout, spacing, type and colour: the raw material of every interface surface.
Author

Benedict Thekkel

Visual design in an interface is not decoration. It is the mechanism by which a reader knows what matters, what belongs together, and where to look next, before reading a single word. A screen with correct content and no hierarchy is genuinely harder to use than a plain one with hierarchy, because the reader has to construct the structure themselves every time they look at it.

Five variables do nearly all of the work:

Variable Controls
Size Importance. The strongest and bluntest signal available
Weight Emphasis without changing the space a thing occupies
Colour Category and state, never meaning on its own
Space Grouping. The most underused variable in most interfaces
Position Reading order and relative priority

The sections below take these in the order you would actually apply them: hierarchy first, then the grid that positions things, then the spacing scale, then type, then colour.


1. Visual hierarchy

Before anything else, decide the order in which a first-time viewer should notice things. There should be one clear first place for the eye to land per screen, and a deliberate second and third.

NO HIERARCHY HIERARCHY Monthly usage report Your usage rose 12% this month Compared to the same month last year View the full breakdown Download CSV Manage alert thresholds Usage up 12% Monthly report, October 2026 Compared with October 2025 View breakdown Download CSV Manage alert thresholds

Both panels contain the same six pieces of information. The right one has decided which matters.

The technique is contrast, and contrast requires difference. Two headings at 17 px and 19 px do not read as two levels, they read as an inconsistency. If two things have different importance, make the difference obvious; if they have the same importance, make them identical.

Squint at it. Blur the screen, or look at it from across the room. Whatever still stands out is your real hierarchy, and it is frequently not the one you intended. This is the cheapest visual design test available.

One primary action per view. Three buttons of equal weight is the same as no primary action. Primary gets the filled treatment, secondary gets an outline or a plain text button, tertiary goes in a menu.


2. Layout and grids

A grid exists so that alignment decisions are made once instead of per element. The 12-column grid is the common default because 12 divides by 2, 3, 4 and 6, giving halves, thirds, quarters and sixths without fractional widths.

12 columns, 8 px gutters, fixed margins span 12 span 8 span 4 4 4 4

In CSS you rarely need a column framework any more. Grid and flexbox express the intent directly:

.page {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 1rem;
  max-width: 72rem;       /* a measure cap, not a device width */
  margin-inline: auto;
  padding-inline: 1rem;   /* the gutter that keeps text off the edge */
}
.main  { grid-column: span 8; }
.aside { grid-column: span 4; }

/* Auto-fitting cards need no breakpoints at all. */
.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1rem;
}

Note minmax(0, 1fr) rather than 1fr: without it, a long unbreakable string can force a column wider than its share and blow out the layout.

Alignment is the cheapest quality signal there is. Fewer alignment edges reads as more deliberate. Pick a left edge and hold it down the whole page; the eye notices a 3 px inconsistency without being able to name it.

Cap the content width, not the viewport. A text column should stop at a readable measure regardless of how wide the window is. See section 4.


3. Spacing

Spacing is how grouping is communicated, and it is the variable most often left to chance. The mechanism is the Gestalt principle of proximity: things near each other are read as related, whatever the borders say.

EVEN SPACING: pairs unclear PROXIMITY: pairs obvious Site name Meter ID Site name Meter ID 16 16 6 24

The rule: space within a group must be smaller than space between groups. A label 6 px from its field and 24 px from the next pair needs no lines, boxes or shading to be understood. If you find yourself adding borders to clarify grouping, the spacing is usually wrong.

Use a scale, not arbitrary numbers. A 4 px base with doubling steps is the common choice, since it lands on whole pixels at every zoom level and gives roughly the right granularity:

:root {
  --space-1: 0.25rem;  /*  4px  icon to label            */
  --space-2: 0.5rem;   /*  8px  inside a control          */
  --space-3: 0.75rem;  /* 12px  label to field            */
  --space-4: 1rem;     /* 16px  between fields            */
  --space-6: 1.5rem;   /* 24px  between field groups      */
  --space-8: 2rem;     /* 32px  between card sections     */
  --space-12: 3rem;    /* 48px  between page sections     */
  --space-16: 4rem;    /* 64px  major page divisions      */
}

Two habits that follow from a scale:

  • Only ever use values from it. The moment a 13 px gap appears, the system stops guaranteeing rhythm, and the next person copies the 13.
  • Apply space in one direction. Margin-bottom on stacked elements, or better, gap on the container. Mixing top and bottom margins makes collapsing margins your problem and every gap becomes a subtraction puzzle.

White space is not wasted space. The most common note on a first draft interface is that everything needs more room. Density is occasionally correct, for a trading screen or a log viewer, but it should be a decision you can defend rather than the result of filling the viewport.


4. Typography

Most interface text is set by four decisions: the family, the scale, the measure, and the line height.

A type scale gives you distinguishable sizes. Pick a base of 16 px, because that is the browser default and shrinking it fights the user’s own settings, then generate steps by a ratio. 1.25 (major third) is a safe interface ratio; 1.2 is tighter for dense applications; 1.333 and above suits marketing pages.

12 / 16 caption, table meta 14 / 20 secondary body, labels 16 / 24 body, the default 20 / 28 h3 25 / 32 h2 31 / 40 h1
:root {
  --text-xs: 0.75rem;    /* 12px */
  --text-sm: 0.875rem;   /* 14px */
  --text-base: 1rem;     /* 16px */
  --text-lg: 1.25rem;    /* 20px */
  --text-xl: 1.5625rem;  /* 25px */
  --text-2xl: 1.9375rem; /* 31px */
}

Measure: 45 to 75 characters per line, with 66 as a good target. Beyond about 80 the eye loses the line when it returns to the left edge; below about 40 the rhythm breaks. Set it in ch units so it tracks the font rather than guessing pixel widths:

.prose { max-width: 66ch; }

Line height scales inversely with size. Body text wants 1.5; a large heading wants 1.1 to 1.25, because at that size generous leading pulls the words apart. Set line height unitless so it inherits proportionally.

Weight and case.

  • Two or three weights are enough: regular for body, semibold or bold for headings, plus one more if you need it. Loading six weights costs real download time for a distinction nobody notices.
  • Avoid all caps for anything longer than a couple of words. Uppercase removes the word shapes that fast reading depends on, which measurably slows reading.
  • Light weights below 300 at small sizes fail contrast in practice even when the colour maths passes, because thin strokes carry less ink.

Alignment. Left-aligned (or start-aligned, which handles right-to-left correctly) for body text. Centre only short runs such as a heading or an empty-state message. Justified text creates rivers of white space in a browser, which lacks proper hyphenation control, so avoid it.

Numbers in tables need tabular figures, or the columns will not line up as the digits change:

.metric { font-variant-numeric: tabular-nums; }

Use a system font stack unless a custom face earns its cost. It renders instantly, matches the platform, and covers every script the user has installed.

body {
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

5. Colour

The mistake with colour is treating it as a palette of pretty values. Treat it as a set of roles instead, each with a defined job, and pick values that satisfy those jobs.

Role Job
Surface / background The ground everything sits on, usually two or three levels
Text Primary, secondary, disabled: a small ramp, not one colour
Border Separation where space alone is not enough
Accent / primary One colour, for the primary action and active state
Semantic Success, warning, danger, info. Fixed meanings, used nowhere else

Neutrals do most of the work. A good interface is largely greys, with colour reserved for meaning. If everything is coloured, colour signals nothing. The 60-30-10 guideline is a reasonable starting ratio: 60 percent dominant neutral, 30 percent secondary, 10 percent accent.

Build a ramp, not a swatch. One hue at nine or ten lightness steps gives you hover states, borders, and tints without inventing new colours:

50500950

Use a perceptual colour space for the maths. HSL lies about lightness: hsl(60 100% 50%) (yellow) and hsl(240 100% 50%) (blue) claim the same 50 percent and are wildly different to the eye. OKLCH fixes this, so a ramp built in OKLCH has steps that actually look evenly spaced, and two hues at the same lightness really match.

:root {
  /* lightness, chroma, hue: evenly stepped and predictable */
  --accent-400: oklch(70% 0.14 250);
  --accent-500: oklch(62% 0.16 250);
  --accent-600: oklch(54% 0.17 250);
}

Colour must never be the only carrier of meaning. Roughly 1 in 12 men has a colour vision deficiency, and red-green is the common one, which is exactly the pair most status indicators use. Add an icon, a shape, a label or a pattern. This is a WCAG requirement and it is also just better design, since it survives a greyscale printout and a bright afternoon on a phone. Details in Accessibility.

Contrast minimums (WCAG 2.2 AA): 4.5:1 for normal text, 3:1 for text at 24 px or 19 px bold and above, 3:1 for the visible boundary of interactive components and meaningful graphics. Check with a contrast tool rather than by eye, since eye judgement of contrast is unreliable.

Design light and dark together, not dark as an afterthought. Dark mode is not inverted light mode. Saturated colours vibrate on dark grounds and need to be desaturated; pure black backgrounds with pure white text produce halation, so use a very dark grey with a slightly dimmed white; and shadows stop working as elevation cues, so depth has to come from surface lightness instead. Tokens make this a table of values rather than a second stylesheet, which is the subject of the next notebook.


6. Gestalt principles

The rules by which the visual system groups things before conscious reading. Five of them matter daily.

Proximity Similarity Common region Continuity Closure
Principle Statement Interface use
Proximity Near things are related The primary grouping tool. See section 3
Similarity Alike things are related Same styling implies same kind. So do not style two different kinds identically
Common region A shared enclosure groups Cards and panels. Weaker than proximity, so do not rely on a border to fix bad spacing
Continuity The eye follows a line Alignment along an edge creates an implied path down the page
Closure The eye completes a shape Why a partly visible row communicates “scroll for more”

Two corollaries worth stating outright:

  • Similarity cuts both ways. If two elements look the same, users will expect them to behave the same. A clickable card next to a non-clickable card in the same style is a design bug, not a user error.
  • Proximity beats a border. When spacing and enclosure disagree, spacing wins. That is why a form with even spacing inside a box is still ambiguous.

7. Elevation, depth and borders

Depth answers “what is on top of what”, which matters for menus, dialogs, sticky headers and drag operations.

Three tools, in order of subtlety: background lightness, border, shadow. Use the least that works.

:root {
  --shadow-1: 0 1px 2px rgb(0 0 0 / .06);                        /* resting card    */
  --shadow-2: 0 2px 8px rgb(0 0 0 / .08);                        /* hovered, raised */
  --shadow-3: 0 8px 24px rgb(0 0 0 / .12);                       /* dropdown        */
  --shadow-4: 0 16px 48px rgb(0 0 0 / .18);                      /* modal           */
}

Keep a consistent light source. If one shadow is offset downward, all of them are. Mixed shadow directions read as sloppiness even to people who cannot say why.

A shadow is a semantic signal, not a decoration. Reserve the deeper levels for things that genuinely float and are dismissible. A static card with a modal-sized shadow makes the whole hierarchy unreadable.

Elevation does not translate to dark mode. A black shadow on a near-black surface is invisible, so depth in dark themes comes from making the raised surface lighter than its ground. That is one more reason to express elevation as a token with a per-theme value rather than a hard-coded shadow.

Prefer a border to a shadow for anything static, and prefer a background change to a border where you can get away with it. Borders at full strength box the page in; a border at low contrast (a neutral-200 rather than neutral-400) separates without shouting.


8. Icons

Icons are a compression format. They save space and speed recognition for concepts the user already knows, and they hide functionality for everything else.

  • Use one icon set. Mixed stroke weights and corner radii between sets are immediately visible and look like a mistake. Pick one (Lucide, Phosphor, Material Symbols, Heroicons) and stay in it.
  • Label anything ambiguous. Only a handful of icons are genuinely universal: close, search, print, plus a few platform conventions. A three-dot menu, a bookmark, a share glyph and anything domain-specific need words. Icon-only toolbars are a recognised usability problem, not a modern aesthetic.
  • If it must be icon-only, give it an accessible name and a tooltip. aria-label for assistive technology and a visible tooltip on hover and focus for everyone else.
  • Match the optical size to the text, typically 1 em to 1.25 em beside body text, and align to the cap height rather than the line box.
  • Prefer inline SVG for anything that must inherit colour or change with state. currentColor makes an icon follow its text automatically, which is why every SVG in this notebook uses it.
  • Decorative icons get aria-hidden="true", so a screen reader does not announce a meaningless graphic next to the label it duplicates.
<!-- Icon plus text: the icon is decorative, the text is the name -->
<button><svg aria-hidden="true" width="16" height="16">...</svg> Delete site</button>

<!-- Icon only: the icon must carry the name -->
<button aria-label="Delete site" title="Delete site"><svg aria-hidden="true">...</svg></button>

SVG covers the format itself in depth.


9. How visual design fails

  • No hierarchy. Everything at 14 px in the same grey. Squint at the screen and nothing emerges.
  • Too many type sizes and weights, usually because each screen was designed alone. Six sizes that differ by 1 px each communicate less than three that differ clearly.
  • Arbitrary spacing. 13 px here, 18 px there, and grouping that has to be rescued with borders.
  • Colour as decoration. Accent colour on six elements, so the primary action no longer stands out.
  • Colour as the only signal. Red and green status dots with no icon or label.
  • Contrast traded for aesthetics. Light grey placeholder text on white is the most common accessibility failure in visual design, and it fails for everyone on a phone outdoors, not only for people with low vision.
  • Shadows everywhere. When every element floats, nothing does.
  • Centred body text, which forces the eye to find a new left edge on every line.
  • Full-width text on a large monitor. A 200-character line is unreadable no matter how good the type is.
  • Dark mode by inversion, producing vibrating saturated colours and invisible elevation.

Where this goes next

On the implementation side: CSS for the properties, Bootstrap for a ready-made grid and component set, and SVG for vector assets and icons.


Back to top