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 🙏

© 2025 – Pkg Stats / Ryan Hefner

remark-flow

v0.1.6

Published

Remark plugins for custom button and variable syntax transformation

Readme

Remark Flow

A remark plugin library for parsing MarkdownFlow documents

MarkdownFlow (also known as MDFlow or markdown-flow) extends standard Markdown with AI to create personalized, interactive pages. Its tagline is "Write Once, Deliver Personally".

npm version License: MIT TypeScript

English | 简体中文

🚀 Quick Start

Install

npm install remark-flow
# or
yarn add remark-flow
# or
pnpm add remark-flow

Basic Usage

import { remark } from 'remark';
import remarkFlow from 'remark-flow';

const processor = remark().use(remarkFlow);

const markdown = `
# Welcome to Interactive Content!

Choose one option: ?[Option A | Option B | Option C]
Choose multiple skills: ?[%{{skills}} JavaScript||TypeScript||Python]
Enter your name: ?[%{{username}}...Please enter your name]
`;

const result = processor.processSync(markdown);
// Each ?[...] becomes a structured custom-variable node in the AST

Advanced Usage

import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkFlow from 'remark-flow';
import remarkStringify from 'remark-stringify';

const processor = unified()
  .use(remarkParse)
  .use(remarkFlow)
  .use(remarkStringify);

const result = processor.processSync(`
Select theme: ?[%{{theme}} Light//light | Dark//dark | ...custom]
Action: ?[Save Changes//save | Cancel//cancel]
`);

🧩 Supported Syntax Patterns

1. Simple Buttons

?[Submit]
?[Continue | Cancel]
?[Yes | No | Maybe]

Output: { buttonTexts: ["Yes", "No", "Maybe"], buttonValues: ["Yes", "No", "Maybe"], isMultiSelect: false }

2. Custom Button Values

