Semola

Prompts

Interactive CLI prompts for Bun terminals

Ask users questions in the terminal: text, passwords, confirms, numbers, and selects.

Needs a real TTY (or a custom PromptRuntime for tests). Ctrl+C cancels.

Import

import {
  input,
  password,
  confirm,
  number,
  select,
  multiselect,
} from "semola/prompts";

Quick start

The first prompt requires a project name; the second returns a boolean, defaulting to true when the user presses Enter.

const name = await input({
  message: "Project name",
  placeholder: "my-app",
  required: true,
});

const proceed = await confirm({
  message: "Create the project?",
  defaultValue: true,
});

Prompt kinds

Shared options across prompts: message, optional validate, optional transform. Pass a second argument implementing PromptRuntime so prompts do not touch the real terminal.

Selects take choices with value, and optional label, hint, disabled. Multiselect: space toggles, a toggles all enabled (second press clears them).

Defaults

PromptNotable defaults
confirmdefaultValue: false; Yes/No labels
passwordOmit mask to hide text entirely
numberAlways requires a finite value
multiselectdefaultValue: []

Examples

Text input with input()

input() reads a line of text and enforces the required value before returning.

const name = await input({
  message: "Project name",
  placeholder: "my-app",
  required: true,
});

Hidden input with password()

password() masks each typed character and returns the secret text.

const secret = await password({
  message: "API token",
  mask: "*",
});

Boolean input with confirm()

confirm() asks a yes-or-no question and uses true when the user accepts the default.

const proceed = await confirm({
  message: "Create the project?",
  defaultValue: true,
});

Numeric input with number()

number() rejects non-finite values and enforces the configured range.

const port = await number({
  message: "Port",
  min: 1,
  max: 65535,
  defaultValue: 3000,
});

One choice with select()

select() returns the selected choice's typed value; labels and hints only affect display.

const framework = await select({
  message: "Framework",
  choices: [
    { value: "bun", label: "Bun" },
    { value: "node", label: "Node", hint: "legacy" },
  ],
});

Many choices with multiselect()

multiselect() returns selected values and keeps prompting until at least one is selected.

const features = await multiselect({
  message: "Features",
  choices: [
    { value: "auth" },
    { value: "queue" },
    { value: "orm" },
  ],
  min: 1,
});

Validate input

Returning an error string keeps the prompt open; returning undefined accepts the value.

const email = await input({
  message: "Email",
  validate: (value) =>
    value.includes("@") ? undefined : "Enter a valid email",
});

Test with a mock runtime

Passing a runtime as the second argument keeps the prompt away from the real terminal.

await input({ message: "Name" }, mockRuntime);

Reference

ExportMeaning
inputText
passwordSecret text
confirmYes / No
numberNumeric input
selectSingle choice
multiselectMultiple choices

Pass runtime as the second argument for non-TTY use or tests.

On this page