npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@react-text-game/mdx

v0.3.1

Published

MDX support for React Text Game

Readme

@react-text-game/mdx

MDX integration package for React Text Game engine. This package enables you to write game passages using MDX (Markdown + JSX), allowing you to combine narrative content with interactive React components in a seamless, author-friendly format.

Why MDX?

  • Author-friendly: Write game content in familiar Markdown syntax
  • Component integration: Embed React components directly in your narrative
  • Type-safe: Full TypeScript support for your custom components
  • Vite integration: Optimized build pipeline with Vite plugin
  • Structured data extraction: Automatically extract metadata and story structure from MDX files

Quick Start

Installation

Install the package along with its peer dependencies:

# Using Bun
bun add @react-text-game/mdx @react-text-game/core @mdx-js/mdx @mdx-js/react

# Using npm
npm install @react-text-game/mdx @react-text-game/core @mdx-js/mdx @mdx-js/react

# Using yarn
yarn add @react-text-game/mdx @react-text-game/core @mdx-js/mdx @mdx-js/react

# Using pnpm
pnpm add @react-text-game/mdx @react-text-game/core @mdx-js/mdx @mdx-js/react

Setup

This package provides an MDX plugin that works with any MDX-compatible bundler.

With Vite

  1. Set up MDX following the official MDX documentation

  2. Add the React Text Game plugin to your MDX configuration in vite.config.ts:

import mdx from "@mdx-js/rollup";
import { reactTextGameStoryPlugin } from "@react-text-game/mdx/plugin";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";

export default defineConfig({
    plugins: [
        {
            enforce: "pre",
            ...mdx({ ...reactTextGameStoryPlugin() }),
        },
        react(),
    ],
});

With Other Bundlers

Follow the MDX installation guide for your bundler, then pass reactTextGameStoryPlugin() as an option to the MDX compiler:

import { compile } from "@mdx-js/mdx";
import { reactTextGameStoryPlugin } from "@react-text-game/mdx/plugin";

await compile(mdxSource, { ...reactTextGameStoryPlugin() });

Basic Usage

Create an MDX file for your story (e.g., src/game/stories/intro.mdx):

---
passageId: intro
---

import { Action, Actions } from "@react-text-game/mdx";
import { player } from "../entities/player";

# Welcome to the Adventure

Hello, {player.name}! This is your first passage.

<Actions>
    <Action onPerform={() => alert("Started!")}>Start Adventure</Action>
</Actions>

Import your MDX files so they register with the game engine. The recommended approach is to create a registry structure:

// src/game/stories/index.ts
import "./intro.mdx";
import "./chapter1.mdx";

// src/game/registry.ts
export * from "./stories";

// src/main.tsx
import "./game/registry";

Advanced Example

Here's a more complex story demonstrating all available components:

---
passageId: adventure-start
---

import {
    Action,
    Actions,
    Conversation,
    Say,
    Include,
} from "@react-text-game/mdx";
import { player } from "../entities/player";

# The Grand Adventure

{player.name}, you stand at the entrance of an ancient temple. Strange symbols glow on the walls.

![Temple Entrance](./assets/temple.png "The Ancient Temple")

<video src="./assets/temple-ambience.mp4" />

<Conversation>
    <Say>What is this place?</Say>
    <Say>The markings... they're unlike anything I've seen before.</Say>
    <Say>I should proceed carefully.</Say>
</Conversation>

<Actions>
    <Action onPerform={() => console.log("Examining symbols")}>
        Examine the symbols closely
    </Action>
    <Action onPerform={() => console.log("Entering temple")}>
        Enter the temple
    </Action>
    <Action onPerform={() => console.log("Walking away")}>Walk away</Action>
</Actions>

<Include storyId="temple-lore" />

Components Reference

The MDX package provides JSX components that map to the core game engine's story components. All core component types are supported through either direct MDX components or standard Markdown/HTML syntax.

Text & Headers

Standard Markdown text and headers are automatically converted to the appropriate core components:

# Header Level 1

## Header Level 2

### Header Level 3

Regular paragraph text becomes a text component.

Maps to: TextComponent and HeaderComponent from @react-text-game/core

Images & Video

Use standard Markdown image syntax or HTML elements:

![Alt text](./image.png "Image title")

<img src="./image.png" alt="Alt text" />

<video src="./video.mp4" />

Maps to: ImageComponent and VideoComponent from @react-text-game/core

<Actions> and <Action>

Container for interactive action buttons. Each <Action> represents a clickable choice.

<Actions>
    <Action onPerform={() => alert("Action 1")}>
        First Choice
    </Action>
    <Action
        onPerform={() => Game.jumpTo("chapter2")}}
        color="danger"
        variant="bordered"
    >
        Dangerous Choice
    </Action>
</Actions>

Props for <Action>:

  • onPerform: () => void - Callback executed when clicked (required)
  • children: string - Button caption, compiled into the action's content (required)
  • color?: ButtonColor - Color scheme: "default", "primary", "secondary", "success", "warning", "danger"
  • variant?: ButtonVariant - Style variant: "solid", "bordered", "light", "flat", "faded", "shadow", "ghost"
  • isDisabled?: boolean - Disable the button
  • tooltip?: object - Tooltip configuration with content, position, and className
  • className?: string - Custom CSS classes

Props for <Actions>:

  • direction?: "horizontal" | "vertical" - Layout direction (default: "horizontal")
  • className?: string - Custom CSS classes

Maps to: ActionsComponent and ActionType from @react-text-game/core

<Conversation> and <Say>

Display dialogue or conversation sequences with multiple messages.

