Custom Fonts
There are two separate font layers: regular player UI inherits CSS, while dialog and choice text can also receive explicit defaults from Texts and Item.
1. Load the font
Prefer WOFF2 for web delivery and keep a system fallback.
@font-face {
font-family: "GameText";
src: url("/fonts/game-text.woff2") format("woff2");
font-weight: 400 700;
font-style: normal;
font-display: swap;
}
.game-player {
font-family: "GameText", system-ui, sans-serif;
}In Next.js, next/font/local is another good option. Apply its generated class to the same Player boundary.
2. Apply it to the player
<GameProviders>
<Player
story={story}
className="game-player"
onReady={({ liveGame }) => liveGame.newGame()}
/>
</GameProviders>This covers pages, buttons, and UI that inherit font-family.
3. Set dialog defaults
Since NarraLeaf 0.10, dialog font defaults are component props. They are no longer part of GameConfig.
import { Dialog, Nametag, Texts } from "narraleaf-react";
export function GameDialog() {
return (
<Dialog className="game-player rounded-xl bg-black/75 p-6">
<Nametag color="#fbbf24" />
<Texts
defaultColor="white"
fontFamily="GameText, system-ui, sans-serif"
fontSize={22}
fontWeight={400}
fontWeightBold={700}
/>
</Dialog>
);
}Sentence and Word styles still take priority over these defaults.
4. Set choice defaults
Custom menus set typography on each Item.
<Item
defaultColor="white"
fontFamily="GameText, system-ui, sans-serif"
fontSize={20}
fontWeight={400}
fontWeightBold={700}
/>Checks
- Confirm the font URL returns 200 and is not blocked by CORS.
- Check normal and bold weights; browsers synthesize missing weights.
- Keep
font-display: swapso text does not stay invisible while loading. - Test Chinese, Japanese, and Korean glyph coverage when the game uses those scripts. A Latin-only font will silently fall back per character.