Documentation
Custom NVL Dialog

Custom NVL Dialog

Overview

NVL (Novel) mode displays multiple dialogue entries in a scrollable list, suitable for novel-style narration. You can replace the default NvlContainer via the nvlDialog option in game.configure to fully customize the NVL dialogue appearance.

This guide demonstrates two approaches: fully custom (using NvlContainer) and customizing per-dialogue layout only (using DefaultNvlContainer + renderDialogItem).

1. Fully Custom NVL Container

Use NvlContainer as the outer wrapper, receive the dialogs array, and render each dialogue yourself. INvlContainerProps includes dialogs and an optional renderDialogItem.

import { useEffect } from "react";
import {
  NvlContainer,
  Nametag,
  Texts,
  useGame,
  type INvlContainerProps,
} from "narraleaf-react";
 
function CustomNvlContainer({ dialogs = [] }: INvlContainerProps) {
  return (
    <NvlContainer className="bg-black/80 text-white p-16">
      {/* NvlContainer: handles visibility, transitions, aspect-ratio scaling */}
      {dialogs.map((d) => (
        <div key={d.entry.id} className="space-y-2 mb-4">
          {/* d.entry: NvlDialogEntry with character, sentence, etc. */}
          {d.entry.character && (
            <Nametag entry={d.entry} className="text-amber-400 font-bold" />
          )}
          <Texts
            entry={d.entry}
            gameState={d.gameState}
            words={d.words}
            useTypeEffect={d.useTypeEffect}
            isActive={d.isActive}
          />
        </div>
      ))}
    </NvlContainer>
  );
}
 
function App() {
  const game = useGame();
  useEffect(() => {
    game.configure({ nvlDialog: CustomNvlContainer });
  }, []);
  return /* ... */;
}

2. Customize Per-Dialogue with renderDialogItem

If you only need to adjust how each dialogue is displayed (e.g. border, opacity), keep using DefaultNvlContainer and pass renderDialogItem to customize the layout:

import {
  DefaultNvlContainer,
  useGame,
  type INvlContainerProps,
} from "narraleaf-react";
 
function CustomNvlWithRenderer({ dialogs, renderDialogItem }: INvlContainerProps) {
  return (
    <DefaultNvlContainer
      dialogs={dialogs}
      renderDialogItem={({ entry, index, isActive, nametag, texts }) => (
        // nametag, texts: pre-rendered by DefaultNvlContainer
        <div className={`mb-4 ${isActive ? "opacity-100" : "opacity-60"}`}>
          {nametag}
          <div className="border-l-4 border-blue-500 pl-2">{texts}</div>
        </div>
      )}
    />
  );
}
 
// Register: game.configure({ nvlDialog: CustomNvlWithRenderer });

3. renderDialogItem Parameters

renderDialogItem receives NvlDialogItemRenderProps:

ParamTypeDescription
entryNvlDialogEntryDialogue entry (character, sentence, etc.)
indexnumberIndex in the list
isActivebooleanWhether this is the currently active (typing) dialogue
nametagReact.ReactNodePre-rendered character name (null when no character)
textsReact.ReactNodePre-rendered text content

4. Register with Game

import { useEffect } from "react";
import { useGame } from "narraleaf-react";
 
function App() {
  const game = useGame();
 
  useEffect(() => {
    game.configure({
      nvlDialog: CustomNvlContainer,  // or CustomNvlWithRenderer
    });
  }, [game]);
 
  return <Player>{/* ... */}</Player>;
}

See Also