<Conversation>
    <Say>Hello there!</Say>
    <Say who={{ name: "NPC", avatar: "./npc.png" }} side="left">
        Greetings, traveler!
    </Say>
    <Say who={{ name: "Player" }} side="right" color="#3b82f6">
        I'm looking for the temple.
    </Say>
</Conversation>

Props for <Say>:

  • children: ReactNode - Message content (required)
  • who?: { name?: string; avatar?: string } - Speaker information
  • side?: "left" | "right" - Message alignment (default: "left")
  • color?: string - Custom bubble color (hex format)
  • classNames?: object - CSS class overrides for base, content, and avatar

Props for <Conversation>:

  • appearance?: "atOnce" | "byClick" - Message reveal mode (default: "atOnce")
  • variant?: "chat" | "messenger" - Visual style (default: "chat")
  • className?: string - Custom CSS classes

Maps to: ConversationComponent and ConversationBubble from @react-text-game/core

<Include>

Embed another story passage within the current one. You can include any registered story passage in the game, whether it's defined in a TypeScript file (using defineStory() — or the still-supported newStory() — from @react-text-game/core) or another MDX file.

<!-- Include an MDX story -->

<Include storyId="common-intro" />

<!-- Include a TypeScript-defined story -->

<Include storyId="combat-system" />

Props:

  • storyId: string - ID of the story passage to include (required). The story must be registered with the game engine.

Maps to: AnotherStoryComponent from @react-text-game/core

Dynamic Variables

Embed dynamic variables that are evaluated at runtime when the story is displayed. This allows you to show game state, player properties, or any JavaScript expression directly in your text content.

Two syntaxes are supported:

1. Bare Expressions (Recommended - Concise)

---
passageId: player-status
---

import { player } from "../entities/player";
import { Game } from "@react-text-game/core";

# Hello, {player.name}!

You have {player.gold} gold coins and {player.inventory.length} items.

<Conversation>
    <Say>What are you doing, {player.name}?</Say>
</Conversation>

<Actions>
    <Action onPerform={() => console.log("test")}>Talk to {player.name}</Action>
</Actions>

2. <Var> Wrapper (Explicit Alternative)

---
passageId: player-status
---

import { Var } from "@react-text-game/mdx";
import { player } from "../entities/player";

# Hello, <Var>{player.name}</Var>!

You have <Var>{player.gold}</Var> gold coins.

Key Features:

  • Runtime Evaluation: Variables are evaluated when the story renders, not at compile time
  • IDE Support: Full TypeScript autocomplete and type checking (because you import the variables)
  • Import Tracking: IDE enforces proper imports for referenced variables
  • Dynamic Content: Access game state, player properties, calculations, etc.
  • Works Everywhere: Paragraphs, headers, Say bubbles, Action labels, etc.

Expression Examples:

<!-- Simple property access -->

{player.name}

<!-- Nested properties -->

{player.stats.strength}

<!-- Calculations -->

{player.gold \* 2}

<!-- Method calls -->

{player.getTitle()}

<!-- Conditional expressions -->

{player.level >= 10 ? "Expert" : "Novice"}

<!-- Array/object methods -->

{player.inventory.map(item => item.name).join(", ")}

Important Notes:

  • Variables must be imported at the top of the MDX file
  • Expressions are evaluated in the story's render context
  • TypeScript will validate your variable paths
  • Can be used inline within paragraphs, headers, components, and other text content

Maps to: Template literals in the compiled output, evaluated at runtime

Limitations & Best Practices

Supported Components Only

Only use components provided by @react-text-game/mdx or standard HTML/Markdown elements. Custom React components are not supported and will be ignored during compilation.

Supported:

<!-- Package components -->

<Action onPerform={() => {}}>Click</Action>
<Say>Hello</Say>
<Include storyId="intro" />

<!-- Standard HTML -->

<video src="video.mp4" />
<img src="image.png" />

<!-- Markdown syntax -->

# Header

![Image](image.png)

Not supported:

<!-- Custom components will be ignored -->

<CustomButton onClick={() => {}}>Click</CustomButton>
<MyComponent />

Unsupported HTML Elements

Only specific HTML elements are processed by the plugin:

  • <img> - Converted to ImageComponent
  • <video> - Converted to VideoComponent

All other HTML elements (like <div>, <span>, <button>, etc.) are ignored and will not appear in the final story. Use standard Markdown syntax for text formatting instead.

Example:

<!-- ❌ This will be ignored -->

<div className="container">
    <span>Text</span>
</div>

<!-- ✅ Use Markdown instead -->

Regular text with **bold** and _italic_ formatting.

Unsupported Props

The plugin only processes specific props for each component. Unsupported props are silently ignored.

For <Say> component:

  • ✅ Supported: who, side, color, classNames
  • ❌ Ignored: Any other props (e.g., className, style, id)

For <Conversation> component:

  • ✅ Supported: appearance, variant, className
  • ❌ Ignored: Any other props

For <Action> component:

  • ✅ Supported: onPerform, color, variant, isDisabled, tooltip, className
  • ❌ Ignored: Any other props

For <Actions> component:

  • ✅ Supported: direction, className
  • ❌ Ignored: Any other props

For HTML elements:

  • <img>: Supported props are src, alt, title, className, disableModal, onClick
  • <video>: Supported props are src, className, controls, autoPlay, loop, muted

Example:

<!-- ❌ 'style' and 'id' will be ignored -->

<Say style={{ color: "red" }} id="greeting">
    Hello
</Say>

<!-- ✅ Use supported props instead -->

<Say color="#ff0000">Hello</Say>