Typography
Typography is made of tokens, a small set of element styles scoped to .iv-root, and eight text utilities. There is no typographic plugin and no prose class: text inside .iv-root is styled by its own tags.
Type scale
Eight steps. The two largest are fluid: they interpolate with the viewport through clamp(), so a page title shrinks on a phone without a media query.
| Token | Value | Used by |
|---|---|---|
--iv-text-xs | 0.75rem | iv-u-text-xs, small print in components |
--iv-text-sm | 0.875rem | <small>, <caption>, <figcaption>, <kbd>, iv-u-text-sm |
--iv-text-md | 1rem | Body text on .iv-root, <h6>, iv-u-text-md |
--iv-text-lg | 1.125rem | <h5>, iv-u-text-lg |
--iv-text-xl | 1.25rem | <h4>, iv-u-text-xl |
--iv-text-2xl | 1.5rem | <h3>, iv-u-text-2xl |
--iv-text-3xl | clamp(1.75rem, 1.2rem + 1.5vw, 2.25rem) | <h2> — no utility |
--iv-text-4xl | clamp(2.25rem, 1.5rem + 2.5vw, 3.5rem) | <h1> — no utility |
The utility matrix stops at 2xl on purpose: the two fluid steps belong to page headings, and a page should not have several of them. Apply them with <h1> and <h2>, or by reading the token directly in your own CSS.
The scale rendered
Each row below is real markup using the text utilities, so it renders with the same tokens as your pages.
xs — 0.75rem · footnotes, metadata
sm — 0.875rem · captions, helper text
md — 1rem · body copy, the default
lg — 1.125rem · lead paragraph
xl — 1.25rem · section subtitle
2xl — 1.5rem · card and section titles
Line height, weight and family
| Token | Value | Applied to |
|---|---|---|
--iv-leading-tight | 1.1 | All headings h1–h6 receive it; h4–h6 then override with snug |
--iv-leading-snug | 1.25 | h4, h5, h6, <pre> |
--iv-leading-normal | 1.5 | .iv-root, inherited by body text |
--iv-leading-relaxed | 1.65 | Available for long-form columns; not applied by default |
--iv-weight-normal | 400 | Body text, iv-u-font-normal |
--iv-weight-medium | 500 | iv-u-font-medium |
--iv-weight-semibold | 600 | h3–h6, <strong>, <b>, iv-u-font-semibold |
--iv-weight-bold | 700 | h1, h2, iv-u-font-bold |
--iv-font-sans | system-ui, -apple-system, "Segoe UI", Roboto, sans-serif | .iv-root |
--iv-font-mono | ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace | <code>, <kbd>, <samp>, <pre> |
What .iv-root applies
Element styles live in base.css and every selector starts with .iv-root. Outside that scope iVOLT styles no element, which is what makes it safe to drop into an existing page.
Headings
| Element | Size | Weight | Line height |
|---|---|---|---|
h1 | --iv-text-4xl | bold | tight |
h2 | --iv-text-3xl | bold | tight |
h3 | --iv-text-2xl | semibold | tight |
h4 | --iv-text-xl | semibold | snug |
h5 | --iv-text-lg | semibold | snug |
h6 | --iv-text-md | semibold | snug |
All headings get margin-block: 0 and text-wrap: balance. The zero margin is deliberate: vertical rhythm comes from a layout primitive, normally iv-stack, not from collapsing margins you have to fight. Size is presentation; choose the heading level by document structure and change its size with a utility or your own rule if you need to.
Flow content
<p>:margin-block: 0andtext-wrap: pretty.<ul>,<ol>: no block margin,padding-inline-start: var(--iv-space-6); list items get a small top margin (--iv-space-1).<a>:--iv-color-primary, underlined withtext-underline-offset: 0.15em, moving to--iv-color-primary-hoveronly inside@media (hover: hover), so touch devices do not get a stuck hover colour.<strong>and<b>: semibold.<small>:--iv-text-sm.<blockquote>: muted text with a 2px inline-start border in--iv-color-border-strong.<hr>: a single border line in--iv-color-borderwith--iv-space-8above and below.
Code
code, kbd, samp and pre switch to --iv-font-mono at 0.9375em, which keeps monospace text optically level with the surrounding size instead of jumping. Inline code and kbd sit on --iv-color-surface with --iv-radius-sm and a little inline padding; kbd adds a border. pre is a padded surface block with overflow-x: auto and snug leading, and pre code drops the background and padding so nested code does not double up.
Tables and media
Content tables — plain <table>, not the iv-table component — get border-collapse: collapse, full inline size and start-aligned text; <caption> is small, muted and start-aligned. img, svg and video are capped at max-inline-size: 100%; figure loses its default margins and figcaption is small and muted.
Reading measure
Long lines are hard to track back. --iv-measure is 68ch, and because ch is relative to the current font, the measure follows whatever typeface and size you set. Three ways to use it:
<div class="iv-container iv-container--narrow">…</div> <!-- container capped at the measure -->
<article class="iv-u-max-w-measure">…</article> <!-- utility on any block -->
.my-article { max-inline-size: var(--iv-measure); } /* your own CSS, reading the token */Body copy is the case for the measure. Tables, code blocks and dashboards usually want the full container; do not wrap them in a narrow container out of habit.
Text utilities
From the finite matrix. Only alignment has responsive prefixes (sm md lg xl); the size, weight and colour utilities do not, because a size that changes per breakpoint is normally the fluid heading scale in disguise.
| Utility | Effect | Responsive |
|---|---|---|
iv-u-text-start|center|end | Logical text alignment, so it follows RTL | yes |
iv-u-text-xs|sm|md|lg|xl|2xl | Sets font-size from the scale | no |
iv-u-font-normal|medium|semibold|bold | Sets font-weight | no |
iv-u-text-muted | --iv-color-text-muted — secondary text, never the only carrier of meaning | no |
iv-u-truncate | One line with an ellipsis; keep the full text reachable elsewhere | no |
Utilities are emitted with a doubled selector, specificity (0,2,0), and loaded last, so they beat component modifiers in both the layered and the flat stylesheet. Anything outside the matrix does not exist in v0.1 — there is no arbitrary-value syntax and no class scanner.
Changing the typeface
Redefine the family tokens after the iVOLT stylesheet. Every element style and component reads the token, so one declaration moves the whole system.
/* your-theme.css, loaded after ivolt.css */
.iv-root {
--iv-font-sans: "Your Sans", system-ui, sans-serif;
--iv-font-mono: "Your Mono", ui-monospace, monospace;
}Two things to keep in mind. First, --iv-measure is in ch: a wider typeface makes the column physically wider at the same 68 characters, which is the intended behaviour but worth checking. Second, if you self-host a webfont, declare @font-face in your own stylesheet with a font-display you have chosen, and keep a real system fallback in the stack so text is readable before the file arrives.
You can also rescale without touching the families. --iv-text-* are plain custom properties, so a compact variant is a handful of overrides in the same block.
Why system-ui
The package ships no font files and requests none. The default stack starts at system-ui and falls back through -apple-system, Segoe UI and Roboto, so every platform renders in a face it already has. The reasons are concrete:
- No network request, so no flash of unstyled or invisible text and no layout shift while a file downloads.
- No third-party origin in the critical path, which matters when a site runs a strict Content-Security-Policy or has to state where its assets come from.
- Nothing to bundle or serve: the CSS works from a file system, a WordPress theme folder or a CDN of your own without extra assets.
The trade-off is real and we do not hide it: the same page does not look identical on macOS, Windows and Android, because it is not the same typeface. If your brand needs one face everywhere, self-host it and override the token as shown above — the rest of the system does not change.