---
title: 'Text Formatting'
source: 'https://academia.sh/en/courses/css-fundamentals/text-formatting'
course: 'Visual Presentation with CSS'
language: en
updated: '2026-08-17T18:09:19+00:00'
license: 'CC BY-SA 4.0'
---

# Text Formatting

Whitespace processing rules, alignment and indentation, text-transform's dependence on language, and controlling underline decoration.

The font has arrived and the size scale is built. Decisions on the
text itself remain: what happens to whitespace in the source text,
how lines get aligned, how letters get converted, how links get
underlined.

The first of these decisions is the most surprising one. In the
document written in the Web Fundamentals and HTML curriculum, the
text was written indented, and the indentation did not show up on
screen. This lesson shows the reason for that, with a rule and a
calculation.

## Whitespace Processing

In the default behavior, **consecutive whitespace characters in the
source text get collapsed into a single space**, and line breaks
count as a space character too. This is what makes the HTML document
formattable: indenting, line-splitting, and alignment happen freely
in the source text without affecting the output.

The rule gets changed with the `white-space` property. The program
below applies five values to the same source text.

```js
// whitespace.mjs — how white-space values process the same source text
const SOURCE = "North   Slope\n  Station\tmeasurement   readings";

const BEHAVIOR = {
  "normal":   { collapseSpaces: true,  preserveBreaks: false, wrap: true  },
  "nowrap":   { collapseSpaces: true,  preserveBreaks: false, wrap: false },
  "pre":      { collapseSpaces: false, preserveBreaks: true,  wrap: false },
  "pre-wrap": { collapseSpaces: false, preserveBreaks: true,  wrap: true  },
  "pre-line": { collapseSpaces: true,  preserveBreaks: true,  wrap: true  },
};

function process(text, rule) {
  let s = text;
  if (rule.preserveBreaks) {
    if (rule.collapseSpaces) s = s.split("\n").map((p) => p.replace(/[ \t]+/g, " ").trim()).join("\n");
  } else {
    s = s.replace(/\s+/g, " ").trim();
  }
  return s;
}

console.log("source (with escape sequences):");
console.log(JSON.stringify(SOURCE));
console.log("");
for (const [name, rule] of Object.entries(BEHAVIOR)) {
  const result = process(SOURCE, rule);
  console.log(`${name.padEnd(9)} wrap=${String(rule.wrap).padEnd(5)} -> ${JSON.stringify(result)}`);
}

console.log("\n--- text-transform: the Turkish special case ---");
const sample = "istasyon ILIK ışık";
console.log(`source            : ${sample}`);
console.log(`uppercase (en)    : ${sample.toLocaleUpperCase("en-US")}`);
console.log(`uppercase (tr)    : ${sample.toLocaleUpperCase("tr-TR")}`);
console.log(`lowercase (en)    : ${sample.toLocaleLowerCase("en-US")}`);
console.log(`lowercase (tr)    : ${sample.toLocaleLowerCase("tr-TR")}`);

console.log("\n--- text-indent and alignment effect (60-character box) ---");
const BOX = 60;
const line = "Station sits at 1840 meters above sea level";
for (const [name, align] of [["start", (s) => s.padEnd(BOX)], ["end", (s) => s.padStart(BOX)], ["center", (s) => s.padStart((BOX + s.length) / 2).padEnd(BOX)]]) {
  console.log(`${name.padEnd(7)}|${align(line)}|`);
}
```

```
source (with escape sequences):
"North   Slope\n  Station\tmeasurement   readings"

normal    wrap=true  -> "North Slope Station measurement readings"
nowrap    wrap=false -> "North Slope Station measurement readings"
pre       wrap=false -> "North   Slope\n  Station\tmeasurement   readings"
pre-wrap  wrap=true  -> "North   Slope\n  Station\tmeasurement   readings"
pre-line  wrap=true  -> "North Slope\nStation measurement readings"

--- text-transform: the Turkish special case ---
source            : istasyon ILIK ışık
uppercase (en)    : ISTASYON ILIK IŞIK
uppercase (tr)    : İSTASYON ILIK IŞIK
lowercase (en)    : istasyon ilik ışık
lowercase (tr)    : istasyon ılık ışık

--- text-indent and alignment effect (60-character box) ---
start  |Station sits at 1840 meters above sea level                 |
end    |                 Station sits at 1840 meters above sea level|
center |        Station sits at 1840 meters above sea level         |
```

The output distinguishes two axes: whether whitespace gets collapsed,
and whether line-splitting gets allowed.

`normal` — consecutive spaces collapse to one, line breaks count as a
space, the line gets split to fit the box. The three spaces and the
tab character in the source text turned into a single space.

`nowrap` — does the same whitespace processing but does not allow
line-splitting. The text overflows the box; the situation established
in the Overflow Management lesson shows up.

