---
title: 'Balance and Alignment'
source: 'https://academia.sh/en/courses/interface-fundamentals/balance-and-alignment'
course: 'Fundamentals of Interface Design'
language: en
updated: '2026-08-17T18:11:08+00:00'
license: 'CC BY-SA 4.0'
---

# Balance and Alignment

Defining alignment as a shared invisible axis; computing the number of alignment axes and area-weighted horizontal balance, and measuring optical alignment and the ragged right edge.

Up to this point, every decision was made through individual elements: how large this
element is, how close these two are, how heavy this button is. What holds a screen
together, though, is not the elements themselves but the invisible lines between them.
If the filter panel's left edge and the page title's left edge line up on the catalog
page, a relationship is established even though no line exists between them.

This lesson ties three questions to a number: how many alignment axes a layout has, how
weight is distributed along the horizontal axis of the screen, and how irregular a text
block's right edge is.

## Alignment Is a Shared Axis

**Alignment** is multiple elements sharing the same invisible line. That line can be the
elements' left edge, right edge, vertical center axis, or text baseline. The term also
appeared in the How Computers Work course, in the sense of memory addresses landing on a
word boundary; here it refers to a visual axis, and the only thing the two uses share is
the idea of "landing on a common boundary."

Alignment's function is to establish a relationship. The proximity principle spends
space to bind elements together; alignment does the same job between distant elements
while spending nothing. If the page title at the very top of the screen and a footnote
at the very bottom sit on the same left axis, they read as belonging to the same column
even with 500 pixels between them.

In exchange, alignment produces a cost: **every new axis is one more rule.** Every
distinct left edge on screen is one more starting point the reader has to learn. The
axis count is therefore a directly measurable property of a layout.

## Axis Count and Horizontal Balance

The program below takes the blocks on the catalog results page, counts how many
distinct left and right axes are used, and computes the layout's **area-weighted
horizontal center**. The second measure gives balance: each block's area is treated as
weight, the blocks' shared center of weight is found, and its deviation from the
container's center axis is measured.

```js
// 04-alignment.mjs — alignment axis count and area-weighted horizontal balance

const CONTAINER = 1200;

// Blocks on the catalog results page: (name, left edge, width, height)
const before = [
  { name: "page title", x: 24, w: 600, h: 40 },
  { name: "search field", x: 32, w: 520, h: 44 },
  { name: "filter panel", x: 24, w: 264, h: 480 },
  { name: "filter heading", x: 40, w: 232, h: 24 },
  { name: "results list", x: 312, w: 600, h: 480 },
  { name: "record title", x: 328, w: 560, h: 24 },
  { name: "record metadata", x: 336, w: 540, h: 18 },
];

const after = [
  { name: "page title", x: 24, w: 1152, h: 40 },
  { name: "search field", x: 24, w: 1152, h: 44 },
  { name: "filter panel", x: 24, w: 264, h: 480 },
  { name: "filter heading", x: 24, w: 264, h: 24 },
  { name: "results list", x: 312, w: 864, h: 480 },
  { name: "record title", x: 312, w: 864, h: 24 },
  { name: "record metadata", x: 312, w: 864, h: 18 },
];

function axes(blocks) {
  const left = new Set(blocks.map((b) => b.x));
  const right = new Set(blocks.map((b) => b.x + b.w));
  return { left: [...left].sort((a, b) => a - b), right: [...right].sort((a, b) => a - b) };
}

function balance(blocks) {
  const totalArea = blocks.reduce((t, b) => t + b.w * b.h, 0);
  const moment = blocks.reduce((t, b) => t + b.w * b.h * (b.x + b.w / 2), 0);
  const center = moment / totalArea;
  const deviation = center - CONTAINER / 2;
  return { center, deviation, ratio: (100 * Math.abs(deviation)) / (CONTAINER / 2) };
}

function report(name, blocks) {
  const a = axes(blocks);
  const b = balance(blocks);
  console.log(name);
  console.log(`  left axes (${a.left.length}): ${a.left.join(", ")}`);
  console.log(`  right axes (${a.right.length}): ${a.right.join(", ")}`);
  console.log(`  total axis count: ${a.left.length + a.right.length}`);
  console.log(
    `  area-weighted center: ${b.center.toFixed(1)}px   container center: ${CONTAINER / 2}px`
  );
  console.log(`  deviation: ${b.deviation.toFixed(1)}px   ${b.ratio.toFixed(1)}% of half-width`);
  console.log("");
}

report("before", before);
report("after", after);
```

