Dialog
The Dialog component renders the dialog box, including the character nametag, dialog text, and the optional dialog avatar (small portrait).
To customize the dialog, provide your own component via game.configure({ dialog }). You can compose Avatar, Nametag, and Texts, or use useAvatar / useDialog for layout logic.
0.9.1 adds dialog avatar APIs (Character avatars, portrait resolution, Avatar / useAvatar) and Sentence.getMetadata. See the CHANGELOG (opens in a new tab).
Quick example
- Import the components
import { Dialog, Texts, Nametag, useGame } from "narraleaf-react";- Compose the default ADV layout (optional
Avatar)
import { Avatar, Dialog, Nametag, Texts } from "narraleaf-react";
function GameDialog() {
return (
<Dialog className="bg-white">
<div className="dialog-content flex items-start gap-4 w-full h-full">
<Avatar />
<div className="dialog-text-content min-w-0 flex-1">
<Nametag className="font-bold" />
<Texts className="text-lg" />
</div>
</div>
</Dialog>
);
}The built-in DefaultDialog (opens in a new tab) uses the same structure: flex row with Avatar, then a column with Nametag and Texts.
- Configure the game
function App() {
const game = useGame();
useEffect(() => {
game.configure({
dialog: GameDialog,
});
}, []);
return /* ... */
}Components
Dialog
Renders the dialog container. Children are typically Avatar, Nametag, and Texts (any layout you prefer).
children?: React.ReactNode— Dialog content....props: React.HTMLAttributes<HTMLDivElement>— Passed to the inner wrapper.
Nametag
Renders the speaker name. Must be used inside Dialog.
Note: Nametag color comes from Character config; avoid overriding it with CSS in a way that fights the engine.
...props: React.HTMLAttributes<HTMLDivElement>
Texts
Renders the typed dialogue text. Must be used inside Dialog.
Note: Per-word styling follows sentence/word config; avoid overriding text styles that the engine already sets.
children?: never...props: React.HTMLAttributes<HTMLDivElement>
Avatar
Renders the resolved dialog avatar image for the current line, or nothing when no avatar is resolved (no layout gap).
...props: React.ImgHTMLAttributes<HTMLImageElement>— Merged with defaults (see useAvatar).
Default image style:
width/height:96objectFit:"cover"borderRadius:6flex:"0 0 auto"
Dialog avatars
Dialog avatars show a small portrait in the ADV box. They fit visual novels where the speaker may be on-screen, off-screen (voice-over), or where most lines should not need per-line avatar configuration.
Mental model
Characterholds avatar intent — default and off-screen avatar, plus the portrait bindings.- A stage
Imagerefines the avatar when it is the current visible bound portrait. Sentenceoverrides only exceptions — hide avatar or use a one-off asset/resolver.
Resolution order:
- Narrator or unnamed character → no avatar.
sentence.config.avatar === false→ no avatar.- Sentence-level avatar override (string, resolver, etc.).
- Avatar from the most recent visible bound portrait for that character.
- Character-level avatar.
- Otherwise no avatar.
The engine does not auto-crop full-body sprites. If no avatar is configured, nothing is shown.
Character-level avatar
Use when the same headshot should appear for normal lines and off-screen lines.
import { Character } from "narraleaf-react";
const alice = new Character("Alice", {
avatar: "/assets/alice/avatar-default.png",
});
alice.say("I can speak off-screen and still show an avatar.");Method style:
const alice = new Character("Alice")
.setAvatar("/assets/alice/avatar-default.png");
alice.say("This line uses Alice's default avatar.");Binding stage portraits
When a visible sprite should drive the avatar, bind a stage Image to the character.
import { Character, Image } from "narraleaf-react";
const aliceBody = new Image({
name: "alice-body",
src: "/assets/alice/body-normal.png",
opacity: 1,
});
const alice = new Character("Alice", {
avatar: "/assets/alice/avatar-default.png",
portraits: [
{
image: aliceBody,
avatar: "/assets/alice/avatar-normal.png",
},
],
});When aliceBody is visible in the current scene, that portrait’s avatar is used; otherwise the character-level avatar is used.
const alice = new Character("Alice")
.setAvatar("/assets/alice/avatar-default.png")
.addPortrait(aliceBody, {
avatar: "/assets/alice/avatar-normal.png",
});Expression-based avatars (tagged images)
Portrait avatars can be resolvers that receive the current portrait, currentSrc, tags, character, sentence, and gameState. The resolver may return:
- an image URL or
StaticImageData; nullto force no avatar at this layer;undefinedto fall through to the next layer.
const aliceBody = new Image({
name: "alice-body",
src: {
groups: [
["normal", "happy", "angry"],
["school", "casual"],
],
defaults: ["normal", "school"],
resolve: (emotion, outfit) => `/assets/alice/body-${emotion}-${outfit}.png`,
},
opacity: 1,
});
const alice = new Character("Alice", {
avatar: "/assets/alice/avatar-default.png",
portraits: [
{
image: aliceBody,
avatar: ({ tags }) => {
const emotion = tags?.[0] ?? "normal";
return `/assets/alice/avatar-${emotion}.png`;
},
},
],
});Per-line overrides
alice.say("Hide avatar for this line only.", {
avatar: false,
});
alice.say("Special cut-in.", {
avatar: "/assets/alice/avatar-special.png",
});
alice.say("Resolver for one line.", {
avatar: ({ tags, currentSrc }) => {
if (tags?.includes("angry")) {
return "/assets/alice/avatar-angry-close.png";
}
return undefined;
},
});avatar: false skips avatar immediately; other overrides win over portrait and character defaults.
Multiple visible portraits
If several bound portraits are visible, the engine picks the most recently shown visible one along the scene’s display order (back to front). A displayable with opacity effectively zero is ignored.
Custom dialogs and useAvatar
Use <Avatar /> for simple layouts. Use useAvatar() when grid/flex should depend on whether an avatar exists (visible, src, character, portrait).
See useAvatar for props, return shape, and examples.
TypeScript reference (public exports)
Types and classes are exported from narraleaf-react (see Character, SentenceConfig, CharacterConfig):
DialogAvatarSource,DialogAvatarResolverContext,DialogAvatarResolver,DialogAvatarCharacterPortraitConfig,DialogAvatarResolutionCharactermethods:setAvatar,addPortrait,setPortraitsSentence/sayconfig:avataron SentenceUserConfig- React:
Avatar,useAvatar, typeDialogAvatarContext
Avatar resolution for the default UI runs inside the player; you normally configure characters and sentences, then render <Avatar /> or call useAvatar().
Customize the dialog box
Example: container styles
function GameDialog() {
return (
<Dialog
style={{
backgroundColor: "rgba(0, 0, 0, 0.5)",
borderRadius: "10px",
padding: "20px",
}}
>
{/* ... */}
</Dialog>
);
}Example: nametag decoration
function GameDialog() {
return (
<Dialog>
<Nametag
style={{
backgroundImage: "url('/path/to/image.png')",
backgroundSize: "cover",
width: "100%",
height: "100%",
}}
/>
</Dialog>
);
}