Skip to content
AstroCraft Docs
On this theme

Typography

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

The typeface

Host Grotesk Variable, from Fontsource, latin subset only:

@font-face {
  font-family: "HostGroteskVariable";
  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, U+0152-0153, /* … */;
}

One file, one @font-face, a 300–800 weight axis covering every weight the scale asks for. font-display: swap means text renders in the fallback immediately rather than blocking.

It is preloaded in BaseHead for 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" />

Note that the href comes from an import rather than a hardcoded path, so it carries the hashed build URL and can never point at a stale file.

Only the latin subset ships, to keep the payload small. The package also carries host-grotesk-latin-ext-wght-normal.woff2 and italic files — add a second @font-face block in src/styles/fonts.css if you need them.

Wiring the family

--font-sans in tailwind-theme.css is the single place the family is named:

--font-sans:
  "HostGroteskVariable", "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";

html then takes it in global.css, so every element inherits it and no component sets a family:

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

Ligatures and contextual alternates are on globally. --font-mono is a system stack — no monospace webfont ships.

The scale

Three shared classes in @layer components in global.css, one per heading level:

.h1 { @apply text-4xl font-bold md:text-6xl; }        /* 60px bold at md+ */
.h2 { @apply text-3xl font-semibold md:text-5xl; }    /* 48px semibold */
.h3 { @apply text-2xl font-semibold md:text-3xl; }    /* 30px semibold */

.description { @apply text-muted-foreground md:text-lg; }   /* Body Large, 18px */

They are @apply classes rather than components because they are a pattern repeated across many unrelated elements, which is exactly what @apply is for in this codebase. Anything with structure or variants becomes a component or a tv() config instead.

Two deliberate omissions in those rules:

No leading-*. Tailwind’s text-4xl and above already ship tight line-heights (1.11 down to 1.0) that override the 1.6 set on <html>, which is what the design shows. Adding a leading utility here would fight that.

Mobile sizes are the base, desktop is the md: step. The design is drawn at 1440, so the desktop size is the one the mock states and the mobile size is the scaled-down base. That is mobile-first, and it is why every one of these classes reads small-first.

Display (72px) and H4 (24px) are not written, because nothing in the theme uses them yet. Add them next to these three when a hero or a fourth level needs one, rather than reaching for an arbitrary text-[72px] at a call site.

Using them

<h1 class="h1">Rebuilding Meridian's trading interface</h1>
<p class="description">We rebuilt a legacy trading dashboard…</p>

For anything outside the scale, use Tailwind’s utilities directly — text-sm, font-medium, tracking-tight. The three classes exist for the three levels that repeat, not as a mandate.

The blog post body

Long-form prose is the one place that needs element-level rules, because the content is markdown and you cannot put a class on a generated <p>. Those rules live in src/components/Sections/Blog/_post-body.css, a sidecar imported by PostBody.astro.

That is the theme’s general pattern for CSS that will not express as utilities — a leading-underscore sidecar next to the component that uses it, imported from its frontmatter. ui/_overlay.css and ui/accordion/_accordion.css are the other two.

If you write a sidecar of your own, restate the reduced-motion guard inside it. An unlayered sidecar stylesheet outranks the layered global guard in motion/index.css, so motion declared in a sidecar is not covered by it. Both existing sidecars carry their own @media (prefers-reduced-motion: reduce) block for this reason.

Changing the typeface

  1. pnpm add @fontsource-variable/<family> (or drop a .woff2 into src/assets/ and point at it).
  2. Edit the @font-face block in src/styles/fonts.css — family name, weight range, file path, unicode range.
  3. Update --font-sans in src/styles/tailwind-theme.css to name it first.
  4. Update the preload import in src/layouts/BaseHead.astro to the new file.
  5. pnpm remove @fontsource-variable/host-grotesk.

Five edits, none of them in a component. If your replacement is not variable, you will need one @font-face per weight and should check the scale still reads — .h1 asks for 700 and .h2/.h3 for 600.

Two typographic details set globally

html {
  -webkit-text-size-adjust: none;
  text-size-adjust: none;
  scrollbar-gutter: stable;
  scroll-padding-top: 5rem;
}

text-size-adjust: none stops mobile Safari inflating text in landscape. scrollbar-gutter: stable reserves the scrollbar’s width so a page that grows past one viewport does not shift horizontally.

scroll-padding-top: 5rem exists because the header is fixed and out of flow, so an in-page anchor would otherwise land underneath it. The number is the header’s top-6 offset plus its h-11 height, rounded up — 68px of chrome, 80px of padding. If you change the header’s height or offset, change this too, or every anchor link on the site lands slightly under the bar.

NEXT STEPLayout & Spacing