`pre` — changes nothing: whitespace and line breaks are kept exactly
as they are, and no automatic line-splitting happens. This is the
browser's default style for the `pre` element.

`pre-wrap` — keeps whitespace and line breaks but splits lines that
do not fit the box. This value gets written instead of `pre` when
long lines need showing without overflowing.

`pre-line` — collapses whitespace but **keeps line breaks**. It gets
used to show user text coming from a text field with its paragraph
structure.

## Letter Conversion Depends on Language

The output's second block shows why the `text-transform` property
cannot be considered a purely visual conversion.

The word `istasyon` becomes `ISTASYON` when uppercased by English
rules, and `İSTASYON` when uppercased by Turkish rules. The word
`ILIK` becomes `ilik` when lowercased by English rules, and `ılık` by
Turkish rules — two different words.

The reason is that in the Turkish alphabet, `i` and `ı` are separate
letters, and their uppercase counterparts are `İ` and `I`. The code
point distinction from the How Computers Work curriculum turns into a
difference in meaning here.

This shows that writing `text-transform` depends on the document's
`lang` declaration. In a document where `lang="tr"` has not been
declared on the root element, converting to uppercase can give a
wrong result.

A second warning: the conversion happens only in drawing. The text in
the document does not change; copied text comes in its source form,
and a screen reader reads its source form. If a heading needs to
appear in uppercase, this is a visual decision and gets made in the
style sheet; the text itself should stay in its ordinary spelling in
the source.

## Alignment

`text-align` takes five basic values: `start`, `end`, `center`,
`justify`, and their physical counterparts `left` and `right`. The
logical values follow the writing direction, and that is the form
used in this course.

The output's third block shows three alignments in a fixed-width box.
The decision gets made by three criteria.

**Centering** works for short text; in long paragraphs, each line's
starting point shifts, making it harder for the eye to find the start
of a line. It suits headings and short captions, not body text.

**Justification** (`justify`) makes every line the same length by
widening the word spacing. In narrow columns, the spacing widens
excessively and vertical white streaks form inside the text. The
result is more balanced when used together with `hyphens: auto`, but
left alignment still gets preferred in narrow columns.

**Indentation** (`text-indent`) pulls paragraph starts inward. If
vertical spacing exists between paragraphs, indentation is
unnecessary; when both get used together, the paragraph break gets
marked twice. In print tradition, indentation stands in for spacing.

## Text Decoration

`text-decoration` is a shorthand and sets four longhands: `-line`
(where the line sits), `-color`, `-style`, `-thickness`.

Links being underlined comes from the browser default and is not a
visual decoration: marking a link within text with color alone makes
the link invisible to a reader who cannot perceive color
distinctions. The underline should not get removed unless another
visual distinguisher gets put in its place.

The underline's legibility gets adjusted with `text-underline-offset`
and `text-decoration-skip-ink`; the second ensures the line does not
cut through letters' descenders.

```css
/* station.css — step 20: text formatting */
.measurement-table th {
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: var(--font-small);
}

.measurement-raw-data {
  white-space: pre-wrap;
  font-family: ui-monospace, monospace;
  tab-size: 2;
}

.observation-note { white-space: pre-line; }

main a {
  text-decoration-line: underline;
  text-underline-offset: 0.15em;
  text-decoration-thickness: 1px;
  text-decoration-skip-ink: auto;
}

main a:hover { text-decoration-thickness: 2px; }
```

The `.measurement-raw-data` class shows the raw measurement record
coming from the station: line breaks and alignment spaces carry
meaning, so they get preserved; writing `pre-wrap` instead of `pre`
makes long lines split instead of overflow.

The letter spacing given to the header cells is a standard correction
for uppercase-transformed text: capital letters look more cramped
than lowercase, and reading gets harder if the spacing does not
widen.

## Summary

- In the default whitespace processing, consecutive spaces collapse
  to one and line breaks count as a space; the source text's
  indentability rests on this.
- `white-space` sets two axes at once: whether whitespace gets
  collapsed, and whether line-splitting gets allowed. `pre-wrap`
  keeps whitespace and splits the line, `pre-line` collapses
  whitespace and keeps line breaks.
- `text-transform` depends on language; because `i` and `ı` have
  separate uppercase counterparts in Turkish, a wrong conversion can
  result without a `lang` declaration.
- The conversion happens only in drawing; the text in the document
  does not change, and comes in its source form when copied.
- A link's underline is not a decoration, it is a color-independent
  distinguisher; if it gets removed, another visual marker has to get
  put in its place.

## Next Step

The text decisions are complete. The back of the boxes has not been
looked at yet: a box's background can be not just a color, but a
positionable and repeatable image too. The next lesson takes up
background layers, positioning and repetition rules, and which region
the background extends to.
