Gallery
Every component and every registered plugin, rendered by running them — the static projection beside the animation, in all five modes.
Generated by npm run gallery:page from what flagstaff has registered. Do not edit by hand.
Every cell on this page is produced by running the component, through the same
hoist() a program uses and the same code path as flagstaff check — not by describing
it. Escapes are shown as ␛, carriage returns as ␍ and newlines as ⏎, so what a
terminal would swallow is visible.
The five modes
One component, five answers. The tty row is the animation; every other row is the
static projection, and it is the artifact — what a pipe, a CI log, an agent and a
screen reader all read. Notice that no other row contains an escape sequence.
spinner
The running glyph and the text, then the settled glyph — never a frame.
| mode | what it writes |
|---|---|
tty | ␛[?25l⠋ building␛[1G␛[0J⠙ building␛[1G␛[0J⠹ building␛[1G␛[0J⠸ building␛[1G␛[0J✔ built⏎ ␛[?25h |
pipe | … building⏎ ✔ built⏎ |
ci | … building⏎ ✔ built⏎ |
json | {"event":"spinner","state":{"text":"building"}}⏎ {"event":"spinner","state":{"text":"built","status":"ok"}}⏎ |
accessible | … building⏎ ✔ built⏎ |
progress
The count and the percentage; a bar of blocks says nothing in a log.
| mode | what it writes |
|---|---|
tty | ␛[?25l█████░░░░░░░ 12/30 files␛[1G␛[0J30/30 files · 100%⏎ ␛[?25h |
pipe | 12/30 files · 40%⏎ 30/30 files · 100%⏎ |
ci | 12/30 files · 40%⏎ 30/30 files · 100%⏎ |
json | {"event":"progress","state":{"done":12,"total":30,"label":"files"}}⏎ {"event":"progress","state":{"done":30,"total":30,"label":"files"}}⏎ |
accessible | 12/30 files · 40%⏎ 30/30 files · 100%⏎ |
tasks
Only what has settled, so a pipe is not told twice that a step is running.
| mode | what it writes |
|---|---|
tty | ␛[?25l✔ install⏎ ⠋ build␛[1G␛[1A␛[0J✔ install⏎ ⠙ build␛[1G␛[1A␛[0J✔ install⏎ ⠹ build␛[1G␛[1A␛[0J✔ install⏎ ⠸ build␛[1G␛[1A␛[0J✔ install⏎ ✔ build⏎ ␛[?25h |
pipe | ✔ install⏎ ✔ build⏎ |
ci | ✔ install⏎ ✔ build⏎ |
json | {"event":"tasks","state":{"tasks":[{"title":"install","status":"ok"},{"title":"build","status":"running"}]}}⏎ {"event":"tasks","state":{"tasks":[{"title":"install","status":"ok"},{"title":"build","status":"ok"}]}}⏎ |
accessible | ✔ install⏎ ✔ build⏎ |
box
The title and the text; a border is noise a screen reader reads character by character.
| mode | what it writes |
|---|---|
tty | ␛[?25l╭─ dev ──────────────────────────╮⏎ │ starting │⏎ ╰────────────────────────────────╯␛[1G␛[2A␛[0Jdev: ready on :3000⏎ ␛[?25h |
pipe | dev: starting⏎ dev: ready on :3000⏎ |
ci | dev: starting⏎ dev: ready on :3000⏎ |
json | {"event":"box","state":{"text":"starting","title":"dev"}}⏎ {"event":"box","state":{"text":"ready on :3000","title":"dev"}}⏎ |
accessible | dev: starting⏎ dev: ready on :3000⏎ |
table
One line per row of header-and-value pairs, parseable without knowing the drawing.
| mode | what it writes |
|---|---|
tty | ␛[?25l┌──────┬───────┐⏎ │ host │ tests │⏎ ├──────┼───────┤⏎ │ ora │ 99 │⏎ └──────┴───────┘␛[1G␛[4A␛[0Jhost: ora, tests: 99⏎ host: log-update, tests: 99⏎ ␛[?25h |
pipe | host: ora, tests: 99⏎ host: log-update, tests: 99⏎ |
ci | host: ora, tests: 99⏎ host: log-update, tests: 99⏎ |
json | {"event":"table","state":{"rows":[["ora","99"]]}}⏎ {"event":"table","state":{"rows":[["ora","99"],["log-update","99"]]}}⏎ |
accessible | host: ora, tests: 99⏎ host: log-update, tests: 99⏎ |
Spinner styles
What the registry holds right now. static is what every mode but tty prints; a style
without one is refused at register().
| style | static | frames | interval |
|---|---|---|---|
dots | … | ⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ | 80 ms |
line | … | - \ | / | 130 ms |
Two, because two is what the package ships. The corpus everyone already has is one line
away and is not bundled — fromCliSpinners(cliSpinners) adds 88 more
(dots2, dots3, dots4, dots5, dots6, dots7, …), each with … as its static
projection unless you say otherwise:
import cliSpinners from 'cli-spinners';
import { fromCliSpinners } from 'flagstaff/import';
import { register } from 'flagstaff/plugin';
register(fromCliSpinners(cliSpinners));Borders
Drawn by box(), from the same registry. fromCliBoxes(cliBoxes) adds the rest of that
corpus the same way.
round
╭────────────────────────────────╮
│ box('…', { border: 'round' }) │
╰────────────────────────────────╯single
┌────────────────────────────────┐
│ box('…', { border: 'single' }) │
└────────────────────────────────┘double
╔════════════════════════════════╗
║ box('…', { border: 'double' }) ║
╚════════════════════════════════╝bold
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ box('…', { border: 'bold' }) ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛classic
+--------------------------------+
| box('…', { border: 'classic' |
| }) |
+--------------------------------+none
box('…', { border: 'none' }) Writing one of your own
A plugin is one plain object, validated against schema.json — the same file that ships in the tarball:
export default {
name: 'pulse',
contract: 1,
spinners: {
pulse: { frames: ['◜', '◝', '◞', '◟'], interval: 90, static: '…' },
},
};npx flagstaff check ./pulse.mjscheck prints exactly the table at the top of this page for whatever you wrote, and exits
1 with the code and a fix if the schema refuses it. Leave the static off and it says so:
E_NO_STATIC_PROJECTION: pulse has no static projection
fix: give it a `static`: the text a pipe, an agent or a screen reader gets instead of the animation