Skip to main content

React Text Game

Build interactive fiction with a small engine layer and use as much—or as little—of the supplied React UI as you want.

React Text Game is split into three libraries:

PackageUse it for
@react-text-game/corereactive game entities, passage navigation, saves, migrations, audio, and i18n
@react-text-game/uiready-made story and map renderers, save UI, and replaceable component slots
@react-text-game/mdxauthoring story passages in MDX

A passage in 30 seconds

src/game/intro.ts
import { defineStory } from "@react-text-game/core";

export const intro = defineStory("intro", (h) => [
h.header("The station", { level: 1 }),
h.text("The last train is waiting."),
h.actions([{ content: "Board the train", action: h.jump("inside-train") }]),
]);

The content callback receives a toolbox of component builders (h) as its first argument, so you never have to write component objects by hand.

Passages and entities register when their modules are imported. GameProvider initializes the engine and renders the current passage:

src/main.tsx
import "./game/intro";
import "@react-text-game/ui/styles";
import { GameProvider, PassageController } from "@react-text-game/ui";

const options = {
gameName: "Night Train",
startPassage: "intro",
isDevMode: import.meta.env.DEV,
};

root.render(
<GameProvider options={options}>
<PassageController />
</GameProvider>
);

Choose your path

Design principles

  • Game state belongs to core entities, not UI components.
  • A passage describes what to display; a renderer decides how it looks.
  • Map coordinates are percentages of the fitted source image, so hotspots remain anchored across viewport sizes.
  • Callable content is evaluated when a passage is displayed, allowing state-driven stories without a second schema.

The packages are independently usable, fully typed, and published under the MIT license. Source and example applications are available on GitHub.