Animated terminal progress bars, spinners, and status messages for Node.js CLI applications.
npm install termpulseimport { Bar } from 'termpulse'
const bar = new Bar()
bar.start()
bar.message('Starting...')
setTimeout(() => bar.stop('Done!'), 3000)const { Bar } = require('termpulse')const bar = new Bar({
length: 80, // bar width (default: terminal width)
prefix: '[', // leading clamp (default: '[')
suffix: ']', // trailing clamp (default: ']')
character: '-', // moving character (default: '-')
before: ' ', // fill before position (default: ' ')
after: ' ', // fill after position (default: ' ')
text: '', // label shown beside the bar (default: '')
reverse: false, // put label before the bar (default: false)
interval: 35, // ms per frame (default: 35)
mode: 'bounce', // 'bounce' | 'loop' | 'loop-reverse' (default: 'bounce')
progress: 0, // 0–1 enables determinate mode
colors: [], // hex gradient array, e.g. Bar.COLORS.rainbow
bgColors: [], // background gradient
colorFill: false, // apply dimmed gradient to fill chars
colorCycle: 0, // gradient shift speed in cycles/second
shimmer: 0, // brightness wave amplitude 0–1
onBounce: () => {}, // fires on direction reversal
onLoop: () => {}, // fires on position wrap
onComplete: () => {}, // fires once when progress reaches 1
successColor: '#...', // ✔ color override (default: green)
failColor: '#...', // ✖ color override (default: red)
warnColor: '#...', // ⚠ color override (default: yellow)
infoColor: '#...', // ℹ color override (default: blue)
glyphs: true, // include ✔ ✖ ⚠ ℹ in non-TTY output (default: true)
})All properties can be changed while the bar is running.
bar.prefix = 'Loading: ['
bar.suffix = '] 42%'
bar.character = '='
bar.before = '-'
bar.after = '-'
bar.empty = '·' // sets both before and after
bar.text = 'Uploading…'
bar.mode = 'loop'
bar.progress = 0.5 // switch to determinate
bar.progress = undefined // back to animation
bar.colors = Bar.COLORS.rainbow
bar.colorCycle = 0.5
bar.shimmer = 0.7Pass an array of hex colors. The gradient is interpolated across the bar by position.
bar.colors = ['#0000ff', '#ff0000'] // blue → red
bar.colors = Bar.COLORS.rainbow // presetBuilt-in presets: blueRed, redBlue, rainbow, heat, cool, sunset.
| Method | Description |
|---|---|
bar.start() |
Begin animation, hide cursor |
bar.stop(msg?) |
Stop animation, restore cursor, optionally print message |
bar.message(string) |
Update the label beside the bar |
bar.succeed(msg?) |
Print ✔ msg line |
bar.fail(msg?) |
Print ✖ msg line |
bar.warn(msg?) |
Print ⚠ msg line |
bar.info(msg?) |
Print ℹ msg line |
import { Spinner } from 'termpulse'
const spinner = new Spinner({
frames: Spinner.FRAMES.braille,
colors: Bar.COLORS.rainbow,
colorCycle: 1.0,
text: 'Loading…',
})
spinner.start()
setTimeout(() => spinner.stop('Done!'), 3000)const spinner = new Spinner({
frames: Spinner.FRAMES.braille, // frame array (default: braille)
prefix: '', // string prepended to the spinner line
suffix: '', // string appended to the spinner line
text: '', // label shown beside the spinner
reverse: false, // put label before the spinner (default: false)
interval: 80, // ms per frame (default: 80)
colors: [], // hex gradient
bgColors: [], // background gradient
colorCycle: 0, // gradient shift speed in cycles/second
shimmer: 0, // brightness wave amplitude 0–1
onSpin: () => {}, // fires after each full frame cycle
successColor: '#...', // ✔ color override (default: green)
failColor: '#...', // ✖ color override (default: red)
warnColor: '#...', // ⚠ color override (default: yellow)
infoColor: '#...', // ℹ color override (default: blue)
glyphs: true, // include ✔ ✖ ⚠ ℹ in non-TTY output (default: true)
})Built-in frame sets: braille, dots, line, arrow, bounce.
All properties can be changed while the spinner is running.
spinner.frames = Spinner.FRAMES.dots
spinner.text = 'Still working…'
spinner.colors = Bar.COLORS.heat
spinner.colorCycle = 0.5
spinner.shimmer = 0.8| Method | Description |
|---|---|
spinner.start() |
Begin animation, hide cursor |
spinner.stop(msg?) |
Stop animation, restore cursor, optionally print message |
spinner.message(string) |
Update the label beside the spinner |
spinner.succeed(msg?) |
Print ✔ msg line |
spinner.fail(msg?) |
Print ✖ msg line |
spinner.warn(msg?) |
Print ⚠ msg line |
spinner.info(msg?) |
Print ℹ msg line |
Interactive single-item picker with animated highlight and optional descriptions.
import { Select } from 'termpulse'
const select = new Select()
const choice = await select.ask('Which framework?', [
{ label: 'React' },
{ label: 'Vue' },
{ label: 'Svelte' },
])
if (choice) console.log(`Picked: ${choice.label}`)const { Select } = require('termpulse')Navigate with ↑ / ↓ or number keys. Press Enter to confirm, or choose the auto-appended Skip item to return null.
Each item requires a label and accepts an optional description:
const items = [
{ label: 'Production', description: 'live traffic' },
{ label: 'Staging', description: 'QA sign-off required' },
{ label: 'Development', description: 'breaks often' },
]You can attach any extra fields to items — whatever type you pass in is what you get back:
interface Env { label: string; url: string }
const env = await select.ask<Env>('Target?', [
{ label: 'Production', url: 'https://example.com' },
])
if (env) deploy(env.url)const select = new Select({
skipLabel: 'Skip', // label for the skip/none item (default: 'Skip')
promptGlyph: '◆', // glyph before the prompt line (default: '◆')
promptColor: '#...', // color for glyph and selected marker (default: green)
descriptionColor: '#...', // color for item descriptions (default: blue)
selectedPrefix: '>', // marker shown left of selected item (default: '>')
selectedSuffix: '<', // marker shown right of selected item (default: '<')
colors: [], // hex gradient for animated selected marker
colorCycle: 1.0, // gradient shift speed in cycles/second
shimmer: 0, // brightness wave amplitude 0–1
interval: 80, // animation frame interval in ms
})| Method | Description |
|---|---|
select.ask(prompt, items) |
Show prompt and list, resolve with selected item or null if skipped |
Print a one-line status message without needing an active spinner or bar.
import { log } from 'termpulse'
log.succeed('Deployment complete')
log.fail('Connection refused')
log.warn('Deprecated API in use')
log.info('Listening on port 3000')const { log } = require('termpulse')All four methods accept an optional message and an optional options object:
log.succeed(message?, options?)
log.fail(message?, options?)
log.warn(message?, options?)
log.info(message?, options?)interface LogOptions {
color?: string // hex or ANSI color override for the glyph
glyphs?: boolean // include ✔ ✖ ⚠ ℹ in non-TTY output (default: true)
}| Method | Glyph | Default color |
|---|---|---|
log.succeed(msg?, opts?) |
✔ |
green |
log.fail(msg?, opts?) |
✖ |
red |
log.warn(msg?, opts?) |
⚠ |
yellow |
log.info(msg?, opts?) |
ℹ |
blue |
When stdout is not a TTY (piped to a file, CI log, etc.) termpulse automatically adjusts:
- The animation loop does not start — no bar or spinner frames are written.
- ANSI colors and cursor escape codes are suppressed.
Select.ask()returnsnullimmediately without rendering.stop(msg?),succeed,fail,warn, andinfoonBar/Spinner, and alllog.*methods, still write their message as plain text, prefixed with the status glyph by default:
✔ Build complete
✖ Connection failed
⚠ Retrying…
ℹ 3 files skipped
Set glyphs: false to omit the glyph prefix and write only the message text.
MIT © Jay Deaton