react-command-flow
v1.7.12
Published
A beautiful interactive CLI-style command interface for React apps
Maintainers
Readme
🖥️ React Command Flow
A beautifully interactive CLI-style command interface for React apps.
Type commands, flow through steps, and build dynamic, conversational UI experiences.
✨ Features
- 🖋 Terminal-style input & output
- 📋 Step-based command flows
- ✅ Built-in validation (sync & async)
- 🔀 Conditional prompts, branching logic
- ⚡ Dynamic suggestions & tab completion
- 💅 Theme support
- 🔍 Help & clear commands out of the box
- 🔌 Easy to extend and customize
📦 Installation
npm install react-command-flow⚡ Usage
import React from "react";
import { CommandLine } from "react-command-flow";
import { commands } from "./commands";
export default function App() {
return (
<div style={{ padding: "2rem" }}>
<h1>React Command Flow Demo</h1>
<CommandLine commands={commands} />
</div>
);
}📚 Defining Commands
const colors = ["Red", "Green", "Blue", "Yellow", "Purple"];
const shapes = ["Circle", "Square", "Triangle", "Rectangle", "Hexagon"];
const commands = {
"color-shape-quiz": {
description: "Start your color and shape quiz 🌈🔺",
steps: [
{
prompt: async () => {
await new Promise((res) => setTimeout(res, 1000)); // async wait
return `What is your favorite color?\n${colors
.map((c, i) => `${i + 1}. ${c}`)
.join("\n")}\nEnter color index:`;
},
field: "colorIndex",
validate: async (val) => {
return ["1", "2", "3", "4", "5"].includes(val)
? true
: "❌ Please select a number between 1 and 5.";
},
redirect: (val) => {
const color = colors[parseInt(val) - 1];
if (color === "Red") return "color-red-flow";
if (color === "Green") return "color-green-flow";
if (color === "Blue") return "color-blue-flow";
if (color === "Yellow") return "color-yellow-flow";
if (color === "Purple") return "color-purple-flow";
return "color-other-flow";
},
},
],
onComplete: () => "Redirecting...",
},
"color-red-flow": {
description: "Red color path 🔴",
hidden: true,
steps: [
{
prompt: "What shape do you associate with Red?",
field: "redShape",
validate: (val) => {
return shapes.includes(val) ? true : "❌ Please enter a valid shape.";
},
},
],
onComplete: async () => {
await new Promise((res) => setTimeout(res, 1000)); // fake async post
return "✅ Red is a powerful color!";
},
},
"color-green-flow": {
description: "Green color path 🟢",
hidden: true,
steps: [
{
prompt: "What shape do you associate with Green?",
field: "greenShape",
validate: (val) => {
return shapes.includes(val) ? true : "❌ Please enter a valid shape.";
},
},
],
onComplete: async () => {
await new Promise((res) => setTimeout(res, 1000)); // fake async post
return "✅ Green is a calming color!";
},
},
"color-blue-flow": {
description: "Blue color path 🔵",
hidden: true,
steps: [
{
prompt: "What shape do you associate with Blue?",
field: "blueShape",
validate: (val) => {
return shapes.includes(val) ? true : "❌ Please enter a valid shape.";
},
},
],
onComplete: async () => {
await new Promise((res) => setTimeout(res, 1000)); // fake async post
return "✅ Blue is a soothing color!";
},
},
"color-yellow-flow": {
description: "Yellow color path 🟡",
hidden: true,
steps: [
{
prompt: "What shape do you associate with Yellow?",
field: "yellowShape",
validate: (val) => {
return shapes.includes(val) ? true : "❌ Please enter a valid shape.";
},
},
{
prompt: "What emotion do you associate with Yellow?",
field: "yellowEmotion",
validate: (val) => {
const emotions = ["happiness", "energy", "warmth", "caution"];
return emotions.includes(val.toLowerCase())
? true
: "❌ Please enter a valid emotion (happiness, energy, warmth, caution).";
},
},
{
prompt: "What object do you associate with Yellow?",
field: "yellowObject",
validate: (val) => {
return val.length > 0 ? true : "❌ Please enter a valid object.";
},
},
],
onComplete: async () => {
await new Promise((res) => setTimeout(res, 1000)); // fake async post
return "✅ Yellow is a bright and cheerful color!";
},
},
"color-purple-flow": {
description: "Purple color path 🟣",
hidden: true,
steps: [
{
prompt: "What shape do you associate with Purple?",
field: "purpleShape",
validate: (val) => {
return shapes.includes(val) ? true : "❌ Please enter a valid shape.";
},
},
],
onComplete: async () => {
await new Promise((res) => setTimeout(res, 1000)); // fake async post
return "✅ Purple is a royal color!";
},
},
"color-other-flow": {
description: "Other color path 🌈",
hidden: true,
steps: [],
onComplete: () => "💛 All colors are beautiful!",
},
help: {
description: "List available commands",
steps: [],
onComplete: (_, __, all) => {
return Object.entries(all)
.filter(([_, c]) => !c.hidden)
.map(([name, c]) => `- ${name}: ${c.description}`)
.join("\n");
},
},
clear: {
description: "Clear the screen",
steps: [],
onComplete: () => "__CLEAR__",
},
};
// Alias added outside
commands["?"] = commands.help;
export default commands;🧠 Advanced Examples
- 🔁 Async prompt resolution
- ✅ Custom validation per step
- 🔀
onSteplogic to transform values - 🧭 Conditional steps (based on previous answers)
- 📦 Redirects to subcommands (optional)
- 🔄 Repeat functionality with custom repeat questions
🎯 Repeat Configuration
Commands can use a comprehensive repeat object for full control over the repeat behavior:
const commands = {
"job-application": {
description: "Job application flow",
steps: [
/* ... */
],
onComplete: (data) => `Application submitted for ${data.name}`,
repeat: {
mode: "ask",
question: "💼 Submit another application ({{yes}},{{no}})?",
yes: "sure",
no: "nope",
wrongInput: "Please select either {{yes}} or {{no}}:",
yesReply: "Starting new application...",
noReply: "Application process completed.",
targetCommand: "other-command", // Optional: redirect to different command
},
},
survey: {
description: "User survey",
steps: [
/* ... */
],
onComplete: (data) => "Survey completed!",
repeat: {
mode: "ask",
// Function-based custom repeat question
question: ({ yesOption, noOption, commandName }) =>
`🔄 Take the survey again? Type '${yesOption}' to continue or '${noOption}' to finish.`,
yes: "y",
no: "n",
wrongInput: "Please select either {{yes}} or {{no}}:",
yesReply: "Let's do another survey!",
noReply: "Thanks for participating!",
},
},
"async-demo": {
description: "Async repeat question demo",
steps: [
/* ... */
],
onComplete: (data) => "Task completed!",
repeat: {
mode: "ask",
// Async function-based custom repeat question
question: async ({ yesOption, noOption, commandName }) => {
// Simulate async operation (e.g., checking user preferences)
await new Promise((resolve) => setTimeout(resolve, 500));
return `🤔 Continue with another task? Type '${yesOption}' or '${noOption}'.`;
},
yes: "y",
no: "n",
wrongInput: "Please select either {{yes}} or {{no}}:",
yesReply: "Great! Let's continue...",
noReply: "Task session ended.",
},
},
};Repeat Object Properties:
mode- "yes", "no", or "ask"question- Custom repeat question (string or function)yes- Custom "yes" option textno- Custom "no" option textwrongInput- Custom error message for invalid inputyesReply- Custom message when user selects "yes"noReply- Custom message when user selects "no"targetCommand- Optional: different command to run on repeat
Available placeholders for string-based questions and messages:
{{yes}}- The "yes" option value{{no}}- The "no" option value{command}- The command name (legacy format)
Function parameters for dynamic questions:
yesOption- The "yes" option stringnoOption- The "no" option stringcommandName- The name of the command
🖌️ Theming
Supports custom themes with colors and fonts.
You can pass a theme prop to <CommandLine />.
Colored and highlighted segments (§tag[...]§)
Any output or prompt string may contain markers of the form §tag[text]§.
The tag picks a color from the theme:
| tag | theme key |
|-----|-----------|
| error | errorColor |
| warn / warning | warningColor |
| success / output | successColor |
| accent / value | accentColor |
| title | titleColor |
| muted | mutedColor |
| mark | markBackground (background) + markColor (text) |
| key | keyBackground + keyColor (default to the mark colors), no horizontal padding |
| any tag with tagColor / tagBackground in the theme | those (since 1.7.10, e.g. ok -> okColor + okBackground) |
| any other string | used as a CSS color |
Every built-in tag also honours a <tag>Background theme key (e.g.
errorBackground).
mark is a highlight: it paints a background behind the text. key is the
same highlight for a single shortcut letter inside a word ([§key[a]§dd]),
without padding so the word stays whole. Since 1.7.8 markers are rendered in
the live prompt too, not only in output lines. The typewriter effect
keeps the highlights while typing (since 1.7.11; 1.7.8 to 1.7.10 showed
them once typing completed). The prompt echoed into the history keeps its
markers. Since 1.7.9 the suggestion line set with setSuggestionText
renders markers as well.
prompt: "Choose the §mark[kind of relation]§ to add for «name» [§key[a]§dd/§key[q]§uit]:",
output: "§error[Unknown command.]§ Type §accent[help]§ for the list.",Tab completion, back/exit matching and sessionData always see the
plain text (markers stripped).
💡 Example Commands You Can Try
helloprofile-setupselect-serverhelpclear
📜 License
MIT — free to use, modify, contribute.
Made with 💛 by Stam & React Command Flow Contributors.
⭐ Star this repo if you like it!
Let's build the coolest CLI UIs in the React world 🌍