?[Save Changes//save-action]
?[确定//confirm | 取消//cancel]

Output: { buttonTexts: ["Save Changes"], buttonValues: ["save-action"] }

3. Variable Text Input

?[%{{username}}...Enter your name]
?[%{{age}}...How old are you?]
?[%{{comment}}...]

Output: { variableName: "username", placeholder: "Enter your name" }

4. Variable Button Selection (Single-Select)

?[%{{theme}} Light | Dark]
?[%{{size}} Small//S | Medium//M | Large//L]

Output: { variableName: "theme", buttonTexts: ["Light", "Dark"], buttonValues: ["Light", "Dark"], isMultiSelect: false }

5. Variable Button Selection (Multi-Select)

?[%{{skills}} JavaScript||TypeScript||Python]
?[%{{lang}} JS//JavaScript||TS//TypeScript||PY//Python]

Output: { variableName: "skills", buttonTexts: ["JavaScript", "TypeScript", "Python"], buttonValues: ["JavaScript", "TypeScript", "Python"], isMultiSelect: true }

6. Combined: Buttons + Text Input

# Single-select with text input

?[%{{size}} Small//S | Medium//M | Large//L | ...custom size]

# Multi-select with text input

?[%{{tags}} React||Vue||Angular||...Other framework]

Output:

{
  variableName: "size",
  buttonTexts: ["Small", "Medium", "Large"],
  buttonValues: ["S", "M", "L"],
  placeholder: "custom size"
}

7. Separator Priority Rules

The first separator type encountered determines the parsing mode:

# Single-select mode (| appears first)

?[%{{option}} A | B||C] # Results in: ["A", "B||C"]

# Multi-select mode (|| appears first)

?[%{{option}} A||B | C] # Results in: ["A", "B | C"]

Key Points:

  • | = Single-select mode, || becomes part of button values
  • || = Multi-select mode, | becomes part of button values
  • First separator type wins and determines the entire parsing behavior

8. Unicode & International Support

?[%{{语言}} English//en | 中文//zh | 日本語//ja]
?[%{{用户名}}...请输入您的姓名]
?[👍 Good | 👎 Bad | 🤔 Unsure]

📖 API Reference

Plugin Exports

// Default export (recommended)
import remarkFlow from 'remark-flow';

// Named exports
import {
  remarkFlow, // Main plugin, functionally the same as the default export
  remarkInteraction, // The core plugin, which is also the default export
  remarkCustomVariable, // Variable-focused plugin
  createInteractionParser, // Parser factory
  InteractionType, // Type enums
} from 'remark-flow';

Output Format

All plugins transform ?[...] syntax into custom-variable AST nodes:

interface CustomVariableNode extends Node {
  type: 'custom-variable';
  data: {
    variableName?: string; // For %{{name}} syntax
    buttonTexts?: string[]; // Button display text
    buttonValues?: string[]; // Corresponding button values
    placeholder?: string; // Text input placeholder
  };
}

Parser API

import { createInteractionParser, InteractionType } from 'remark-flow';

const parser = createInteractionParser();

// Parse content and get detailed result
const result = parser.parse('?[%{{theme}} Light | Dark]');

// Parse and convert to remark-compatible format
const remarkData = parser.parseToRemarkFormat('?[%{{theme}} Light | Dark]');

🔗 Usage Examples

remark-flow can be used in two main ways:

  1. Standalone - Parse and transform syntax, then render with your own UI components
  2. With markdown-flow-ui - Use the pre-built React components for instant interactive UI

🎯 Standalone Usage (Custom Rendering)

When using remark-flow standalone, you parse the syntax and create your own UI components based on the AST nodes.

Basic AST Transformation

import { remark } from 'remark';
import { visit } from 'unist-util-visit';
import remarkFlow from 'remark-flow';
import type { Node } from 'unist';

const processor = remark().use(remarkFlow);

const markdown = `
# Choose Your Preferences

Select language: ?[%{{language}} JavaScript | Python | TypeScript | Go]
Enter your name: ?[%{{username}}...Your full name]
Action: ?[Save//save | Cancel//cancel]
`;

// Parse and examine the AST
const ast = processor.parse(markdown);
processor.runSync(ast);

// Find custom-variable nodes
visit(ast, 'custom-variable', (node: any) => {
  console.log('Found interaction:', node.data);
  // Output: { variableName: 'language', buttonTexts: ['JavaScript', 'Python', 'TypeScript', 'Go'], buttonValues: [...] }
});

Custom HTML Renderer

import { visit } from 'unist-util-visit';
import { remark } from 'remark';
import remarkHtml from 'remark-html';

function createCustomRenderer() {
  return (tree: Node) => {
    visit(tree, 'custom-variable', (node: any) => {
      const { variableName, buttonTexts, buttonValues, placeholder } =
        node.data;

      if (buttonTexts && buttonTexts.length > 0) {
        // Render as button group
        const buttonsHtml = buttonTexts
          .map((text, i) => {
            const value = buttonValues?.[i] || text;
            return `<button onclick="selectOption('${variableName}', '${value}')" class="interactive-btn">
              ${text}
            </button>`;
          })
          .join('');

        node.type = 'html';
        node.value = `
          <div class="button-group" data-variable="${variableName}">
            ${buttonsHtml}
          </div>
        `;
      } else if (placeholder) {
        // Render as text input
        node.type = 'html';
        node.value = `
          <div class="input-group">
            <label for="${variableName}">${placeholder}</label>
            <input
              id="${variableName}"
              name="${variableName}"
              placeholder="${placeholder}"
              class="interactive-input"
            />
          </div>
        `;
      }
    });
  };
}

// Use with remark processor
const processor = remark()
  .use(remarkFlow)
  .use(createCustomRenderer)
  .use(remarkHtml);

const result = processor.processSync(markdown);
console.log(result.toString()); // HTML with custom interactive elements

React Custom Components

import React from 'react';
import { remark } from 'remark';
import remarkReact from 'remark-react';
import remarkFlow from 'remark-flow';

// Custom React components for interactive elements
const InteractiveButton = ({ variableName, buttonTexts, buttonValues, onSelect }) => (
  <div className="flex gap-2">
    {buttonTexts.map((text, i) => (
      <button
        key={i}
        onClick={() => onSelect(variableName, buttonValues[i])}
        className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
      >
        {text}
      </button>
    ))}
  </div>
);

const InteractiveInput = ({ variableName, placeholder, onInput }) => (
  <div className="my-2">
    <input
      type="text"
      placeholder={placeholder}
      onChange={(e) => onInput(variableName, e.target.value)}
      className="border border-gray-300 rounded px-3 py-2 w-full"
    />
  </div>
);

// Usage in React component
function CustomMarkdownRenderer() {
  const handleInteraction = (variableName, value) => {
    console.log(`${variableName}: ${value}`);
    // Handle user interaction
  };

  const processor = remark()
    .use(remarkFlow)
    .use(remarkReact, {
      remarkReactComponents: {
        'custom-variable': ({ node }) => {
          const { variableName, buttonTexts, buttonValues, placeholder } = node.data;

          if (buttonTexts?.length > 0) {
            return (
              <InteractiveButton
                variableName={variableName}
                buttonTexts={buttonTexts}
                buttonValues={buttonValues}
                onSelect={handleInteraction}
              />
            );
          }

          if (placeholder) {
            return (
              <InteractiveInput
                variableName={variableName}
                placeholder={placeholder}
                onInput={handleInteraction}
              />
            );
          }

          return null;
        },
      },
    });

  const content = `
  # Interactive Form

  Choose language: ?[%{{lang}} English | 中文 | Español]
  Your name: ?[%{{name}}...Enter your name]
  Action: ?[Submit//submit | Reset//reset]
  `;

  return <div>{processor.processSync(content).result}</div>;
}

🎨 With markdown-flow-ui (Pre-built Components)

For a complete React component library with ready-to-use interactive components, use markdown-flow-ui.

Basic Integration

import { MarkdownFlow } from 'markdown-flow-ui';

function InteractiveChat() {
  const content = `
  # Welcome! 👋

  Select your preference: ?[%{{language}} JavaScript | Python | TypeScript]
  Enter your name: ?[%{{username}}...Your full name]
  Ready to start: ?[Let's Go!//start]
  `;

  return (
    <MarkdownFlow
      initialContentList={[{ content }]}
      onSend={(data) => {
        console.log('User interaction:', data);
        // Handle user interactions
      }}
      typingSpeed={30}
    />
  );
}

For advanced examples with streaming, multi-step forms, and more features, see:

📊 Comparison: Standalone vs markdown-flow-ui

| Aspect | Standalone Usage | With markdown-flow-ui | | --------------------- | ---------------------------------- | -------------------------------------- | | Setup Complexity | Medium - Need custom rendering | Low - Pre-built components | | Customization | High - Full control over UI | Medium - Theme/style customization | | Bundle Size | Smaller - Only remark plugin | Larger - Full React component library | | Framework Support | Any (React, Vue, vanilla JS, etc.) | React only | | Advanced Features | Manual implementation needed | Built-in (streaming, typewriter, etc.) | | Use Case | Custom UI requirements, non-React | Rapid prototyping, React projects |

🌐 MarkdownFlow Ecosystem

remark-flow is part of the MarkdownFlow ecosystem for creating personalized, AI-driven interactive documents:

  • markdown-flow - The main repository containing homepage, documentation, and interactive playground
  • markdown-flow-agent-py - Python agent for transforming MarkdownFlow documents into personalized content
  • remark-flow - Remark plugin to parse and process MarkdownFlow syntax in React applications
  • markdown-flow-ui - React component library for rendering interactive MarkdownFlow documents

💖 Sponsors

📄 License

MIT License - see LICENSE file for details.

🙏 Acknowledgments

📞 Support