Foundations

Text Color System

Text colors are designed for high legibility, long session comfort, and clear hierarchy. Towbook ships paired token sets for dark surfaces (the default) and light surfaces — each tuned for AA+ contrast in its own context.

Use the smallest number of text levels possible — too many shades make a layout feel busy.

On Dark Surface

Primary heading

Secondary heading

Body copy sits a step quieter than headlines, but stays comfortable to read for long stretches.

Tertiary — captions and metadata.

Disabled — inactive elements only.

On Light Surface

Primary heading

Secondary heading

Body copy sits a step quieter than headlines, but stays comfortable to read for long stretches.

Tertiary — captions and metadata.

Disabled — inactive elements only.

Token pairs

Every level has a dark-surface and light-surface value. Pick the pair that matches the surface you're on — never mix.

Primary Text

Headlines, primary copy, and any text that needs to feel certain.
On Dark
Primary Text
class
text-primary
var
--color-primary
hex
#FFFFFF
On Light
Primary Text
class
text-primary-light
var
--color-primary-light
hex
#050608

Secondary Text

Supporting copy, descriptions, and secondary information.
On Dark
Secondary Text
class
text-secondary
var
--color-secondary
hex
#C7CCD4
On Light
Secondary Text
class
text-secondary-light
var
--color-secondary-light
hex
#3A4250

Tertiary / Muted Text

Captions, metadata, timestamps, and helper text.
On Dark
Tertiary / Muted Text
class
text-tertiary
var
--color-tertiary
hex
#8A93A1
On Light
Tertiary / Muted Text
class
text-tertiary-light
var
--color-tertiary-light
hex
#6A7384

Disabled / Hint

Disabled states and very low-priority labels.
On Dark
Disabled / Hint
class
text-disabled
var
--color-disabled
hex
#5C6573
On Light
Disabled / Hint
class
text-disabled-light
var
--color-disabled-light
hex
#9099A6

In context

The same content rendered on both surfaces. Notice how each token shifts to maintain the same perceived hierarchy.

Dark · Card

Invoice #INV-1042

$1,240.00

Smith Towing · 4 services billed

Created Apr 28, 2026 · Due May 12

Light · Card

Invoice #INV-1042

$1,240.00

Smith Towing · 4 services billed

Created Apr 28, 2026 · Due May 12

Using these on the web

Every token is exposed as a Tailwind utility class and a CSS custom property. Reach for the utility class first; drop to the variable only when you're outside Tailwind (vanilla CSS, inline styles, third-party widgets).

Tailwind utilities
<!-- on dark surfaces -->
<h1 class="text-primary">Heading</h1>
<p  class="text-secondary">Body copy</p>
<small class="text-tertiary">Caption</small>
<button class="text-disabled" disabled>Off</button>

<!-- on light surfaces -->
<h1 class="text-primary-light">Heading</h1>
<p  class="text-secondary-light">Body copy</p>
CSS variables
.invoice-total {
  color: var(--color-primary);
}

.invoice-meta {
  color: var(--color-tertiary);
}

/* Light surface variant */
.invoice-card.light .invoice-total {
  color: var(--color-primary-light);
}
Naming convention
  • text-{level} — dark surface (default).
  • text-{level}-light — light surface variant.
  • Levels: primary, secondary, tertiary, disabled.
Dos & don'ts
  • Pick the surface first, then the matching token pair.
  • Use these utilities instead of arbitrary hex values like text-[#FFF].
  • Don't mix — never put a dark-surface token on a light surface (or vice versa).
  • Don't apply text-disabled to body copy. It's intentionally below AA for small text.

Rules of thumb

  • Pick the surface first, then the token pair. Never use a dark-surface token on a light surface (or vice versa).
  • Primary headings always use the strongest available value: pure white on dark, near-black on light.
  • Reserve Disabled for genuinely inactive elements — it intentionally fails AA on small text.