```
before
  left axes (6): 24, 32, 40, 312, 328, 336
  right axes (7): 272, 288, 552, 624, 876, 888, 912
  total axis count: 13
  area-weighted center: 459.7px   container center: 600px
  deviation: -140.3px   23.4% of half-width

after
  left axes (2): 24, 312
  right axes (2): 288, 1176
  total axis count: 4
  area-weighted center: 608.6px   container center: 600px
  deviation: 8.6px   1.4% of half-width
```

The "before" layout looks flawless when examined element by element: every block sits
neatly inside its own container, the internal spacing is reasonable, nothing overflows.
The number says something different. Seven blocks use thirteen distinct axes; the left
edges start at 24, 32, 40, 312, 328, and 336 pixels. None of these values is wrong on
its own; the problem is that all of them exist **at the same time**. An 8-pixel
difference is not perceived as alignment, and it is not perceived as deliberate
indentation either; it just blurs the edge.

The "after" layout carries the same information on four axes. The record title now
aligns with the results list's left axis rather than with its own container's internal
spacing. The internal spacing has not disappeared; it moved outside the list, between
the blocks.

The second measure also shows another flaw in the first layout. The area-weighted
center sits at 459.7 pixels, 140.3 pixels left of the container's center axis. There is
a 288-pixel-wide strip of empty space on the right, and that strip serves no function.
When the results list is widened to the container's right edge, the deviation drops to
8.6 pixels.

## Zero Deviation Is Not the Goal

The balance measure's purpose is not to impose symmetry. A symmetric layout is balanced,
but not every balanced layout is symmetric: an asymmetric layout with a narrow, tall
panel on the left and a wide, short block on the right can also produce a deviation
close to zero.

The measure's function is to catch **unintended** imbalance. A 23 percent deviation is
usually not a decision but a leftover: content was not extended to the right, the
container's width changed, but the blocks were never updated. The criterion used
throughout this course is: **a deviation exceeding 10 percent of the half-width requires
a justification.** If a justification exists, the deviation stays — a reading column may
be deliberately pushed left, or the space on the right may be deliberately reserved. If
no justification exists, it is corrected.

## Optical Alignment

Mathematical alignment does not always produce perceived alignment. Shapes with edges
that are not straight look misaligned when their boxes are aligned. Correcting this
difference is called **optical alignment**.

There are three common cases. The first is round shapes: a circle looks smaller when it
sits in the same box as a square, because its edge touches the box at only four points.
The second is punctuation: a line beginning with a quotation mark looks indented by one
step, because of the open space inside the mark. The third is triangular shapes: a
triangle pointing right looks shifted left when centered exactly in its box, because its
mass is concentrated at its base.

What these three cases share is that the fix is not **immeasurable** but
**uncomputable**: the difference is real, but it is specific to the shape and has no
general formula. The applicable rule is this: an optical correction is applied only to
shapes with edges that are not straight, only at large sizes, and only with the amount
of the shift documented. Undocumented optical corrections are the most common source of
the consistency problem taken up in the next lesson: nobody knows why that 2 pixels is
there, and nobody dares to remove it.

## The Right Edge of Text

A text block's left edge is an axis; its right edge is an irregular profile produced by
the line lengths. This profile is called the **ragged right edge**, and the degree of
its irregularity can be measured.

The program below wraps a paragraph at three different line lengths, extracts the
distribution of the line lengths, and computes how far the word gaps would stretch if
**justification** were applied. The measurement uses a fixed-width assumption: each
character is taken as 8 pixels, normal word spacing as 4 pixels.

