Skip to content
Español

Navigate

Type to search. Press Escape to close.

    Foundations

    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.

    CSS: base and tokens (in ivolt.css and in core.css). No JavaScript. Scope: .iv-root.

    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.

    TokenValueUsed by
    --iv-text-xs0.75remiv-u-text-xs, small print in components
    --iv-text-sm0.875rem<small>, <caption>, <figcaption>, <kbd>, iv-u-text-sm
    --iv-text-md1remBody text on .iv-root, <h6>, iv-u-text-md
    --iv-text-lg1.125rem<h5>, iv-u-text-lg
    --iv-text-xl1.25rem<h4>, iv-u-text-xl
    --iv-text-2xl1.5rem<h3>, iv-u-text-2xl
    --iv-text-3xlclamp(1.75rem, 1.2rem + 1.5vw, 2.25rem)<h2> — no utility
    --iv-text-4xlclamp(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

    TokenValueApplied to
    --iv-leading-tight1.1All headings h1h6 receive it; h4h6 then override with snug
    --iv-leading-snug1.25h4, h5, h6, <pre>
    --iv-leading-normal1.5.iv-root, inherited by body text
    --iv-leading-relaxed1.65Available for long-form columns; not applied by default
    --iv-weight-normal400Body text, iv-u-font-normal
    --iv-weight-medium500iv-u-font-medium
    --iv-weight-semibold600h3h6, <strong>, <b>, iv-u-font-semibold
    --iv-weight-bold700h1, h2, iv-u-font-bold
    --iv-font-sanssystem-ui, -apple-system, "Segoe UI", Roboto, sans-serif.iv-root
    --iv-font-monoui-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

    ElementSizeWeightLine height
    h1--iv-text-4xlboldtight
    h2--iv-text-3xlboldtight
    h3--iv-text-2xlsemiboldtight
    h4--iv-text-xlsemiboldsnug
    h5--iv-text-lgsemiboldsnug
    h6--iv-text-mdsemiboldsnug

    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: 0 and text-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 with text-underline-offset: 0.15em, moving to --iv-color-primary-hover only 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-border with --iv-space-8 above 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.

    UtilityEffectResponsive
    iv-u-text-start|center|endLogical text alignment, so it follows RTLyes
    iv-u-text-xs|sm|md|lg|xl|2xlSets font-size from the scaleno
    iv-u-font-normal|medium|semibold|boldSets font-weightno
    iv-u-text-muted--iv-color-text-muted — secondary text, never the only carrier of meaningno
    iv-u-truncateOne line with an ellipsis; keep the full text reachable elsewhereno

    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.