BloxwapFont

Cross-platform typography

Pin font assets, select web scripts, use Next.js and register static React Native faces.

This recipe supplies the font assets for HIG typography #7. HIG owns the approved semantic type roles and consumer certification. Font supplies the binary faces and registration names. Selective CSS and the manifest ship in 0.1.3. The consolidated native entries below ship in 0.1.4.

Pin and inspect

Use a reviewed immutable font commit and its built package tarball until a release is available. Do not duplicate binaries in each product repository. A source checkout needs Node, Python and the pinned packaging tools:

python3 -m venv packages/font/.venv
packages/font/.venv/bin/pip install 'fonttools[woff,unicode]==4.66.1' 'brotli==1.2.1'
export FONT_PYTHON="$PWD/packages/font/.venv/bin/python"
cd packages/npm
npm pack
# The same tarball contains web and native fonts for all scripts.
# In your application, install the resulting local/CI artifact tarball.
import manifest from '@bloxwap/font/manifest';
// schemaVersion, package version, OFL-1.1 license, generator;
// faces: path, package (native), family, weight, style, scripts (ISO 15924), font version,
// byte count, SHA-256, axes, and static face registrationName/source hash.

The built manifest in @bloxwap/font includes every WOFF2 and static TTF. All paths are relative to @bloxwap/font; native entries also identify @bloxwap/font in their package field. A manifest path such as native/BloxwapSans/BloxwapSans-Regular.ttf is imported through @bloxwap/font/native/assets/BloxwapSans/BloxwapSans-Regular.ttf. Since 0.1.4, the single package includes every native TTF and web font. npm installs the full catalog, while direct asset and selective CSS imports limit the application bundle. Package version and internal font version are distinct: the existing binaries report Version 1.000. The packaging tests verify files, hashes, Unicode coverage, family names and actual binary metadata. Packaging uses a process pool with one worker per CPU and memoized file hashes. Repeated builds reuse native targets only when the manifest source hashes, package version and existing TTF hash match. Native TTFs decode the hash-pinned named static WOFF2 release sources, preserving glyph outlines, metrics and hinting. Their registration metadata normalizes the published weight tiers (for example, an approximate source class 608 becomes 600); the manifest retains sourceWeight. The existing Pixel static faces use ROND 50. Sources, glyph design and existing variable binaries stay unchanged. Font generation provenance lives in Building; all assets retain the SIL OFL 1.1.

Web: load only the roles and scripts you use

@import '@bloxwap/font/css/sans-core';
@import '@bloxwap/font/css/mono-core';
@import '@bloxwap/font/css/sans-arabic';
@import '@bloxwap/font/css/sans-hebrew';
body { font-family: 'Bloxwap Sans', system-ui, sans-serif; font-synthesis: none; }
code, pre { font-family: 'Bloxwap Mono', ui-monospace, monospace; }
.price { font-variant-numeric: tabular-nums; }

Entries sans, mono, pixel load one family with all its available companions. Entries sans-core, mono-core, pixel-core select the core; Sans/Mono additionally offer arabic, hebrew, armenian, georgian, jp, kr, sc. There is no Pixel companion or synthesized italic for companions that only publish normal faces. Existing @bloxwap/font/css remains the complete catalog. Core faces use Latin (including all Latin Extended, IPA and Vietnamese), Greek, Cyrillic and remaining-code-point subsets, with explicit shared combining marks, punctuation and numbers for shaping. Companions retain their existing files. All use unicode-range to defer downloads until text needs the face. See Installation for cross-subset shaping limitations and the complete-font option.

Prefer explicit locale entries for Japanese, Korean and Simplified Chinese. Loading multiple CJK companions with overlapping Han ranges does not implement a locale switch: scope the published named families (Bloxwap Sans JP, etc.) to matching lang containers. Use lang and dir="auto" for mixed translations, and a host-approved system fallback for unsupported characters. Pixel is a scoped display accent; use Sans for UI/prose and Mono for code/numeric roles.

All rules use font-display: swap. A system fallback is available while loading or offline, but metrics differ; do not claim zero layout shift. Preload only the primary normal UI face. The generated manifest gives exact byte budgets; choose a budget from selected faces rather than loading the catalog. The migration report records measured production requests and large-text/fallback tests. Host language-specific clipping and layout-shift budgets still require consumer checks.

Next.js App Router

The current package loaders remain supported and include companions. They preload the complete core normal face; italic styles are lazy. next/font/local has no per-source unicode-range, so use the CSS entries above for automatic core subsetting:

app/layout.tsx
import { BloxwapSans } from '@bloxwap/font/sans';
import { BloxwapMono } from '@bloxwap/font/mono';
import './globals.css';