```js
// 04-ragged-right.mjs — depth of the ragged right edge and the spacing cost of justification

const text =
  "A catalog record's detail view brings together the book's title, the author's " +
  "name, the publication year, the shelf code, and whether the book is currently " +
  "on the shelf, all within one single screen. A reader arrives at this screen " +
  "asking two separate questions: is this the exact book being sought, and can " +
  "it be borrowed right now today. The layout of the view answers both apart.";

// Fixed-width measurement: every character is assumed to have equal width.
function wrap(text, measure) {
  const words = text.split(" ");
  const lines = [];
  let current = "";
  for (const w of words) {
    const candidate = current ? `${current} ${w}` : w;
    if (candidate.length <= measure) current = candidate;
    else {
      lines.push(current);
      current = w;
    }
  }
  if (current) lines.push(current);
  return lines;
}

const CHARACTER_WIDTH = 8; // average character width in 16px body text
const NORMAL_SPACE = 4; // 0.25em word spacing

function measureColumn(measure) {
  const lines = wrap(text, measure);
  const bodyLines = lines.slice(0, -1); // the last line is not part of the rag calculation
  const lengths = bodyLines.map((s) => s.length);
  const average = lengths.reduce((t, u) => t + u, 0) / lengths.length;
  const deviation = Math.sqrt(
    lengths.reduce((t, u) => t + (u - average) ** 2, 0) / lengths.length
  );
  const shortest = Math.min(...lengths);
  const ragDepth = measure - shortest;

  // Justification: the missing width is distributed across the word gaps.
  const stretches = bodyLines.map((s) => {
    const gaps = s.split(" ").length - 1;
    const shortfall = (measure - s.length) * CHARACTER_WIDTH;
    return gaps > 0 ? shortfall / gaps : 0;
  });
  const maxStretch = Math.max(...stretches);

  console.log(`measure ${measure} characters  (${measure * CHARACTER_WIDTH}px)`);
  console.log(`  line count: ${lines.length}  (in rag calculation: ${bodyLines.length})`);
  console.log(`  line lengths: ${lengths.join(", ")}`);
  console.log(`  average ${average.toFixed(1)}  standard deviation ${deviation.toFixed(2)}`);
  console.log(`  rag depth: ${ragDepth} characters  (${((100 * ragDepth) / measure).toFixed(1)}% of the measure)`);
  console.log(
    `  largest word gap when justified: ${(NORMAL_SPACE + maxStretch).toFixed(1)}px  ` +
      `(${((NORMAL_SPACE + maxStretch) / NORMAL_SPACE).toFixed(2)} times normal)`
  );
  console.log("");
}

measureColumn(45);
measureColumn(68);
measureColumn(92);
```

```
measure 45 characters  (360px)
  line count: 10  (in rag calculation: 9)
  line lengths: 37, 45, 41, 43, 38, 42, 39, 40, 42
  average 40.8  standard deviation 2.39
  rag depth: 8 characters  (17.8% of the measure)
  largest word gap when justified: 16.8px  (4.20 times normal)

measure 68 characters  (544px)
  line count: 6  (in rag calculation: 5)
  line lengths: 68, 68, 63, 68, 68
  average 67.0  standard deviation 2.00
  rag depth: 5 characters  (7.4% of the measure)
  largest word gap when justified: 7.6px  (1.91 times normal)

measure 92 characters  (736px)
  line count: 5  (in rag calculation: 4)
  line lengths: 87, 92, 89, 91
  average 89.8  standard deviation 1.92
  rag depth: 5 characters  (5.4% of the measure)
  largest word gap when justified: 7.1px  (1.77 times normal)
```

The narrow column's right edge is deep: one line ends at 37 characters, leaving 17.8
percent of the measure empty. Here the right edge is not an axis but a jagged profile.

Justification promises to flatten this profile, but it pays for that with word spacing.
In the narrow column, the widest word gap grows to 4.20 times normal. When word gaps
exceed letter spacing, the gaps stack up between lines into vertical channels inside the
text, and reading no longer advances along the line but jumps between the gaps.

This ties a design decision to a number: justification is not applied unless the line
length is large enough. The measurement brings the stretch down to 1.77 times in the
92-character column; it climbs to 4.20 times in the 45-character column. The description
text in the catalog interface's filter panel lives in narrow columns, so the ragged
right edge is kept. The same calculation has a flip side: if the rag depth exceeds a
quarter of the measure, the problem is not a lack of justification but a column that is
too narrow.

## Summary

- Alignment is an invisible axis shared by elements; it establishes a relationship
  between distant elements without spending space, and in exchange every new axis loads
  one more rule onto the reader.
- Axis count is a directly countable layout property; in the measured layout, seven
  blocks were brought down from thirteen axes to four, and no information was lost.
- The area-weighted horizontal center's deviation from the container's center measures
  balance; a deviation exceeding 10 percent of the half-width requires a justification,
  but zero deviation is not the goal.
- Optical alignment is needed only for shapes with edges that are not straight, has no
  formula, and when applied, the amount of the shift is documented.
- The depth of a ragged right edge can be measured; if the rag depth exceeds a quarter
  of the line length, the column is too narrow.
- Justification stretches a narrow column's word gaps to several times normal — 4.20
  times in the case measured here; it is this stretch ratio, not aesthetics, that
  determines the decision.

## Next Step

This lesson measured a single screen's internal consistency. The real question sits
between screens: what is lost when a gap that is 8 pixels on the catalog interface's
search page becomes 10 pixels on the record detail page? The next lesson treats
repetition as the source of learnability, extracts an interface's decision inventory,
and counts how many different values are used to do the same job.
