Skip to content
AstroCraft Docs
On this theme

Typography

Urengi ships one typeface, self-hosted, in one variable file. The type scale is four shared classes plus Tailwind’s own utilities. There is no typography plugin and no prose stylesheet for the site chrome — rendered markdown is the one place that gets its own rules.

The typeface

Host Grotesk, variable, weights 300–800, via @fontsource-variable/host-grotesk. It is the design’s font/family/sans, and it carries every weight the mock uses: 400 body, 500 nav links, 600 column headings and buttons, 700 the CTA headline.

Only the variable roman ships. No italic file is included, to keep the download small — add the italic @font-face from the same package if a design needs one.

/* src/styles/fonts.css */
@font-face {
  font-family: "Host Grotesk Variable";
  font-style: normal;
  font-display: swap;
  font-weight: 300 800;
  src: url(@fontsource-variable/host-grotesk/files/host-grotesk-latin-wght-normal.woff2)
    format("woff2-variations");
  unicode-range: U+0000-00FF, U+0131, /* … Latin subset … */;
}

The file is self-hosted, so no request leaves your domain for it and no font CDN sees your visitors. BaseHead also preloads the exact woff2 for a faster first paint:

import hostGroteskVariable from "@fontsource-variable/host-grotesk/files/host-grotesk-latin-wght-normal.woff2";

<link rel="preload" href={hostGroteskVariable} as="font" type="font/woff2" crossorigin="anonymous" />

Importing the file rather than hard-coding a path is what keeps the preload pointing at the hashed asset the build actually emits. If you change the typeface, change both — a preload for a font nobody loads is a wasted request, and it is silent.

The wordmark is not a webfont. The design’s display face for the amber “U” is Ceimo, which is not distributable, so the wordmark ships as the exported Figma vector at src/assets/logo-wordmark.svg (with a large variant beside it). Replace those two SVGs to change the logo; no font work is involved.

The stacks

@theme {
  --font-sans:
    "Host Grotesk Variable", "Host Grotesk", ui-sans-serif, system-ui, -apple-system,
    BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji",
    "Segoe UI Emoji";
  --font-mono:
    "SFMono-Regular", "Menlo", "Monaco", "Consolas", "Liberation Mono", "Courier New", "monospace";
}

--font-sans is applied to html directly in global.css, alongside the base reading settings:

html {
  font-family: var(--font-sans);
  line-height: 1.6;
  -webkit-text-size-adjust: none;
  text-size-adjust: none;
  font-feature-settings: "liga" 1, "calt" 1;
}

--font-mono is a system stack — no monospace webfont ships, because the only monospace on the site is inside code fences in blog posts and the system faces are good enough there to not be worth a second download.

The four shared classes

Defined once in @layer components in global.css, and used with @apply or straight in markup across the whole site:

.h1 { @apply text-3xl font-medium md:text-4xl; }
.h2 { @apply text-3xl font-medium; }
.h3 { @apply text-xl font-medium; }

.description { @apply text-base-700 dark:text-base-300 md:text-lg; }

They exist so that retuning the site’s headline scale is four lines rather than a search across every section. .description is the standfirst treatment — the lead paragraph under a heading — and it carries its own light/dark pair, which is why sections do not repeat that pair by hand.

These are classes, not elements. .h1 on an <h2> is correct and common: heading level is a document-structure decision that belongs to the section, and heading size is a design decision that belongs to the class. The ESLint accessibility rules and astro check will not catch a heading order mistake for you, so keep the levels honest and reach for the class for the size.

Everything else is plain Tailwind: text-sm, text-lg, font-semibold, tracking-tight. There is no ramp of custom size tokens, deliberately — Tailwind’s own scale is the scale.

Rendered markdown

Blog posts and customer stories are the one place with a real prose stylesheet, and it lives in exactly one component: Sections/Global/Prose.astro, roughly forty lines of rules against the project’s own tokens.

It was PostBody’s own <style> block until the customer-story pages arrived wanting the identical treatment. Two components carrying a copy of the same :global selectors is the shape where re-tuning the type scale fixes one page and leaves the other behind.

The styles are scoped to .post-prose and reach the slotted children with :global, which is what compiled markdown needs — there is no class to hang on the elements the renderer produced. Astro adds its scope hash to the .post-prose element itself, so the :global descendants are still fenced to that component’s box rather than leaking site-wide.

Still no typography plugin. This is one design’s worth of rules against tokens you already own; a plugin would be a dependency, a config and an override sheet to fight.

One detail inside it worth knowing: headings carry a scroll offset so an anchor lands the heading below the sticky header rather than under it.

Changing the typeface

  1. Install the Fontsource package for your face, or drop a woff2 into src/assets/.
  2. Replace the @font-face block in src/styles/fonts.css, including the font-weight range and the unicode-range if your subset differs.
  3. Update --font-sans in tailwind-theme.css so the family name matches.
  4. Update the preload import in src/layouts/BaseHead.astro to point at the new file.
  5. Check the weights. The site uses 400, 500, 600 and 700; a face that ships only 400 and 700 will synthesise the middle two, and synthesised weights look wrong at large sizes.

Steps 2–4 are one edit each, and none of them is inside a component. That is the whole point of the arrangement.

Two things the scale assumes

line-height: 1.6 is set on html, so it cascades everywhere the design does not override it. A component that sets its own leading is making a deliberate exception; most do not.

scrollbar-gutter: stable is also on html, reserving the scrollbar’s width so a short page and a long page do not shift the layout horizontally between navigations. It is a typography-adjacent setting that people usually discover by accident when they remove it.

NEXT STEPLayout & Spacing