export default function Layout({ children }: { children: React.ReactNode }) {
  return <html lang="en" className={`${BloxwapSans.variable} ${BloxwapMono.variable}`}>
    <body>{children}</body>
  </html>;
}
app/globals.css
body { font-family: var(--font-bloxwap-sans), system-ui, sans-serif; }
code, pre { font-family: var(--font-bloxwap-mono), ui-monospace, monospace; }

For a strict script budget, import the selective CSS entries in the root layout instead. They need no Next loader or copied font files. See Installation for loader behavior and existing APIs. The HIG migration uses Next 16.3.6 static export with /font; other Next versions require host validation.

Expo / React Native

Install the single package for every web and native family:

npm install @bloxwap/font

All core and companion families are included: Sans, Mono and Pixel, plus Sans/Mono Arabic, Hebrew, Armenian, Georgian, Japanese, Korean and Simplified Chinese. Use @bloxwap/font/native/<Family> for a typed Metro family map, or @bloxwap/font/native/assets/<Family>/<File>.ttf for one face.

Migrating from 0.1.3: replace both @bloxwap/font-native/… and @bloxwap/font-native-<script>/… with @bloxwap/font/native/…. Remove the old packages from your dependencies. Registration names remain unchanged. The former packages are deprecated after the consolidated release is available; existing versions remain installable.

WOFF2 is a web asset. Native uses the generated static TTFs. Each face has a unique embedded family and PostScript name, identical to its registration key. Choose weight/style by changing the key, retaining native font scaling:

import * as Font from 'expo-font';
import { Text } from 'react-native';

// Register just the selected faces, before rendering content.
export async function registerFonts() {
  await Font.loadAsync({
  'BloxwapSans-Regular': require('@bloxwap/font/native/assets/BloxwapSans/BloxwapSans-Regular.ttf'),
  'BloxwapSans-SemiBold': require('@bloxwap/font/native/assets/BloxwapSans/BloxwapSans-SemiBold.ttf'),
  'BloxwapSans-RegularItalic': require('@bloxwap/font/native/assets/BloxwapSans/BloxwapSans-RegularItalic.ttf'),
  'BloxwapMono-Regular': require('@bloxwap/font/native/assets/BloxwapMono/BloxwapMono-Regular.ttf'),
  });
}

export function Label() {
  return <Text allowFontScaling style={{
    fontFamily: 'BloxwapSans-SemiBold', fontSize: 16, lineHeight: 24,
  }}>Account balance</Text>;
}

Use the exact manifest registrationName as the key (for italic, register BloxwapSans-RegularItalic under that same name). Do not set fontWeight or fontStyle alongside the unique family, as platform font matching may substitute or synthesize a face. The per-file @bloxwap/font/native/assets/* entry above lets Metro bundle only chosen faces. Convenient typed @bloxwap/font/native/BloxwapSans and other per-family maps contain Metro require() asset IDs for every weight in that family; importing a complete map adds all of those files to the native bundle even if only some are registered. Nine weights, 100–900, use Thin, ExtraLight, Light, Regular, Medium, SemiBold, Bold, ExtraBold and Black; available italics append Italic. Bare RN must register/package these dependency TTFs through its asset pipeline; on iOS list the chosen files in UIAppFonts, then use the embedded PostScript names. Do not copy source binaries into each repository.

For a complete family map, use import faces from '@bloxwap/font/native/BloxwapSans'. Companion imports use the same package, for example import arabic from '@bloxwap/font/native/BloxwapSansArabic' or require('@bloxwap/font/native/assets/BloxwapSansArabic/BloxwapSansArabic-Regular.ttf').

Companion maps use BloxwapSansArabic, BloxwapSansHebrew, BloxwapSansArmenian, BloxwapSansGeorgian, BloxwapSansJP, BloxwapSansKR, BloxwapSansSC and matching Mono names. Native has no CSS fallback list: select the locale family, split mixed-script runs when necessary, and preserve system fallback for unsupported glyphs. Arabic/Hebrew/CJK have normal faces only. Pixel maps pin roundness to 50.

The sample uses explicit dp values. Web rem and NativeWind rem-root are separate host decisions; do not multiply or mechanically rename HIG web steps. Keep allowFontScaling enabled, avoid fixed text container heights and validate the largest accessibility text setting with long translations. Package metadata and static registration are verified on Linux; physical Android/iOS rendering, Dynamic Type, VoiceOver and native clipping are not verified here. See the migration report for the exact evidence and remaining HIG responsibilities.

Edit on GitHub

On this page