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 { Game, newStory } from "@react-text-game/core";

export const intro = newStory("intro", () => [
{ type: "header", content: "The station", props: { level: 1 } },
{ type: "text", content: "The last train is waiting." },
{
type: "actions",
content: [
{
label: "Board the train",
action: () => Game.jumpTo("inside-train"),
},
],
},
]);

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.