terminice 1.1.0
terminice: ^1.1.0 copied to clipboard
The ultimate toolkit for Dart CLI apps. 30+ ready-to-use components (prompts, searchable selectors, pickers, indicators, editors), 11 universal themes, and zero boilerplate.

Terminal interfaces, finally done right.
The ultimate UI toolkit for Dart command-line apps.
terminice gives you 30+ ready-to-use terminal components—from simple prompts to complex searchable menus and config editors.
Universal theming out of the box. Every single prompt, selector, picker, and indicator automatically adapts to your chosen theme. Pick from 11 built-in themes or customize your own via a fluent API.
Zero boilerplate. No widget trees or manual state management. Just call a method and get a polished, keyboard-driven UI instantly.
Features #
- 30+ built-in prompts — Text, password, confirm, multiline editor, slider, range, rating, date, form, and 7 selector variants. Plus pickers for files, paths, colors, and dates.
- 11 color themes — Dark, Matrix, Fire, Pastel, Ocean, Monochrome, Neon, Arcane, Phantom, and display modes (Minimal, Compact, Verbose). Mix and match colors, glyphs, and features freely.
- Config editor — A searchable, nested settings editor that composes existing prompts into a unified configuration flow with live theme switching, validation, and JSON serialization.
- Progress and task feedback — Loading spinners, inline spinners, progress bars, progress dots, and async task helpers for long-running
FutureandStreamwork. - Zero boilerplate — One import, one global instance, chainable theme accessors. No setup, no context objects, no widget trees.
- Cross-platform — Works on Linux, macOS, and Windows. Backed by a testable terminal abstraction you can swap for custom I/O.
- Modular architecture — Built on
terminice_core, which exposes navigation primitives, prompt scaffolds, and rendering utilities for when you need full control.
Table of Contents
- Features
- How to use
terminice - Instance Configuration & Fallback
- The Terminice Catalogue
- Theming & Display Modes
- Testing Terminice CLIs
- Quick Start
📖 How to use terminice #
terminice is designed to be as frictionless as possible. There are no widget trees, no state management classes, and no complex setup.
The Global Instance
Everything starts with the global terminice instance. All prompts, selectors, and indicators are exposed as extension methods on this single object.
import 'package:terminice/terminice.dart';
void main() {
// Use the global instance directly
terminice.text('What is your name?');
}
Prompt Execution & Async Tasks
Prompts, selectors, pickers, and config editors are synchronous. You do not need to await user-input prompts; each call blocks until the user provides input or cancels, then returns the result directly.
// No async/await required!
final name = terminice.text('Name');
final age = terminice.slider('Age', min: 0, max: 100);
print('Hello $name, you are $age years old.');
For long-running work, terminice also includes async task helpers. They render status while your Future or Stream is active, return the typed result on success, and rethrow errors after rendering the final failure or cancel status.
final result = await terminice.task('Publishing', run: publish);
await terminice.progressTask(
'Uploading',
total: files.length,
run: (progress) async { ... },
);
final items = await terminice.trackStream(
'Downloading',
stream,
total: count,
);
Shared API Design
Most tools in the library share a consistent set of parameters to keep the API predictable:
promptorlabel: The main text displayed to the user.initialordefaultValue: The starting value or selection.required: (On text/password prompts) Prevents the user from submitting an empty value.validator: A function that returns aString?error message if the input is invalid.
Cancellation Behavior
Prompts use a consistent cancellation policy:
- Nullable prompts return
nullwhen cancelled. - Value prompts return the exact caller-supplied
initialor default value when cancelled, even if the active interactive value is clamped while editing. - Config editor fields leave their existing value unchanged when a field edit is cancelled.
- List and multi selectors return a non-null list; cancelled multi-selection prompts may return
[].
Validation
Validating input is built directly into the prompts. Simply provide a validator function that returns a String error message if the input is invalid, or null if it passes. Returning '' is also accepted as a legacy-compatible success value.
final email = terminice.text(
'Email address',
validator: (val) {
if (!val.contains('@')) return 'Please enter a valid email';
return null; // Input is valid
},
);
For basic required fields, you don't even need a custom validator. Just use the required flag:
final name = terminice.text(
'Full name',
required: true, // Automatically prevents empty submissions
);
Validation works seamlessly with other tools too, like the multiline editor:
final commitMsg = terminice.multiline(
'Commit message',
validator: (lines) {
if (lines.isEmpty) return 'Message cannot be empty';
if (lines.first.length > 50) return 'First line must be under 50 chars';
return null;
},
);
Theming & Chaining
You don't need to pass a theme object to every prompt. Instead, terminice uses a fluent, chainable API. Every theme or display mode getter returns a new, scoped Terminice instance.
// Chain a theme and a display mode directly
final choice = terminice.ocean.compact.confirm(message: 'Save changes?');
// Or save a scoped instance for reuse
final t = terminice.neon.minimal;
final user = t.text('Username');
final pass = t.password('Password');
Display modes preserve the active colors and glyphs, so both of these chains produce Ocean colors with compact display features:
final a = terminice.ocean.compact;
final b = terminice.compact.ocean;
Centralized Instance Configuration
Each Terminice instance carries a single immutable TerminiceConfig. That config controls the effective theme for component calls that use the caller theme, including prompts, selectors, pickers, most guides, and indicators, plus fallback behavior for covered high-level prompts.
final t = terminice.withConfig(
const TerminiceConfig(
baseTheme: PromptTheme.ocean,
featureOverride: DisplayFeatures.compact,
compatibility: TerminalCompatibility.basic,
fallbackMode: TerminiceFallbackMode.auto,
),
);
final name = t.text('Name');
final role = t.searchSelector(
prompt: 'Role',
options: ['Admin', 'User'],
);
baseThemeis the original theme chosen by the caller.featureOverrideapplies a display mode such asDisplayFeatures.compact.compatibilityadapts the theme for terminal capability.fallbackModedecides when covered high-level prompts use line-mode fallback.TerminiceConfig.effectiveThemeis the theme produced from those values.defaultThemeexposes that effective theme on theTerminiceinstance.
You can build the same configuration fluently:
final t = terminice.ocean.compact.basic.autoFallback;
Use withConfig(...) to replace the whole instance config while preserving the terminal. Use withTheme(...) or themed(...) to change the base theme while preserving display mode, compatibility, fallback mode, and terminal. Use withCompatibility(...) or withFallbackMode(...) when you want to pass the enum explicitly.
Because configuration lives on the instance, changing the instance changes theme and behavior consistently across component calls:
final plain = terminice.legacy.fallback;
final project = plain.text('Project name');
final confirm = plain.confirm(message: 'Create $project?');
Compatibility Modes
Compatibility modes are styling transforms. They do not inspect the terminal; choose the mode you want for the Terminice instance.
terminice.modern- Default behavior. Preserves the active theme exactly.terminice.basic- Uses ASCII glyphs and simpler hints/display while keeping ANSI colors.terminice.legacy- Uses ASCII glyphs, disables ANSI colors, and keeps output minimal with no hints.
final readable = terminice.fire.basic;
final plainText = terminice.ocean.legacy;
Fallback Policies
The default behavior is unchanged: terminice uses the rich interactive prompts unless you opt into fallback.
terminice.interactive- Forces rich prompts. This is the defaultfallbackMode.terminice.autoFallback- Uses line-mode fallback when input or output is not a terminal.terminice.fallback- Always uses line-mode fallback for covered high-level prompts.
final ci = terminice.autoFallback.basic;
final confirmed = ci.confirm(message: 'Continue?');
Line-mode fallback uses simple text and numbered prompts instead of raw-mode keyboard UIs. Password fallback reads a normal line; it does not mask input in line mode.
Fallback coverage currently includes text, password, confirm, form, searchSelector, gridSelector, checkboxSelector, choiceSelector, tagSelector, toggleGroup, commandPalette, slider, range, rating, and the focused enum/theme selects used by the config editor.
Components without fallback coverage still receive the effective theme when they use the caller theme, but remain rich/interactive until fallback support is added. Today that includes pickers, guides such as cheatSheet, helpCenter, and hotkeyGuide, manual indicator controller calls such as show(...), multiline, date, and the config editor shell itself; config editor field prompts that call covered components still inherit the instance fallback policy. Async task helpers use plain task rendering in fallback/plain modes.
📚 The terminice Catalogue #
Explore the complete collection of tools available in terminice. Every tool is fully themeable and ready to use with zero setup.
📝 Prompts
Standard input controls for gathering user data.
text— Single-line text input.password— Secure, masked text input.confirm— Yes/No boolean confirmation.multiline— Multi-line text editor.slider— Numeric selection along a single axis.range— Dual-thumb slider for selecting a min/max range.rating— Star-based rating input.date— Date picker.form— Form prompt.
🎯 Selectors
Interactive menus for choosing from predefined options.
searchSelector— Filterable list of options.choiceSelector— Card-based choices with optional multi-select.checkboxSelector— Multi-select list with checkboxes.gridSelector— 2D grid selection.tagSelector— Select and manage multiple tags.toggleGroup— Independent editable boolean switches.commandPalette— Global command launcher with fuzzy search.
🗂️ Pickers
Specialized components for selecting complex data types.
filePicker— Browse and select files from the filesystem.pathPicker— Browse and select directories.colorPicker— Interactive color selection.datePicker— Calendar-based date selection.
⏳ Indicators
Visual feedback for long-running tasks.
loadingSpinner— Full-featured loading animation.inlineSpinner— Compact loading animation for inline use.progressBar— Standard progress bar with percentage.inlineProgressBar— Compact progress bar.progressDots— Minimalist dot-based progress indicator.
🔁 Async Task Helpers
Future and stream wrappers for long-running work.
task— Run a task with spinner or dots status.progressTask— Run a task with determinate progress.trackStream— Collect a stream while tracking progress.TaskProgress— Mutable progress state passed to progress tasks.TaskDisplay— Rendering mode for task helpers.TaskFinalBehavior— Final output policy for task helpers.
🔄 Flow Composition
Chain several prompts and selectors into one sequential CLI workflow.
⚙️ Configuration & Utilities
Advanced tools for building full CLI applications.
configEditor— A searchable, nested settings editor for complex configurations.cheatSheet— Display a quick reference guide.helpCenter— Interactive help documentation viewer.hotkeyGuide— Display available keyboard shortcuts.themeDemo— Showcase all available themes and colors.
▧ Theming & Display Modes #
terminice makes styling effortless. Every prompt adapts automatically to the selected theme and display mode. Just chain the theme or mode accessor before calling any prompt.
| Dark | Matrix | Fire |
|---|---|---|
![]() |
![]() |
![]() |
| Pastel | Ocean | Monochrome |
![]() |
![]() |
![]() |
| Neon | Arcane | Phantom |
![]() |
![]() |
![]() |
❖ Available Themes
| Theme | Description | Usage |
|---|---|---|
| ◉ Dark | The default. Clean, subtle, professional. | terminice.dark |
| ▤ Matrix | Hacker green on black. | terminice.matrix |
| ▧ Fire | Warm reds, oranges, and yellows. | terminice.fire |
| ▦ Pastel | Soft, muted, and friendly colors. | terminice.pastel |
| ▪ Ocean | Deep blues and calming cyans. | terminice.ocean |
| ▣ Monochrome | Pure black and white. High contrast. | terminice.monochrome |
| ▱ Neon | Bright, high-energy cyberpunk colors. | terminice.neon |
| ▰ Arcane | Mystical purples and magentas. | terminice.arcane |
| ◈ Phantom | Ghostly grays and ethereal tones. | terminice.phantom |
◩ Display Modes
Control the verbosity and framing of your prompts:
verbose(Default) — Full borders, contextual hints, and clear separation.compact— Keeps borders but removes hints for a tighter layout.minimal— Strips away borders and frames for a classic, inline CLI feel.
Display modes only override display features. Active colors and glyphs are preserved, so terminice.ocean.compact and terminice.compact.ocean resolve to the same effective theme.
⌨ Example
// Combine theme and display mode
final name = terminice.ocean.compact.text('Name');
// Store a themed instance for consistency
final t = terminice.fire.minimal;
final age = t.text('Age');
final role = t.searchSelector(prompt: 'Role', options: ['Admin', 'User']);
Quick Start #
Get up and running in seconds. No setup required—just import and call.
1. Ask for input
Gather text, passwords, or confirmations with a single line of code.
final name = terminice.text('Project name');
final ship = terminice.confirm(prompt: 'Ship to production?', message: 'Are you sure?');
2. Build interactive menus
Need a searchable list? It's just as easy.
final lang = terminice.searchSelector(
prompt: 'Language',
options: ['Dart', 'Go', 'Rust', 'TypeScript'],
showSearch: true,
);
3. Change themes instantly
Want a different vibe? Just chain a theme name before your prompt.
// Hacker green
final secret = terminice.matrix.password('Passphrase');
// High-energy cyberpunk
final memory = terminice.neon.slider('Memory', min: 128, max: 2048);
For a complete list of available tools, check out The Terminice Catalogue below.
Testing Terminice CLIs #
Serious CLIs need tests that do not depend on a real terminal, real stdin, or timing-sensitive stdout capture. Import the sidecar testing library from tests:
import 'package:test/test.dart';
import 'package:terminice/testing.dart';
package:terminice/testing.dart re-exports the public Terminice API, core mock-terminal testing primitives, and TerminiceTester. It is intentionally a test sidecar; these utilities are not exported from package:terminice/terminice.dart.
Fallback and Line-Mode Flows #
Use TerminiceTester.fallback for deterministic line-mode coverage. This is ideal for testing flow logic, validators, cancellation behavior, and CI-safe prompt paths.
test('creates a project from fallback input', () {
final tester = TerminiceTester.fallback(lines: ['demo', 'yes']);
final result = tester.run(
(t) => t
.flow('Create project')
.text('name', 'Project name')
.confirm('create', message: 'Create project?')
.run(),
);
expect(result.toMap(), equals({'name': 'demo', 'create': true}));
expect(tester.output.plainText, contains('Create project?'));
});
Interactive Key Scripts #
Use TerminiceTester.interactive with TerminalScript when you want to exercise the rich raw-mode prompt path. Scripts are reusable and can queue text, key presses, arrows, Enter, Escape, Tab, Space, and Ctrl keys.
test('chooses No in the interactive confirm prompt', () {
final tester = TerminiceTester.interactive(
script: TerminalScript.build((script) => script.right().enter()),
);
final result = tester.run(
(t) => t.confirm(message: 'Publish release?'),
);
expect(result, isFalse);
expect(tester.output.containsAnsiControls, isTrue);
});
Output Assertions #
Every tester exposes tester.output, a TerminalOutputSnapshot with raw, plainText, normalizedText, plainLines, and containsAnsiControls. Prefer plainText when ANSI styling is irrelevant, normalizedText for stable line assertions, and containsAnsiControls when you need to prove a path rendered with or without terminal control output.
final tester = TerminiceTester.nonInteractive();
final count = await tester.runAsync(
(t) => t.task<int>(
'Warm cache',
run: () async => 42,
success: 'cache ready',
),
);
expect(count, 42);
expect(tester.output.normalizedText, equals('OK: cache ready'));
expect(tester.output.containsAnsiControls, isFalse);
text - Single-Line Text Input #
Collect a single trimmed string with optional placeholder text and inline validation. This is the default building block for names, IDs, short notes, paths, and other one-line values.
text(String prompt, {placeholder, validator, required})Opens a themed text input frame.prompt- Title displayed above the input.placeholder- Dimmed hint text shown while the input is empty.required- Defaults totrue; empty submissions are blocked with an inline error.validator- OptionalString? Function(String)that receives trimmed input. Returnnullfor valid input, or a non-empty error message to block confirmation. Returning''is still accepted as success for backwards compatibility.- Returns
String?- The trimmed input on Enter, ornullwhen the user cancels with Esc/Ctrl+C. - Controls - Type normally, Backspace deletes, Enter confirms, Esc cancels.
Examples
final projectName = terminice.text(
'Project name',
placeholder: 'my_awesome_app',
);
if (projectName == null) {
print('No project created.');
}
final nickname = terminice.text(
'Nickname',
required: false,
placeholder: 'Optional',
);
print('Nickname: ${nickname?.isEmpty == true ? "(none)" : nickname}');
final email = terminice.text(
'Email address',
placeholder: 'ada@example.com',
validator: (value) {
if (!value.contains('@')) return 'Enter a valid email address';
return null;
},
);
Why use this? Instead of manually wiring stdin reads, trimming, empty checks, and retry loops,
textgives you a themed prompt with validation and cancellation behavior in one call.
⤴️ Back → The terminice Catalogue
password - Masked Text Input #
Collect secrets without echoing the raw value. The password prompt uses the same text engine as text, but masks input, can optionally reveal with a hotkey, and can run a two-field verification flow.
password(String prompt, {required, maskChar, allowReveal, verify})Opens a masked text prompt.prompt- Title displayed above the password field.required- Defaults totrue; empty submissions are blocked.maskChar- Character repeated for each typed character. Defaults to'•'.allowReveal- Defaults totrue; when enabled, Ctrl+R toggles plain-text visibility.verify- Defaults tofalse; whentrue, asks for password and confirmation in one form and blocks mismatches.- Returns
String?- The entered password on confirmation, ornullon cancel. - Controls - Type normally, Backspace deletes, Enter confirms, Ctrl+R reveals when enabled, Esc cancels.
Examples
final token = terminice.password('API token');
if (token == null) {
print('Token entry cancelled.');
}
final pin = terminice.password(
'Deployment PIN',
maskChar: '*',
allowReveal: false,
);
final newPassword = terminice.password(
'New account password',
verify: true,
);
if (newPassword != null) {
print('Password accepted.');
}
Why use this? Use
passwordwhen the input should behave like text but render safely. Useverify: truewhen a typo would be expensive and you want the confirmation logic built in.
⤴️ Back → The terminice Catalogue
confirm - Yes/No Confirmation #
Ask for a boolean decision with two labeled choices and a configurable default focus. It is ideal for destructive actions, deployment gates, and "continue?" checkpoints.
confirm({prompt, required message, yesLabel, noLabel, defaultYes})Opens a two-option confirmation prompt.prompt- Frame title. Defaults to'Confirm'.message- Main question displayed inside the prompt.yesLabel- Positive option label. Defaults to'Yes'.noLabel- Negative option label. Defaults to'No'.defaultYes- Defaults totrue; controls the initially selected option.- Returns
bool-truewhen the positive option is confirmed,falsewhen the negative option is confirmed. - Cancel behavior - Esc/Ctrl+C returns the default option value from
defaultYesin the current implementation. - Controls - Left/Right toggles the highlighted option, Enter confirms, Esc/Ctrl+C cancels to the default.
Examples
final shouldDeploy = terminice.confirm(
prompt: 'Deploy',
message: 'Ship the current build?',
);
if (shouldDeploy) {
print('Deploying...');
}
final deleteFile = terminice.confirm(
prompt: 'Delete file',
message: 'Remove config.json permanently?',
defaultYes: false,
);
final restart = terminice.fire.confirm(
prompt: 'Restart service',
message: 'Apply changes now?',
yesLabel: 'Restart',
noLabel: 'Later',
defaultYes: false,
);
Why use this? A plain
stdin.readLineSync()can only guess what users meant.confirmmakes the choice explicit, themeable, and keyboard driven.
⤴️ Back → The terminice Catalogue
multiline - Terminal Text Area #
Capture multi-line text with cursor movement, scrolling, and a dedicated confirm shortcut. This prompt is useful for release notes, commit messages, descriptions, and small config snippets.
multiline(String prompt, {maxLines, visibleLines, allowEmpty})Opens an editable multi-line area.prompt- Title displayed above the editor.maxLines- Maximum number of lines the editor may contain. Defaults to200.visibleLines- Height of the scrollable viewport. Defaults to10.allowEmpty- Defaults totrue; whenfalse, Ctrl+D will not confirm until at least one line contains non-whitespace text.- Returns
String?- The lines joined with\nafter Ctrl+D, ornullon Esc/Ctrl+C. - Controls - Type normally, Enter inserts a new line, Backspace deletes or merges lines, arrows move the cursor, Ctrl+D confirms, Esc/Ctrl+C cancels.
Examples
final notes = terminice.multiline('Release notes');
print(notes ?? 'No notes entered.');
final commitMessage = terminice.multiline(
'Commit message',
visibleLines: 5,
maxLines: 20,
allowEmpty: false,
);
final body = terminice.matrix.multiline(
'Issue description (Ctrl+D to save)',
visibleLines: 8,
);
final lineCount = body?.split('\n').length ?? 0;
print('Captured $lineCount lines.');
Why use this? Use
textfor a short answer. Usemultilinewhen Enter should create content instead of submitting the prompt.
⤴️ Back → The terminice Catalogue
slider - Numeric Selection #
Select a single numeric value within a bounded range using left/right keyboard controls. The rendered bar can show raw values, units, and optional percentage context.
slider(String prompt, {min, max, initial, step, width, unit, showPercent})Opens a single-value slider.prompt- Title displayed above the slider.min- Lowest selectable value. Defaults to0.max- Highest selectable value. Defaults to100.initial- Starting value. Defaults to50; the active slider value is clamped into the range.step- Amount added/subtracted per arrow press. Defaults to1.width- Visual bar width in characters. Defaults to28.unit- Suffix appended to the displayed value, such as'MB','s', or'%'.showPercent- Whentrue, also displays the selected value as a percentage of the range.- Returns
num- The selected value on Enter. Esc/Ctrl+C returns the suppliedinitialvalue. - Controls - Left/Right adjusts by
step, Enter confirms, Esc/Ctrl+C cancels toinitial.
Examples
final memory = terminice.slider(
'Memory allocation',
min: 128,
max: 2048,
initial: 512,
step: 128,
unit: 'MB',
);
final timeout = terminice.slider(
'Request timeout',
min: 0,
max: 120,
initial: 30,
step: 5,
unit: 's',
);
final rollout = terminice.neon.slider(
'Rollout',
min: 0,
max: 100,
initial: 25,
step: 5,
unit: '%',
showPercent: true,
);
Why use this? A slider is faster and safer than asking users to type a number when the valid range is known and small adjustments matter.
⤴️ Back → The terminice Catalogue
range - Dual-Handle Numeric Range #
Select a start and end value inside the same bounded numeric scale. The active handle can be switched from the keyboard, and confirmed values stay ordered during interaction.
range(String prompt, {min, max, startInitial, endInitial, step, width, unit})Opens a two-handle range prompt.prompt- Title displayed above the range bar.min- Lowest allowed value. Defaults to0.max- Highest allowed value. Defaults to100.startInitial- Starting value for the first handle. Defaults to20.endInitial- Starting value for the second handle. Defaults to80.step- Amount added/subtracted from the active handle. Defaults to1.width- Visual bar width in characters. Defaults to28; rendering also adapts to terminal width.unit- Suffix appended to both displayed values. Defaults to'%'.- Returns
RangeResult- Read values with.startand.end. Importpackage:terminice_core/terminice_core.dartonly if you want to name the type explicitly. - Cancel behavior - Esc/Ctrl+C returns a
RangeResultbuilt from the suppliedstartInitialandendInitial. - Controls - Left/Right adjusts the active handle, Up/Down/Space switches handles, Enter confirms, Esc/Ctrl+C cancels to the initial pair.
Examples
final budget = terminice.range(
'Memory budget',
min: 0,
max: 32,
startInitial: 4,
endInitial: 16,
step: 1,
unit: 'GB',
);
print('Allowed: ${budget.start}GB to ${budget.end}GB');
final quietHours = terminice.range(
'Quiet hours',
min: 0,
max: 24,
startInitial: 22,
endInitial: 7,
unit: 'h',
);
final price = terminice.ocean.range(
'Price filter',
min: 0,
max: 500,
startInitial: 50,
endInitial: 250,
step: 25,
unit: r'$',
);
Why use this? Use
sliderfor one value. Userangewhen the user is defining a span, such as min/max price, memory budget, or an allowed operating window.
⤴️ Back → The terminice Catalogue
rating - Star Rating Input #
Collect a small integer rating with stars, number-key shortcuts, and optional labels for each level. This is a compact fit for priority, satisfaction, quality, and risk scoring.
rating(String prompt, {maxStars, initial, labels})Opens a discrete star rating prompt.prompt- Title displayed above the rating.maxStars- Maximum rating value. Defaults to5and must be greater than0.initial- Starting rating. Defaults to3; the active value is clamped into1..maxStars.labels- Optional labels displayed for each rating value when the list has at leastmaxStarsentries.- Returns
int- The confirmed rating. Esc/Ctrl+C returns the suppliedinitialvalue. - Controls - Left/Right adjusts the rating, number keys jump directly to a value, Enter confirms, Esc/Ctrl+C cancels to the initial rating.
🧪 Examples
final satisfaction = terminice.rating('Satisfaction');
print('User rated this $satisfaction/5.');
final priority = terminice.rating(
'Priority',
initial: 3,
labels: ['Lowest', 'Low', 'Medium', 'High', 'Critical'],
);
final risk = terminice.fire.rating(
'Risk level',
maxStars: 10,
initial: 4,
);
Why use this? Ratings keep bounded integer input quick. Number keys make exact selection fast, while labels let you turn numbers into meaningful domain language.
⤴️ Back → The terminice Catalogue
date - Keyboard Date Prompt #
Collect a calendar date by editing day, month, and year fields directly. It renders a formatted preview so the selected date is easy to verify before confirmation.
date(String prompt, {initial})Opens a three-field date selector.prompt- Title displayed above the selector. If an empty string is passed, the prompt uses'Date'.initial- Optional startingDateTime. When omitted, the prompt starts fromDateTime.now().- Returns
DateTime?- The selected date on Enter, ornullon Esc/Ctrl+C. - Controls - Left/Right switches between day/month/year, Up/Down adjusts the active field, Ctrl+E jumps to today, Enter confirms, Esc/Ctrl+C cancels.
- Behavior - The prompt edits calendar fields using Dart
DateTime, so month/year changes follow Dart's normal date normalization.
Examples
final launchDate = terminice.date(
'Launch date',
initial: DateTime(2026, 9, 1),
);
if (launchDate != null) {
print('Launch: ${launchDate.toIso8601String().split('T').first}');
}
final birthday = terminice.pastel.date('Birthday');
print(birthday == null ? 'Skipped' : 'Saved birthday');
final reviewDate = terminice.date(
'Next review',
initial: DateTime.now().add(const Duration(days: 30)),
);
Why use this? Use
datewhen free-form date text would be too error-prone, but a full calendar picker would be more UI than the workflow needs.
⤴️ Back → The terminice Catalogue
form - Multi-Field Input #
Group several text/password-style fields inside one themed frame. Forms support per-field placeholders, masking, required checks, initial values, per-field validators, and cross-field validation on submit.
Form field and result types come from terminice_core, so full form examples use:
import 'package:terminice/terminice.dart';
import 'package:terminice_core/terminice_core.dart';
form(String prompt, {required fields, crossValidator})Opens a multi-field input form.prompt- Title displayed above the form.fields- List ofFormFieldConfigobjects, rendered top-to-bottom.crossValidator- OptionalString? Function(List<String> values). Returnnullfor valid form state, or an error message to block submission.- Returns
FormResult?- Confirmed, trimmed field values on success, ornullon Esc/Ctrl+C. Access values withresult[0],result[1], orresult.values. - Empty fields behavior - Passing an empty
fieldslist returnsFormResult([]). - Controls - Type into the focused field, Tab/Down moves next, Up moves previous, Enter moves next or submits from the last field, Ctrl+R reveals the focused masked field when allowed, Esc/Ctrl+C cancels.
FormFieldConfig supports:
label- Required label shown beside the input.placeholder- Optional hint shown when the focused field is empty.masked- Defaults tofalse; whentrue, the field displays mask characters.maskChar- Character used for masked fields. Defaults to'•'.allowReveal- Defaults tofalse; whentrue, Ctrl+R toggles plain text for that masked field.required- Defaults tofalse; empty trimmed values are rejected withRequired.validator- OptionalString? Function(String)per-field validator. Returnnullfor valid input, or a non-empty error message. Returning''is still accepted as success for backwards compatibility.initialValue- Optional pre-filled text.
Examples
final login = terminice.form(
'Login',
fields: const [
FormFieldConfig(
label: 'Username',
placeholder: 'ada',
required: true,
),
FormFieldConfig(
label: 'Password',
masked: true,
allowReveal: true,
required: true,
),
],
);
if (login != null) {
print('Signing in ${login[0]}...');
}
final account = terminice.form(
'Create account',
fields: [
FormFieldConfig(
label: 'Email',
required: true,
validator: (value) =>
value.contains('@') ? null : 'Enter a valid email address',
),
const FormFieldConfig(
label: 'Password',
masked: true,
allowReveal: true,
required: true,
),
const FormFieldConfig(
label: 'Confirm',
masked: true,
allowReveal: true,
required: true,
placeholder: 're-enter password',
),
],
crossValidator: (values) {
if (values[1] != values[2]) return 'Passwords do not match';
return null;
},
);
final profile = terminice.ocean.form(
'Profile',
fields: const [
FormFieldConfig(label: 'Display name', initialValue: 'Ada'),
FormFieldConfig(label: 'Team', placeholder: 'Platform'),
FormFieldConfig(label: 'Pager', required: false),
],
);
final values = profile?.values ?? const <String>[];
print('Captured ${values.length} fields.');
Why use this? Use separate prompts when each answer should feel like its own step. Use
formwhen the inputs belong together and should validate as one unit, such as login, signup, or connection settings.
⤴️ Back → The terminice Catalogue
searchSelector - Filterable List Selection #
Pick from a vertical list that can be filtered in place. It works as a quick single-choice selector by default, or as a multi-select searchable checklist when multiSelect is enabled.
searchSelector({required options, prompt, multiSelect, showSearch, maxVisible})Opens a searchable list prompt.options-List<String>displayed in the list. Passing an empty list returns[]immediately.prompt- Frame title. Defaults to'Select an option'.multiSelect- Defaults tofalse. Whentrue, Space toggles the focused row and Enter returns all selected labels.showSearch- Defaults tofalse. Whentrue, the search field starts open; otherwise/toggles search mode.maxVisible- Maximum list rows before scrolling. Defaults to10; the prompt also adapts to terminal height.- Returns
List<String>- Confirmed labels. Single-select returns a one-item list. Multi-select returns checked labels, or the focused label if nothing was checked. - Cancel behavior - Esc/Ctrl+C returns
[]. Empty options and no-match confirmation also return[]. - Controls - Up/Down navigates,
/toggles search, type filters while search is open, Space toggles in multi-select, Enter confirms, Esc/Ctrl+C cancels.
Examples
final language = terminice.searchSelector(
prompt: 'Primary language',
options: const ['Dart', 'Go', 'Rust', 'TypeScript', 'Python'],
showSearch: true,
);
final selectedLanguage = language.isEmpty ? null : language.first;
print(selectedLanguage ?? 'No language selected.');
final services = terminice.searchSelector(
prompt: 'Restart services',
options: const ['api', 'worker', 'scheduler', 'web', 'metrics'],
multiSelect: true,
showSearch: true,
maxVisible: 4,
);
if (services.isNotEmpty) {
print('Restarting ${services.join(', ')}');
}
final region = terminice.ocean.compact.searchSelector(
prompt: 'Region',
options: const ['us-east-1', 'us-west-2', 'eu-central-1', 'ap-south-1'],
);
final selectedRegion = region.isEmpty ? 'none' : region.first;
print('Region: $selectedRegion');
Why use this? Use
searchSelectorwhen the list may be longer than the terminal viewport, or when users know the item name and should be able to filter instead of arrowing through everything.
⤴️ Back → The terminice Catalogue
choiceSelector - Card-Based Choice Grid #
Render choices as two-line cards with a label and optional subtitle. It is useful when each option needs a little context, such as actions, plans, environments, or workflows.
choiceSelector(String prompt, {required items, multiSelect, columns, cardWidth, maxColumns})Opens a themed grid of choice cards.prompt- Frame title displayed above the grid.items-List<ChoiceItem>. Passing an empty list returns[]immediately.ChoiceItem(label, {subtitle})- Card model exported bypackage:terminice/terminice.dart;labelis returned,subtitleis rendered dimmed below it.multiSelect- Defaults tofalse. Whentrue, each card shows a checkbox and Space toggles the focused card.columns- Fixed column count. Defaults to0, which lets the selector compute columns from terminal width and item count.cardWidth- Optional card width override, clamped by the implementation to 16-44 characters.maxColumns- Optional cap used by automatic column layout.- Returns
List<String>- ConfirmedChoiceItem.labelvalues. Single-select returns one label. Multi-select returns selected labels, or the focused label if nothing was checked. - Cancel behavior - Esc/Ctrl+C returns
[]. - Controls - Arrow keys navigate across cards with wrapping, Space toggles in multi-select, Enter confirms, Esc/Ctrl+C cancels.
Examples
final action = terminice.choiceSelector(
'Next action',
items: const [
ChoiceItem('Preview', subtitle: 'Render a local diff'),
ChoiceItem('Publish', subtitle: 'Deploy the current build'),
ChoiceItem('Rollback', subtitle: 'Restore previous version'),
],
);
final selectedAction = action.isEmpty ? 'none' : action.first;
print('Action: $selectedAction');
final checks = terminice.choiceSelector(
'Run checks',
items: const [
ChoiceItem('Format', subtitle: 'dart format'),
ChoiceItem('Analyze', subtitle: 'dart analyze'),
ChoiceItem('Test', subtitle: 'dart test'),
ChoiceItem('Build', subtitle: 'compile release artifact'),
],
multiSelect: true,
columns: 2,
);
print('Selected checks: ${checks.join(', ')}');
final environment = terminice.fire.choiceSelector(
'Environment',
items: const [
ChoiceItem('Dev', subtitle: 'Local dependencies'),
ChoiceItem('Stage', subtitle: 'Shared QA stack'),
ChoiceItem('Prod', subtitle: 'Customer traffic'),
],
cardWidth: 24,
maxColumns: 3,
);
Why use this? Use
searchSelectorfor fast text filtering. UsechoiceSelectorwhen the shape of the decision matters and subtitles help users choose confidently.
⤴️ Back → The terminice Catalogue
checkboxSelector - Multi-Select Checklist #
Display a vertical checklist with a live summary line and a select-all shortcut. It is the most direct selector for enabling features, choosing tasks, or collecting a small set of labels.
checkboxSelector(String prompt, {required options, initialSelected, maxVisible})Opens a multi-select checklist.prompt- Frame title displayed above the checklist.options-List<String>labels displayed beside checkboxes. Passing an empty list returns[]immediately.initialSelected- OptionalSet<int>of zero-based option indices that start checked. Invalid indices are ignored by the underlying prompt.maxVisible- Maximum visible rows before scrolling. Defaults to12; the prompt also adapts to terminal height.- Returns
List<String>- Confirmed labels in option order. If the user confirms while nothing is checked, the currently focused option is returned. - Cancel behavior - Esc/Ctrl+C returns
[]. - Controls - Up/Down navigates, Space toggles the focused option,
Aselects all or clears all, Enter confirms, Esc/Ctrl+C cancels.
Examples
final features = terminice.checkboxSelector(
'Enable features',
options: const ['Dark mode', 'Notifications', 'Auto-save', 'Sync'],
initialSelected: {0},
);
print('Enabled: ${features.join(', ')}');
final migrations = terminice.checkboxSelector(
'Migrations',
options: const [
'Create users table',
'Backfill display names',
'Add billing index',
'Vacuum old events',
],
maxVisible: 3,
);
if (migrations.isEmpty) {
print('No migrations selected.');
}
final labels = terminice.matrix.checkboxSelector(
'Issue labels',
options: const ['bug', 'feature', 'docs', 'good first issue'],
initialSelected: {1, 2},
);
Why use this? Use
checkboxSelectorwhen every option is a simple on/off inclusion. UsechoiceSelectorwhen each option needs a card subtitle, andtagSelectorwhen a compact chip layout fits better.
⤴️ Back → The terminice Catalogue
gridSelector - Two-Dimensional Selection Grid #
Arrange string options into a responsive grid with wrapping arrow-key navigation. It is a good fit for compact fixed vocabularies where scanning by rows and columns is faster than a vertical list.
gridSelector({required options, prompt, columns, multiSelect, cellWidth, maxColumns, initialSelection})Opens a grid selector.options-List<String>labels rendered inside cells. Passing an empty list returns[].prompt- Frame title. Defaults to'Select'.columns- Fixed column count. Defaults to0, which computes a responsive column count.multiSelect- Defaults tofalse. Whentrue, cells include checkbox markers and Space toggles the focused cell.cellWidth- Optional fixed width per cell. When omitted, width is computed from item labels and clamped by the core grid prompt.maxColumns- Optional cap for automatic layout.initialSelection- OptionalSet<int>of zero-based indices that start selected. Invalid indices are ignored.- Returns
List<String>- Confirmed labels in option order. Single-select returns the focused label. Multi-select returns checked labels, or the focused label if none were checked. - Cancel behavior - Esc/Ctrl+C returns
[]. - Controls - Arrow keys move in two dimensions with wrapping, Space toggles in multi-select, Enter confirms, Esc/Ctrl+C cancels.
Examples
final color = terminice.gridSelector(
prompt: 'Accent color',
options: const ['Red', 'Blue', 'Green', 'Yellow'],
columns: 2,
);
final selectedColor = color.isEmpty ? 'none' : color.first;
print('Color: $selectedColor');
final targets = terminice.gridSelector(
prompt: 'Deploy targets',
options: const ['api', 'web', 'worker', 'scheduler', 'cron', 'docs'],
multiSelect: true,
initialSelection: {0, 2},
maxColumns: 3,
);
print('Targets: ${targets.join(', ')}');
final size = terminice.neon.gridSelector(
prompt: 'Instance size',
options: const ['small', 'medium', 'large', 'xlarge'],
cellWidth: 14,
);
Why use this? Use
gridSelectorwhen options are short and benefit from spatial scanning. UsesearchSelectorwhen the list is long or users need filtering.
⤴️ Back → The terminice Catalogue
tagSelector - Chip-Style Multi-Select #
Render tags as compact [ tag ] chips in a responsive grid. The selector is always multi-select and shows a count summary while the user toggles chips.
tagSelector({required tags, prompt, maxContentWidth, minContentWidth, minColumnWidth, maxColumnWidth, useTerminalWidth})Opens a chip-style tag selector.tags-List<String>labels rendered inside chips. Passing an empty list returns[]immediately.prompt- Frame title. Defaults to'Select tags'.maxContentWidth- Optional cap for the inner content width.minContentWidth- Lower bound for content width before wrapping. Defaults to32.minColumnWidth- Minimum chip column width. Defaults to8.maxColumnWidth- Maximum chip column width. Defaults to24.useTerminalWidth- Defaults totrue; when enabled, layout is recomputed from the current terminal width during rendering.- Returns
List<String>- Confirmed tags in tag order. If Enter is pressed before any chip is toggled, the focused tag is returned. - Cancel behavior - Esc/Ctrl+C returns
[]. - Controls - Arrow keys move between chips, Space toggles the focused chip, Enter confirms, Esc/Ctrl+C cancels.
Examples
final issueLabels = terminice.tagSelector(
prompt: 'Issue labels',
tags: const ['bug', 'feature', 'docs', 'chore', 'blocked'],
);
print('Labels: ${issueLabels.join(', ')}');
final interests = terminice.tagSelector(
prompt: 'Interests',
tags: const [
'Dart',
'CLI',
'Automation',
'Testing',
'Design',
'Docs',
],
maxContentWidth: 72,
minColumnWidth: 12,
);
final compactTags = terminice.pastel.compact.tagSelector(
prompt: 'Release tags',
tags: const ['stable', 'beta', 'internal', 'breaking', 'security'],
maxColumnWidth: 16,
);
Why use this? Use
tagSelectorwhen the selected values are labels and a compact chip grid is easier to scan than a tall checklist.
⤴️ Back → The terminice Catalogue
toggleGroup - Editable Boolean Switches #
Edit several independent on/off states in one prompt. Unlike a radio group, toggleGroup is not mutually exclusive: every row has its own boolean state.
toggleGroup (String prompt, {required items, alignContent})Opens a vertical group of switches.prompt- Frame title displayed above the switches.items-List<ToggleItem>. Passing an empty list returns{}immediately.ToggleItem(label, {initialOn})- Switch model exported bypackage:terminice/terminice.dart;initialOndefaults tofalse.alignContent- Present in the public API for alignment control; the current renderer pads labels into a tidy column.- Returns
Map<String, bool>- Confirmed switch states keyed byToggleItem.label. Keep labels unique, because duplicate labels overwrite earlier entries in the returned map. - Cancel behavior - Esc/Ctrl+C returns a map built from the initial states, not an empty map.
- Controls - Up/Down navigates rows, Left/Right/Space toggles the focused switch,
Atoggles all switches, Enter confirms, Esc/Ctrl+C cancels to the initial map.
Examples
final settings = terminice.toggleGroup(
'Settings',
items: const [
ToggleItem('Auto deploy', initialOn: true),
ToggleItem('Send email'),
ToggleItem('Write audit log', initialOn: true),
],
);
if (settings['Auto deploy'] == true) {
print('Auto deploy is enabled.');
}
final flags = terminice.toggleGroup(
'Feature flags',
items: const [
ToggleItem('New checkout'),
ToggleItem('Fast search', initialOn: true),
ToggleItem('Verbose logs'),
],
);
for (final entry in flags.entries) {
print('${entry.key}: ${entry.value ? "on" : "off"}');
}
final permissions = terminice.monochrome.toggleGroup(
'Permissions',
items: const [
ToggleItem('Read', initialOn: true),
ToggleItem('Write'),
ToggleItem('Admin'),
],
);
Why use this? Use
checkboxSelectorwhen you only need a list of enabled labels. UsetoggleGroupwhen both enabled and disabled states matter after the prompt returns.
⤴️ Back → The terminice Catalogue
commandPalette - Fuzzy Command Launcher #
Open a command palette that searches command titles and optional subtitles, ranks matches, and returns the selected command entry. It behaves like a compact launcher for CLIs with many actions.
commandPalette (String prompt, {required commands, maxVisible})Opens a ranked command list with an always-visible search input.prompt- Frame title displayed above the palette.commands-List<CommandEntry>. Passing an empty list returnsnullimmediately.CommandEntry({required id, required title, subtitle})- Command model exported bypackage:terminice/terminice.dart.id- Stable identifier returned with the selected entry; use it for dispatching.title- Primary searchable label shown in the list.subtitle- Optional secondary searchable text rendered dimmed beside the title.maxVisible- Maximum result rows before scrolling. Defaults to12; the prompt also adapts to terminal height.- Ranking - Empty query shows all commands. Non-empty query uses fuzzy matching by default, searches title first, falls back to subtitle, and Ctrl+R toggles fuzzy vs substring mode.
- Returns
CommandEntry?- The selected entry on Enter, ornullwhen cancelled, when commands are empty, or when Enter is pressed with no matches. - Cancel behavior - Esc/Ctrl+C returns
null. - Controls - Type to search, Backspace edits, Up/Down navigates matches, Ctrl+R toggles ranking mode, Enter confirms, Esc/Ctrl+C cancels.
Examples
final command = terminice.commandPalette(
'Command Palette',
commands: const [
CommandEntry(id: 'open', title: 'Open Project', subtitle: 'Ctrl+O'),
CommandEntry(id: 'build', title: 'Build Release', subtitle: 'compile'),
CommandEntry(id: 'deploy', title: 'Deploy', subtitle: 'production'),
],
);
switch (command?.id) {
case 'open':
print('Opening project...');
break;
case 'build':
print('Building release...');
break;
case 'deploy':
print('Deploying...');
break;
default:
print('No command selected.');
}
final selected = terminice.commandPalette(
'Git action',
commands: const [
CommandEntry(id: 'status', title: 'Git: Status', subtitle: 'working tree'),
CommandEntry(id: 'commit', title: 'Git: Commit', subtitle: 'create commit'),
CommandEntry(id: 'push', title: 'Git: Push', subtitle: 'origin'),
CommandEntry(id: 'pull', title: 'Git: Pull', subtitle: 'origin'),
],
maxVisible: 4,
);
print('Selected command id: ${selected?.id ?? "none"}');
final tool = terminice.arcane.commandPalette(
'Toolbox',
commands: const [
CommandEntry(id: 'format', title: 'Format Dart', subtitle: 'dart format'),
CommandEntry(id: 'analyze', title: 'Analyze Dart', subtitle: 'dart analyze'),
CommandEntry(id: 'test', title: 'Run Tests', subtitle: 'dart test'),
],
);
Why use this? Use
commandPalettewhen the user is choosing an action by name. It returns the wholeCommandEntry, so display text and dispatch IDs can stay separate.
⤴️ Back → The terminice Catalogue
filePicker - Searchable File Browser #
Browse the filesystem from a starting directory and return the selected file path. The picker renders each directory as an enterable row, adds a parent-directory row when possible, and uses the searchable selector UI so users can filter large folders before choosing.
filePicker(String prompt, {startDirectory, showHidden, foldersOnly})Opens a searchable filesystem browser.prompt- Base title for the frame. The current path is appended in parentheses and shortened when it is longer than 60 visible characters.startDirectory- OptionalDirectoryused as the first folder. Defaults toDirectory.current.showHidden- Defaults tofalse. Whentrue, entries whose basename starts with.are included.foldersOnly- Defaults tofalse. Whentrue, file choices are not returned; directories remain navigation targets. UsepathPickerwhen you need a confirmed directory path.- Returns
String?- The selected file path when a file is confirmed, ornullwhen the user cancels. - Filesystem behavior - Entries come from
Directory.listSync(followLinks: false), sorted with directories first and then by case-insensitive basename. Symlinks are not followed by the listing helper. Unreadable directories can surface the underlyingFileSystemException. - Cancel behavior - Esc/Ctrl+C inside the embedded search selector returns
null. - Controls - Up/Down navigates rows,
/toggles search, typing filters while search is open, Enter enters a directory or confirms a file, Esc/Ctrl+C cancels.
Examples
final configPath = terminice.filePicker(
'Select config file',
startDirectory: Directory.current,
showHidden: true,
);
if (configPath == null) {
print('No config selected.');
} else {
final contents = File(configPath).readAsStringSync();
print('Loaded ${contents.length} characters from $configPath');
}
final assetPath = terminice.ocean.compact.filePicker(
'Select asset',
startDirectory: Directory.current,
);
if (assetPath != null) {
print('Asset: $assetPath');
}
final picked = terminice.filePicker(
'Browse without dotfiles',
startDirectory: Directory.systemTemp,
);
print(picked ?? 'Selection cancelled.');
Why use this? Use
filePickerwhen the user needs to choose an existing file from a folder tree. UsepathPickerwhen confirming the current directory is part of the workflow.
⤴️ Back → The terminice Catalogue
pathPicker - Directory and Path Browser #
Choose a directory, or optionally a file, from a dynamic filesystem list. Unlike filePicker, this prompt includes an explicit ✓ Select this directory row, so it is the right picker for output folders, project roots, cache locations, and other directory targets.
pathPicker (String prompt, {startDirectory, showHidden, allowFiles, maxVisible})Opens a dynamic path browser.prompt- Frame title displayed above the picker.startDirectory- OptionalDirectoryused as the starting point. Defaults toDirectory.current.showHidden- Defaults tofalse. When enabled, dotfiles and dot directories are included.allowFiles- Defaults tofalse. Whentrue, files are shown and can be confirmed; whenfalse, only directories plus navigation rows are shown.maxVisible- Maximum visible rows before the list scrolls. Defaults to18.- Returns
String?- The chosen directory path from✓ Select this directory, the chosen file path whenallowFilesis enabled, ornullon cancel. - Filesystem behavior - Entries are rebuilt as the current directory changes. Directories are sorted before files, hidden entries are filtered unless requested, and symlinks are not followed while listing. Permission errors while reading a directory are swallowed so the parent/current-directory controls can still render.
- Cancel behavior - Esc/Ctrl+C returns
null. - Controls - Up/Down navigates, Enter/Right enters a directory or confirms the focused action, Left moves to the parent directory when one exists, Esc/Ctrl+C cancels.
Examples
final projectRoot = terminice.pathPicker(
'Select project root',
startDirectory: Directory.current,
);
if (projectRoot != null) {
print('Project root: $projectRoot');
}
final inputPath = terminice.pathPicker(
'Select input path',
startDirectory: Directory.current,
allowFiles: true,
maxVisible: 12,
);
if (inputPath != null) {
final type = FileSystemEntity.typeSync(inputPath);
final kind = type == FileSystemEntityType.directory ? 'directory' : 'file';
print('Selected $kind: $inputPath');
}
final exportDir = terminice.neon.pathPicker(
'Choose export folder',
startDirectory: Directory.systemTemp,
showHidden: true,
);
print(exportDir == null ? 'No export folder selected.' : exportDir);
Why use this? Use
pathPickerfor directory-first workflows because it can confirm the current folder directly. Turn onallowFileswhen a command accepts either a file path or a directory path.
⤴️ Back → The terminice Catalogue
colorPicker - ANSI Color Grid #
Pick a terminal-friendly color from a live ANSI swatch grid. The picker maps horizontal movement to hue, vertical movement to brightness, and separate shortcuts to saturation, presets, randomization, reset, and hex entry.
colorPicker (String prompt, {initialHex, cols, rows})Opens an interactive color grid.prompt- Frame title displayed above the grid.initialHex- Optional starting color. Accepts#RRGGBBorRRGGBB; invalid values are ignored. The picker positions the cursor near the requested color on the HSV grid.cols- Number of hue columns. Defaults to24; larger values give finer horizontal hue steps.rows- Number of brightness rows. Defaults to8; larger values give finer vertical brightness steps.- Returns
String?- The selected color as uppercase#RRGGBB, ornullwhen cancelled. - Color behavior - The return value is generated from the currently highlighted HSV grid cell. Hex entry and
initialHexmove the grid selection to the nearest represented color rather than storing a separate exact text value. - Hex entry - Press
Hto type a hex value. Enter applies valid input and leaves invalid input unchanged; Esc exits hex entry without cancelling the picker. - Cancel behavior - Esc/Ctrl+C outside hex entry returns
null. - Controls - Arrow keys move the cursor, Left/Right wrap across hue columns,
Scycles saturation breakpoints,[and]fine-tune saturation,-and=/+adjust brightness,1-0jump to presets,Rrandomizes,Xresets, Enter confirms.
Examples
final accent = terminice.colorPicker(
'Accent color',
initialHex: '#3B82F6',
);
print(accent == null ? 'No accent selected.' : 'Accent: $accent');
final brandColor = terminice.colorPicker(
'Brand color',
initialHex: 'ffcc00',
cols: 36,
rows: 10,
);
if (brandColor != null) {
print('Use $brandColor in generated config.');
}
final dangerColor = terminice.arcane.colorPicker(
'Danger color',
cols: 16,
rows: 6,
);
print(dangerColor ?? '#EF4444');
Why use this? Use
colorPickerwhen the user should see the color before committing it. It is faster than asking for raw hex, but still lets precise users jump in withH.
⤴️ Back → The terminice Catalogue
datePicker - Calendar Date Selection #
Pick a single calendar date from a framed month view. The selected day stays highlighted as the user moves by day, week, or year, and today is accented for orientation.
datePicker (String prompt, {initialDate, startWeekOnMonday, allowPast, allowFuture})Opens an interactive calendar.prompt- Frame title displayed above the calendar.initialDate- Optional starting date. Defaults toDateTime.now()and is normalized to year/month/day for the initial selection.startWeekOnMonday- Defaults totrue. Whenfalse, the calendar renders Sunday as the first weekday.allowPast/allowFuture- Defaults totrue. Set either tofalseto clamp the initial date and keyboard navigation at today.- Returns
DateTime?- The selected date on Enter, ornullwhen cancelled. - Date behavior - Left/Right move one day, Up/Down move one week, and W/S move one year while keeping the visible month synced to the selected date. Ctrl+E jumps to today.
- Normalization note - The returned selection is date-only.
- Cancel behavior - Esc/Ctrl+C returns
null. - Controls - Left/Right move by day, Up/Down move by week, W/S move by year, Ctrl+E jumps to today, Enter confirms, Esc/Ctrl+C cancels.
Examples
final releaseDate = terminice.datePicker(
'Release date',
initialDate: DateTime(2026, 1, 15),
);
if (releaseDate != null) {
final yyyy = releaseDate.year.toString().padLeft(4, '0');
final mm = releaseDate.month.toString().padLeft(2, '0');
final dd = releaseDate.day.toString().padLeft(2, '0');
print('Release: $yyyy-$mm-$dd');
}
final birthday = terminice.datePicker(
'Birthday',
startWeekOnMonday: false,
);
if (birthday != null && birthday.isAfter(DateTime.now())) {
print('Birthday cannot be in the future.');
}
DateTime dateOnly(DateTime value) {
return DateTime(value.year, value.month, value.day);
}
final shipDate = terminice.ocean.datePicker(
'Ship date',
initialDate: DateTime.now(),
);
final normalizedShipDate = shipDate == null ? null : dateOnly(shipDate);
print(normalizedShipDate ?? 'No ship date selected.');
Why use this? Use
datePickerwhen a visual calendar prevents off-by-one mistakes. Use the simplerdateprompt when users already know the exact date string they want to type.
⤴️ Back → The terminice Catalogue
task - Async Status Wrapper #
Run a synchronous or asynchronous operation while terminice renders a small status indicator. Use it when the work is indeterminate: publishing, polling, resolving dependencies, or any operation where there is no useful total.
task<T>(String prompt, {required run, message, success, failure, cancel, isCanceled, interval, style, indicator, maxDots, display, finalBehavior})Runsrunand completes with its typed result.prompt- Main status label. It is also the default success message.run- AFutureOr<T> Function()containing the work.message- Optional detail shown beside the prompt while running.success- Optional final success text. Defaults toprompt.failure- OptionalString Function(Object error, StackTrace stackTrace)for failure text. Defaults to'$prompt failed: $error'.cancel- Optional formatter for cancellation text. Defaults to'$prompt canceled'.isCanceled- Optional predicate that decides whether a thrown error should be labelled as cancellation instead of failure.interval,style,indicator,maxDots- Tune the running animation.indicatorcan use spinner frames or cycling dots.display- ATaskDisplayvalue:auto,inline, orplain.TaskDisplay.plain, fallback modes, non-terminal IO, and non-modern compatibility avoid ANSI cursor control and animation.finalBehavior- ATaskFinalBehaviorvalue controlling whether the final status line remains visible.- Returns
Future<T>- The exact result fromrun. - Error behavior - Synchronous throws and asynchronous errors render failure or cancel status, then rethrow the original error with its stack trace.
Examples
final result = await terminice.task('Publishing', run: publish);
try {
await terminice.task(
'Deploying',
run: deploy,
isCanceled: (error) => error is OperationCanceledException,
cancel: (error, stack) => 'Deployment canceled',
);
} catch (error) {
// The final status was already rendered; handle or rethrow as needed.
}
Why use this? Use
taskwhen you want async status and cleanup without manually wiring timers, cursor hiding, final status lines, or error rendering.
⤴️ Back → The terminice Catalogue
progressTask - Async Progress Wrapper #
Run work with a determinate progress bar. progressTask passes a mutable TaskProgress object into your callback, so the task can update count and detail text as it advances.
progressTask<T>(String prompt, {required total, required run, message, success, failure, cancel, isCanceled, display, finalBehavior, interval, progressWidth})Runsrunwhile rendering progress.prompt- Main status label.total- Total units of work. Must be greater than0.run- AFutureOr<T> Function(TaskProgress progress)callback.message- Optional initial detail text.success,failure,cancel,isCanceled- Same semantics astask.display- ATaskDisplayvalue:auto,inline, orplain. Plain and fallback rendering avoid ANSI/control output.finalBehavior- Controls whether the final status line is persisted or cleared.interval- Animation refresh interval. Defaults to80ms.progressWidth- Width of the inline progress bar used by task rendering.- Returns
Future<T>- The result fromrun. - Progress behavior - Current progress is clamped to
0..total; invalid totals are rejected before the task starts. - Error behavior - Errors are rendered as failure or cancel status, then rethrown.
Examples
await terminice.progressTask(
'Uploading',
total: files.length,
run: (progress) async { ... },
);
await terminice.progressTask(
'Uploading',
total: files.length,
run: (progress) async {
for (final file in files) {
progress.update(message: file.path);
await upload(file);
progress.increment();
}
},
);
Why use this? Use
progressTaskwhen your async work has a known total and the task itself should own progress updates.
⤴️ Back → The terminice Catalogue
trackStream - Stream Progress Collector #
Collect every event from a stream while advancing determinate progress once per event. The returned list preserves stream order.
trackStream<T>(String prompt, Stream<T> source, {required total, message, success, failure, cancel, isCanceled, display, finalBehavior, interval, progressWidth})Tracks and collectssource.prompt- Main status label.source- Stream to listen to.total- Expected number of events. Must be greater than0.message,success,failure,cancel,isCanceled,display,finalBehavior,interval,progressWidth- Same behavior asprogressTask.- Returns
Future<List<T>>- All stream values, in order. - Error behavior - Stream errors render failure or cancel status, then rethrow.
Examples
final items = await terminice.trackStream(
'Downloading',
stream,
total: count,
);
final records = await terminice.ocean.trackStream(
'Importing records',
readRecords(),
total: expectedRecords,
success: 'Imported records',
);
Why use this? Use
trackStreamwhen each stream event maps to one progress unit and you want the collected values back after rendering completes.
⤴️ Back → The terminice Catalogue
TaskProgress - Mutable Progress State #
TaskProgress is the handle passed to progressTask callbacks and indicator whileRunning callbacks. Mutating it updates the rendered progress.
current- Completed units, clamped to the inclusive0..totalrange.total- Total units expected for the task. Must be greater than0.message- Optional detail shown beside the prompt.ratio- Completion ratio between0and1.isComplete-truewhencurrent >= total.update({current, total, message})- Updates progress. Passingnullformessageleaves the current message unchanged.increment([by = 1])- Advancescurrentbyby, clamped tototal.
Examples
await terminice.progressTask(
'Processing',
total: jobs.length,
run: (progress) async {
for (final job in jobs) {
progress.update(message: job.name);
await job.run();
progress.increment();
}
},
);
Why use this? Use
TaskProgressas the single source of truth for count, total, and per-step detail during async progress rendering.
⤴️ Back → The terminice Catalogue
TaskDisplay - Task Rendering Mode #
Choose how async task helpers render while work is running.
TaskDisplay.auto- Default. Uses animated inline rendering when the current terminal andTerminiceconfiguration support it; otherwise uses plain line output.TaskDisplay.inline- Requests animated inline rendering when available; falls back to plain output when animation is unavailable.TaskDisplay.plain- Uses simple final lines without ANSI cursor control, raw mode, or animation.- Fallback behavior - Non-terminal IO,
terminice.fallback, and non-modern compatibility modes use plain rendering.terminice.autoFallbackuses plain rendering when fallback is needed.
Examples
await terminice.task(
'Publishing',
run: publish,
display: TaskDisplay.plain,
);
Why use this? Use
TaskDisplay.plainfor logs, CI, tests, or any output stream where control sequences would be noisy.
⤴️ Back → The terminice Catalogue
TaskFinalBehavior - Final Output Policy #
Control what remains on screen after an async task finishes.
TaskFinalBehavior.persist- Default. Leaves one final success, failure, or cancel status line.TaskFinalBehavior.clear- Clears the animated task display when it finishes. In plain mode, it suppresses the final status line.- Applies to success, failure, and cancellation rendering.
Examples
await terminice.task(
'Refreshing cache',
run: refreshCache,
finalBehavior: TaskFinalBehavior.clear,
);
Why use this? Use
persistwhen the task result should remain in command history. Useclearfor transient status that should disappear after completion.
⤴️ Back → The terminice Catalogue
loadingSpinner - Framed Loading Spinner #
Show a framed, theme-aware spinner for an ongoing task. This is the most expressive spinner: it has a frame title, a message line, themed spinner glyphs, and footer hints describing the active style.
loadingSpinner (String prompt, {message, style})Creates aLoadingSpinnercontroller.prompt- Frame title displayed above the spinner.message- Text shown next to the animated glyph. Defaults to'Loading'.style- ASpinnerStylevalue. Defaults toSpinnerStyle.dots; supported styles aredots,bars, andarcs.- Returns
LoadingSpinner- The spinner is not displayed until you callshow(...), advance it throughrunWith(...), or wrap work withwhileRunning(...). show(int frame)- Renders one frame. The frame index wraps around the style's glyph list, so any increasing integer works.runWith(void Function(void Function() tick) callback)- Runs a synchronous callback with atick()function. Each tick renders the next frame, starting at0. This callback is not awaited and remains for synchronous work.whileRunning<T>(FutureOr<T> Function() run, {message, success, failure, cancel, isCanceled, interval, display, finalBehavior})- Runs async or sync work with spinner task rendering and returns the typed result.clear()- Clears the current spinner frame and resets the internal render state.- Lifecycle - There is no
start/stoptimer for manual rendering. Drive animation from your own loop, timer, synchronousrunWithcallback, or usewhileRunning(...)for awaited work. - Cleanup behavior - Manual loops should call
clear()infinally.runWithhides the cursor while it runs and clears the spinner after the callback returns normally.whileRunninghandles async cleanup and final status rendering.
Examples
void main() async {
final spinner = terminice.loadingSpinner(
'Installing',
message: 'Resolving packages',
style: SpinnerStyle.bars,
);
try {
for (var frame = 0; frame < 12; frame++) {
spinner.show(frame);
await Future.delayed(const Duration(milliseconds: 80));
}
} finally {
spinner.clear();
}
print('Install complete.');
}
void runStep(String step) {
// Do synchronous work for this step.
}
final spinner = terminice.ocean.loadingSpinner(
'Build',
message: 'Compiling',
style: SpinnerStyle.arcs,
);
spinner.runWith((tick) {
for (final step in ['resolve', 'compile', 'package']) {
tick();
runStep(step);
}
});
final release = await terminice.loadingSpinner(
'Publishing',
message: 'Uploading archive',
).whileRunning(publish);
print('Published $release.');
final spinner = LoadingSpinner(
'Deploy',
message: 'Publishing release',
style: SpinnerStyle.dots,
theme: PromptTheme.neon,
);
try {
for (var frame = 0; frame < 8; frame++) {
spinner.show(frame);
}
} finally {
spinner.clear();
}
Why use this? Use
loadingSpinnerwhen the task is indeterminate but important enough to deserve its own framed status area. UseinlineSpinnerwhen you want the same idea in a single log-style line.
⤴️ Back → The terminice Catalogue
inlineSpinner - One-Line Spinner #
Render a compact spinner beside a status label. It is designed for log-style CLIs, CI output, polling loops, and places where a framed widget would be too much visual weight.
inlineSpinner (String prompt, {style})Creates anInlineSpinnercontroller.prompt- Text displayed next to the spinner glyph.style- ASpinnerStylevalue. Defaults toSpinnerStyle.dots; supported styles aredots,bars, andarcs.- Returns
InlineSpinner- The line is not displayed untilshow(...)is called. show(int frame)- Renders one inline frame and clears the previous inline frame if this controller already drew one.clear()- Removes the current inline spinner output and resets the controller.InlineSpinner.framesForStyle(SpinnerStyle style)- Returns the Unicode frame list used by a style, useful when you want to loop exactly one cycle.- Lifecycle - There is no
start,stop, or dedicatedrunWithhelper onInlineSpinner. Callshow(...)from your own loop or timer and finish withclear(). - Async/timer note - If you animate with
Timer.periodic, cancel the timer yourself before callingclear().
Examples
void main() async {
final spinner = terminice.inlineSpinner(
'Waiting for deployment',
style: SpinnerStyle.arcs,
);
try {
for (var frame = 0; frame < 18; frame++) {
spinner.show(frame);
await Future.delayed(const Duration(milliseconds: 70));
}
} finally {
spinner.clear();
}
print('Deployment is ready.');
}
final spinner = terminice.matrix.inlineSpinner(
'Scanning files',
style: SpinnerStyle.dots,
);
final frameCount = InlineSpinner.framesForStyle(SpinnerStyle.dots).length;
try {
for (var frame = 0; frame < frameCount; frame++) {
spinner.show(frame);
}
} finally {
spinner.clear();
}
final spinner = InlineSpinner(
'Syncing cache',
style: SpinnerStyle.bars,
theme: PromptTheme.fire,
);
try {
for (var frame = 0; frame < 10; frame++) {
spinner.show(frame);
}
} finally {
spinner.clear();
}
Why use this? Use
inlineSpinnerfor short-lived or repeated status updates where you do not want a full frame. It gives users motion and context without taking over the terminal.
⤴️ Back → The terminice Catalogue
progressBar - Framed Determinate Progress #
Display bounded progress in a framed widget with a themed bar, percentage, and raw count. The bar is determinate: you provide current and total on every update.
progressBar (String prompt)Creates a themedProgressBarcontroller with the default width.prompt- Frame title displayed above the bar.- Returns
ProgressBar- The bar is not displayed until you callshow(...),runWith(...),whileRunning(...), ortrackStream(...). ProgressBar (String prompt, {width = 36, theme = PromptTheme.dark})Direct constructor for custom width or explicit theme.widthmust be greater than4.show({required int current, required int total, int shimmerPhase = 0})- Renders the current progress state.current/total- Used to compute the ratio. Positive totals clamp the displayed count and percentage into range; whentotal <= 0, the display shows0/0and0%.shimmerPhase- Optional phase offset for the filled bar coloring. Increase it as you update to create motion.runWith(void Function(void Function(int current, int total) update) callback)- Runs a synchronous callback with anupdate(current, total)function. Each update increments the shimmer phase internally. This callback is not awaited and remains for synchronous work.whileRunning<T>(FutureOr<T> Function(TaskProgress progress) run, {required total, message, success, failure, cancel, isCanceled, display, finalBehavior, interval})- Runs work with aTaskProgresshandle and returns the typed result.trackStream<T>(Stream<T> source, {required total, message, success, failure, cancel, isCanceled, display, finalBehavior, interval})- Collects stream events into aList<T>while advancing progress once per event.clear()- Clears the current framed progress output.- Value behavior - The displayed count, filled width, and percentage all use normalized progress. Positive totals clamp
currentinto0..total, and the percentage stays within0..100%. - Lifecycle - There is no automatic timer for manual rendering. Use
show(...)from your own loop,runWith(...)for synchronous work, orwhileRunning(...)/trackStream(...)for awaited work. - Cleanup behavior - Manual loops should call
clear()infinally.runWithhides the cursor while it runs and clears the bar after the callback returns normally. Async wrappers handle cleanup and final status rendering.
Examples
void main() async {
final bar = terminice.progressBar('Downloading SDK');
const total = 100;
try {
for (var current = 0; current <= total; current += 10) {
bar.show(
current: current,
total: total,
shimmerPhase: current,
);
await Future.delayed(const Duration(milliseconds: 90));
}
} finally {
bar.clear();
}
print('Download complete.');
}
final files = ['a.dart', 'b.dart', 'c.dart', 'd.dart'];
final bar = terminice.pastel.progressBar('Formatting files');
bar.runWith((update) {
for (var index = 0; index < files.length; index++) {
update(index + 1, files.length);
// Format files[index] synchronously here.
}
});
await terminice.progressBar('Uploading').whileRunning(
(progress) async {
for (final file in files) {
await upload(file);
progress.increment();
}
},
total: files.length,
);
final downloads = await terminice.progressBar('Downloading').trackStream(
downloadStream,
total: expectedDownloads,
);
final bar = ProgressBar(
'Uploading archive',
width: 48,
theme: PromptTheme.ocean,
);
try {
bar.show(current: 32, total: 64);
bar.show(current: 64, total: 64, shimmerPhase: 1);
} finally {
bar.clear();
}
Why use this? Use
progressBarwhen you know the total amount of work and want a framed progress readout. UseloadingSpinnerorprogressDotswhen the task has no reliable total.
⤴️ Back → The terminice Catalogue
inlineProgressBar - One-Line Percent Indicator #
Show a compact percentage beside a label. Despite the name, the current implementation is a one-line percent readout rather than a graphical bar, making it useful for dense logs and script output.
inlineProgressBar(String prompt)- Creates anInlineProgressBarcontroller.prompt- Text displayed before the percentage.- Returns
InlineProgressBar- The line is not displayed untilshow(...)is called. InlineProgressBar(String prompt, {theme = PromptTheme.dark})- Direct constructor for an explicit theme.show({required int current, required int total})- Renders a normalized percentage next to the prompt.current/total- Provide the current count and total count. Positive totals clampcurrentinto0..total; whentotal <= 0, the displayed percentage is0.clear()- Clears the current inline progress output.- Value behavior - The displayed percentage follows the same bounded progress rules as
progressBar, staying between0%and100%for positive totals. - Lifecycle - There is no
start,stop, or dedicatedrunWithhelper. Drive it manually from your own loop or timer. - Cleanup behavior - Repeated
show(...)calls replace the previous line. Callclear()when the operation is finished or cancelled.
Examples
void main() async {
final progress = terminice.inlineProgressBar('Indexing');
const total = 50;
try {
for (var current = 0; current <= total; current += 5) {
progress.show(current: current, total: total);
await Future.delayed(const Duration(milliseconds: 50));
}
} finally {
progress.clear();
}
print('Index ready.');
}
final progress = terminice.monochrome.inlineProgressBar('Processed rows');
try {
progress.show(current: 250, total: 1000);
progress.show(current: 1000, total: 1000);
} finally {
progress.clear();
}
final progress = InlineProgressBar(
'Uploading',
theme: PromptTheme.arcane,
);
try {
progress.show(current: 3, total: 12);
} finally {
progress.clear();
}
Why use this? Use
inlineProgressBarwhen you want the smallest possible determinate progress signal. UseprogressBarwhen the user benefits from a framed bar and raw count display.
⤴️ Back → The terminice Catalogue
progressDots - Framed Dot Progress #
Show ambient progress with a title, message, and cycling dots. It is useful for work that is active but not measurable, such as waiting for a service, preparing a cache, or polling an external process.
progressDots(String prompt)- Creates a themedProgressDotscontroller.prompt- Frame title displayed above the dots.- Returns
ProgressDots- The indicator is not displayed until you callshow(...),runWith(...), orwhileRunning(...). ProgressDots(String prompt, {message = 'Working', maxDots = 3, theme = PromptTheme.dark})- Direct constructor for custom text, dot count, or explicit theme.maxDotsmust be greater than0.message- Text displayed before the animated dots.maxDots- Maximum number of dots shown before the animation cycles back to zero dots.show({required int phase})- Renders the dot count for a phase. The count isphase % (maxDots + 1), sophase: 0shows the message with no dots.runWith(void Function(void Function() tick) callback)- Runs a synchronous callback with atick()function. Each tick advances the phase by one. This callback is not awaited and remains for synchronous work.whileRunning<T>(FutureOr<T> Function() run, {message, success, failure, cancel, isCanceled, interval, display, finalBehavior})- Runs async or sync work with dot task rendering and returns the typed result.clear()- Clears the current framed dot indicator.- Lifecycle - There is no automatic timer for manual rendering. Use
show(...)from your own loop,runWith(...)for synchronous work, orwhileRunning(...)for awaited work. - Cleanup behavior - Manual loops should call
clear()infinally.runWithhides the cursor while it runs and clears after the callback returns normally.whileRunninghandles async cleanup and final status rendering.
Examples
void main() async {
final dots = terminice.progressDots('Starting server');
try {
for (var phase = 0; phase < 10; phase++) {
dots.show(phase: phase);
await Future.delayed(const Duration(milliseconds: 120));
}
} finally {
dots.clear();
}
print('Server started.');
}
final dots = ProgressDots(
'Migrating database',
message: 'Applying migrations',
maxDots: 5,
theme: PromptTheme.fire,
);
try {
for (var phase = 0; phase < 12; phase++) {
dots.show(phase: phase);
}
} finally {
dots.clear();
}
void doSynchronousPoll() {
// Check one condition or process one item here.
}
final dots = terminice.neon.progressDots('Waiting for job');
dots.runWith((tick) {
for (var attempt = 0; attempt < 4; attempt++) {
tick();
doSynchronousPoll();
}
});
await terminice.progressDots('Waiting for job').whileRunning(
waitForRemoteJob,
success: 'Job finished',
);
Why use this? Use
progressDotswhen you want calmer feedback than a spinner and do not have a meaningful total for a progress bar.
⤴️ Back → The terminice Catalogue
flow - Sequential Flow Composition #
Compose several prompts and selectors into one synchronous, sequential flow. Use it when a CLI command needs a handful of related answers, conditional follow-up questions, or a typed result map without manually wiring each prompt together.
Flow is sequential composition: steps run from top to bottom and can be skipped with when. It is not a back-navigation wizard yet.
flow(String title)Creates aFlowBuilder.run()- Runs applicable steps in order and returns aFlowResult.- Built-in steps -
text,password,select,checkboxes,confirm, andcustom. - Theming and fallback - Built-in steps call the existing Terminice prompts/selectors through the configured instance, so themes, compatibility modes, and line-mode fallback behavior carry through.
Built-In Steps
text(key, prompt, {placeholder, required, validator, validate, when})- StoresString; cancel returnsnullfrom the prompt and cancels the flow by default.password(key, prompt, {required, maskChar, allowReveal, verify, validate, when})- StoresString; cancel also cancels the flow by default.select<T>(key, prompt, {options, labelBuilder, showSearch, maxVisible, validate, when})- Stores the selectedT?. No selection storesnulland continues, so usevalidatewhen one option is required.checkboxes<T>(key, prompt, {options, initialSelected, labelBuilder, maxVisible, validate, when})- Stores an immutableList<T>.confirm(key, {prompt, message, yesLabel, noLabel, defaultYes, validate, when})- Storesbooland preserves the existing confirm semantics, including cancellation resolving to the default boolean.custom<T>(key, label, {run, validate, when, cancelOnNull})- Runs your own synchronous step. Returningnullcancels by default; setcancelOnNull: falseto storenulland continue.
Result Access
FlowResult keeps collected values in insertion order and preserves partial answers when the flow is cancelled.
confirmed-truewhen every applicable step completed.cancelled-truewhen a cancellable step stopped the flow.cancelledKey- Key of the step that cancelled, ornull.value<T>(key)- Reads a required typed value and throws if the key is missing or has a different type.maybe<T>(key)- Reads a typed value, returningnullwhen the key is absent or stored asnull; wrong non-null types still throw.contains(key)- Checks whether a step wrote that key.toMap()- Returns an insertion-ordered copy of the collected values.
Context, Conditions, and Validation
FlowContext is passed to when, validate, and custom runners. It exposes the configured terminice instance plus the same typed value<T>, maybe<T>, contains, and toMap accessors for values collected by earlier steps.
Flow validators use String? Function(value, context): return null for success, return '' for legacy-compatible success, or return a non-empty error string to reject the step with a FlowValidationException.
For text, validator and validate are different layers. validator runs inside the prompt for immediate text-input feedback; validate runs after the step completes and can inspect earlier flow answers through FlowContext.
Examples
final result = terminice.flow('Create project')
.text('name', 'Project name', required: true)
.select('template', 'Template', options: ['CLI', 'Server', 'Package'])
.checkboxes('features', 'Features', options: ['Git', 'CI', 'Docker'])
.confirm('create', message: 'Create project?')
.run();
if (result.confirmed && result.value<bool>('create')) {
final name = result.value<String>('name');
final template = result.maybe<String>('template') ?? 'CLI';
final features = result.value<List<String>>('features');
print('Creating $name from $template with ${features.join(', ')}');
}
final result = terminice.flow('Deployment')
.select(
'environment',
'Environment',
options: ['dev', 'staging', 'prod'],
validate: (value, _) => value == null ? 'Choose an environment' : null,
)
.text(
'changeId',
'Change request',
required: false,
when: (context) => context.value<String>('environment') == 'prod',
validate: (value, context) {
final isProd = context.value<String>('environment') == 'prod';
if (isProd && value.isEmpty) return 'Production needs a change ID';
return null;
},
)
.custom<DateTime>(
'startedAt',
'Start time',
run: (_) => DateTime.now(),
)
.run();
if (result.cancelled) {
print('Stopped at ${result.cancelledKey}.');
}
Why use this? Use
flowwhen several prompt results belong to one command and later steps should react to earlier answers. Use individual prompts when each question stands alone, and useformwhen multiple text/password fields should render together in one frame.
⤴️ Back → The terminice Catalogue
configEditor - Searchable Nested Settings Editor #
Build a full terminal settings screen from typed Configurable fields. The editor renders a searchable list, opens the right prompt for each value, supports nested groups, tracks modified fields, and returns a ConfigResult only when the root editor is saved.
ConfigResult? configEditor(
String prompt, {
required List<Configurable> fields,
int maxVisible = 18,
});
configEditor(String prompt, {required fields, maxVisible = 18})- Opens the editor withpromptas the frame title.fields- A mutable list ofConfigurableobjects. The editor updates the field instances in place as the user edits values.maxVisible- Upper limit for visible rows. The actual viewport is also clamped to the terminal height.- Returns
ConfigResult?- ReturnsConfigResult(fields: fields, confirmed: true)when the root "Save & confirm" row is selected, ornullwhen the root editor is cancelled. - Empty behavior - If
fieldsis empty, the method returns an immediate confirmedConfigResult. - Search behavior - Search is active by default. Typing filters by field
labelorkey;/toggles search on and off. - Navigation behavior -
↑/↓move through rows,Enteropens the focused field or group, and the first row is the root save action. - Group behavior -
GroupConfigurableopens a nested editor with a "Back" action instead of "Save & confirm". Esc or Back in a group returns to the parent and preserves edits in place; only the root save decides whether a result is returned. - Theme behavior - A
ThemeConfigurableat the current editor level updates the editor theme live after a new theme is selected. The editor also starts with that field's selected theme. - Cancellation behavior - Esc / Ctrl+C at the root returns
null. Field-level cancellation leaves the field's existing value unchanged; fields only update their value when theiredit(...)method accepts a new value. - Validation behavior -
Configurable.validate()calls the field's validator, andGroupConfigurable.validate()checks children. The root save action does not currently run a full validation sweep automatically, so run validation yourself if you need a final gate.
Configurable<T> Basics
Every config field stores display metadata, a typed value, serialization hooks, and its own editor.
key- Stable lookup and serialization key.label- Human-readable row label.description- Optional helper text shown under the focused row.hint- Optional metadata available to field implementations.value- Current typed value.defaultValue- Captured from the initial value and used for change detection.formatter- Optional display formatter. If omitted, the field'sformatValue()is used.validator- Optional function that returns an error string ornull. Returning''is also accepted as a legacy-compatible success value.icon- Optional glyph override. Otherwise each field uses its default type icon.displayValue- Formatted row value.typeIcon- Custom icon or field default.edit(Terminice terminice)- Opens the appropriate prompt and returnstruewhen the field changes.toJsonValue()/loadJsonValue(dynamic)- Convert to and from JSON-compatible values.isModified-truewhenvalue != defaultValue; groups recurse into children.reset()- Restores the initial value; groups reset all children.validate()- Returns an error message ornull.
ConfigResult Behavior
fields- Final top-level fields, including groups.confirmed- Alwaystruefor values returned byconfigEditor; cancellation is represented bynull.toMap()- Serializes top-level fields into{key: value}. Groups serialize as nested maps.get<T>(String key)- Looks up a top-level field value and casts it toT; nested group children are not searched recursively.field(String key)- Returns a top-levelConfigurable, ornull.hasChanges-trueif any top-level field is modified. Group fields report child changes.modified- Top-level fields whose values differ from their defaults.loadFromMap(Map<String, dynamic> map)- Loads matching top-level keys. Group fields load matching child keys from nested maps.
Common Configurable Types
BoolConfigurable- Usesconfirm(...); stores abool; displaystrueLabelorfalseLabel; serializes tobool. The confirm prompt is opened with the current value asdefaultYes, so cancelling leaves the field unchanged.StringConfigurable- Usestext(...)ormultiline(...); stores aString; serializes toString. Single-line editing shows the current value as the placeholder when no explicit placeholder is set; multiline editing starts from an empty buffer.PasswordConfigurable- Usespassword(...); stores aString; masks the list display; serializes toString.NumberConfigurable- Usestext(...)with parsing orslider(...); storesnum; serializes tointwhenintegerOnlyis true.EnumConfigurable- Uses a focused searchable selector; stores aString; the initial value must be inoptions.RangeConfigurable- Usesrange(...); storesRangeValue(start, end); serializes to{'start': start, 'end': end}.RatingConfigurable- Usesrating(...); stores anint; supports optional labels.ThemeConfigurable- Uses a focused selector overMap<String, PromptTheme>; stores the selected theme key; default options come frombuiltInThemes.GroupConfigurable- Opens a nested editor; stores a computed map of child values; serializes children under the group key.
Factory Helper
Use terminice.config when you prefer compact field declarations. It returns a ConfigFactory with helpers that mirror the concrete configurable classes.
terminice.config- Returnsconst ConfigFactory().boolean({required key, required label, value = false, description, hint, formatter, validator, icon, trueLabel = 'Yes', falseLabel = 'No'})string({required key, required label, value = '', description, hint, formatter, validator, icon, placeholder, multiline = false, required = false, visibleLines = 10})password({required key, required label, value = '', description, hint, formatter, validator, icon, maskChar = '•', allowReveal = true, required = false, verify = false})number({required key, required label, required value, description, hint, formatter, validator, icon, min = 0, max = 100, step = 1, unit = '', sliderWidth = 28, useSlider = false, showPercent = false, integerOnly = false})select({required key, required label, required value, required options, description, hint, formatter, validator, icon})range({required key, required label, required start, required end, description, hint, formatter, validator, icon, min = 0, max = 100, step = 1, unit = '%', width = 28})rating({required key, required label, value = 3, description, hint, formatter, validator, icon, maxStars = 5, labels})theme({required key, required label, value = 'dark', description, hint, formatter, validator, icon, themes, onChanged})group({required key, required label, required children, description, hint, icon})
Examples
import 'dart:convert';
import 'package:terminice/terminice.dart';
void main() {
final result = terminice.configEditor(
'App Settings',
fields: [
ThemeConfigurable(
key: 'theme',
label: 'Theme',
value: 'dark',
description: 'Changes the editor palette live.',
),
BoolConfigurable(
key: 'telemetry',
label: 'Telemetry',
value: false,
trueLabel: 'Enabled',
falseLabel: 'Disabled',
),
StringConfigurable(
key: 'appName',
label: 'App Name',
value: 'terminice_app',
required: true,
placeholder: 'my_cli',
),
NumberConfigurable(
key: 'port',
label: 'Port',
value: 8080,
min: 1,
max: 65535,
integerOnly: true,
),
GroupConfigurable(
key: 'security',
label: 'Security',
children: [
PasswordConfigurable(
key: 'apiKey',
label: 'API Key',
verify: true,
),
EnumConfigurable(
key: 'authMode',
label: 'Auth Mode',
value: 'token',
options: ['token', 'oauth2', 'basic', 'none'],
),
],
),
],
);
if (result == null) {
print('Cancelled.');
return;
}
print('Changed: ${result.hasChanges}');
print(const JsonEncoder.withIndent(' ').convert(result.toMap()));
}
import 'package:terminice/terminice.dart';
void main() {
final c = terminice.config;
final result = terminice.matrix.configEditor(
'Release Preferences',
fields: [
c.theme(
key: 'theme',
label: 'Theme',
value: 'matrix',
),
c.string(
key: 'releaseName',
label: 'Release Name',
value: 'v1.0.0',
required: true,
),
c.number(
key: 'parallelJobs',
label: 'Parallel Jobs',
value: 4,
min: 1,
max: 16,
useSlider: true,
integerOnly: true,
),
c.range(
key: 'rollout',
label: 'Rollout Window',
start: 10,
end: 50,
unit: '%',
),
c.rating(
key: 'risk',
label: 'Risk Level',
value: 2,
labels: ['Tiny', 'Low', 'Medium', 'High', 'Critical'],
),
],
);
final jobs = result?.get<num>('parallelJobs');
if (jobs != null) {
print('Running with ${jobs.toInt()} parallel jobs.');
}
}
import 'package:terminice/terminice.dart';
void main() {
final c = terminice.config;
final fields = [
c.string(key: 'host', label: 'Host', value: 'localhost'),
c.number(
key: 'port',
label: 'Port',
value: 8080,
min: 1,
max: 65535,
integerOnly: true,
),
c.group(
key: 'proxy',
label: 'Proxy',
children: [
c.boolean(key: 'enabled', label: 'Enabled'),
c.string(key: 'url', label: 'URL'),
],
),
];
final saved = <String, dynamic>{
'host': 'api.example.com',
'port': 443,
'proxy': {
'enabled': true,
'url': 'http://proxy.example.com:3128',
},
};
ConfigResult(fields: fields, confirmed: true).loadFromMap(saved);
final result = terminice.configEditor(
'Connection',
fields: fields,
maxVisible: 8,
);
if (result != null) {
final connection = result.toMap();
print(connection['proxy']);
}
}
import 'package:terminice/terminice.dart';
void main() {
final fields = [
StringConfigurable(
key: 'packageName',
label: 'Package Name',
required: true,
validator: (value) {
final ok = RegExp(r'^[a-z_][a-z0-9_]*$').hasMatch(value);
return ok ? null : 'Use a valid Dart package name.';
},
),
NumberConfigurable(
key: 'timeout',
label: 'Timeout',
value: 30,
min: 1,
max: 300,
unit: 's',
validator: (value) =>
value < 10 ? 'Timeout should be at least 10 seconds.' : null,
),
];
final result = terminice.configEditor('New Project', fields: fields);
if (result == null) return;
final errors = <String>[];
for (final field in result.fields) {
final error = field.validate();
if (error != null) {
errors.add('${field.label}: $error');
}
}
if (errors.isNotEmpty) {
for (final error in errors) {
print(error);
}
return;
}
print('Config accepted.');
}
Why use this? Use
configEditorwhen a CLI needs a durable settings workflow with typed values, nested groups, live theme selection, and JSON-friendly output. Use individual prompts when you only need one or two answers.
⤴️ Back → The terminice Catalogue
cheatSheet - Framed Reference Table #
Render a themed, non-interactive reference table inside a FrameView. It is designed for command lists, shortcut cards, option summaries, or any small table users may want to glance at while using a CLI.
void cheatSheet(
String prompt, {
required List<List<String>> entries,
List<String> columns = const ['Command', 'Shortcut', 'Usage'],
List<ColumnAlign>? columnAlignments,
bool zebraStripes = true,
});
cheatSheet(String prompt, {required entries, columns, columnAlignments, zebraStripes = true})- Renders the table immediately.prompt- Frame title displayed above the table.entries- Table rows. Every row must contain the same number of cells ascolumns.columns- Header labels. Defaults to['Command', 'Shortcut', 'Usage']and must not be empty.columnAlignments- Optional per-column alignment list usingColumnAlign.left,ColumnAlign.center, andColumnAlign.right. When omitted, all columns are left-aligned.zebraStripes- When true, alternating rows are dimmed by the table renderer.- Return behavior - Returns
void; it renders once and does not wait for input. - Assertion behavior - Asserts that
columnsis non-empty, every row matches the column count, andcolumnAlignmentsmatches the column count when provided. - Rendering behavior - Column widths are computed from the headers and entries, and cells are padded by alignment. There is no terminal-width wrapping in
cheatSheet, so keep row text concise. - Import note - Basic usage only needs
package:terminice/terminice.dart. If you useColumnAligndirectly, import it frompackage:terminice_core/terminice_core.dart.
Examples
import 'package:terminice/terminice.dart';
void main() {
terminice.cheatSheet(
'Navigation Shortcuts',
columns: const ['Command', 'Shortcut', 'Usage'],
entries: const [
['List files', 'ls', 'Show directory contents'],
['Change directory', 'cd <path>', 'Move into a folder'],
['Print directory', 'pwd', 'Show the current path'],
],
);
}
import 'package:terminice/terminice.dart';
import 'package:terminice_core/terminice_core.dart' show ColumnAlign;
void main() {
terminice.ocean.cheatSheet(
'Deployment Matrix',
columns: const ['Env', 'Region', 'Replicas', 'Status'],
columnAlignments: const [
ColumnAlign.left,
ColumnAlign.center,
ColumnAlign.right,
ColumnAlign.left,
],
entries: const [
['staging', 'us-east', '2', 'ready'],
['production', 'eu-west', '8', 'locked'],
['sandbox', 'local', '1', 'open'],
],
);
}
final rows = <List<String>>[
for (final command in ['build', 'test', 'publish'])
[
'terminice $command',
command == 'publish' ? 'Ctrl+P' : 'Enter',
'Run the $command task',
],
];
terminice.fire.cheatSheet(
'CLI Commands',
entries: rows,
zebraStripes: false,
);
Why use this? Use
cheatSheetwhen the information is static and should stay compact. UsehelpCenterwhen the user needs search, preview text, and a selected document result.
⤴️ Back → The terminice Catalogue
helpCenter - Searchable Help Browser #
Launch an interactive help viewer with live search, a result list, and a preview pane. It returns the selected HelpDoc, which makes it useful for opening follow-up docs, routing to a tutorial, or tracking which help topic the user chose.
HelpDoc? helpCenter({
String title = 'Help Center',
required List<HelpDoc> docs,
int maxVisibleResults = 10,
int maxPreviewLines = 8,
});
const HelpDoc({
required String id,
required String title,
required String content,
String? category,
});
helpCenter({title = 'Help Center', required docs, maxVisibleResults = 10, maxPreviewLines = 8})Opens the interactive viewer.title- Frame title.docs- Searchable document list. If empty,helpCenterreturnsnullimmediately.maxVisibleResults- Maximum visible rows in the result list.maxPreviewLines- Maximum lines shown in the preview pane.- Returns
HelpDoc?- The selected document on Enter, ornullwhen cancelled or no document is selected. HelpDoc.id- Stable identifier for routing, analytics, or persistence.HelpDoc.title- Main result label.HelpDoc.content- Markdown or plaintext body shown in the preview pane. It is displayed as text; Markdown is not parsed into rich terminal formatting.HelpDoc.category- Optional grouping label appended to the result row.- Search behavior - Typing filters by title, category, and content. Title matches are ranked before content-only matches.
- Controls -
↑/↓move through results,←/→scroll the preview through content lines,Enterconfirms, and Esc / Ctrl+C cancels. - Preview behavior - The preview resets to the top when the selected result or search query changes.
Examples
import 'package:terminice/terminice.dart';
void main() {
final doc = terminice.helpCenter(
docs: const [
HelpDoc(
id: 'shortcuts',
title: 'Keyboard shortcuts',
category: 'Basics',
content: 'Use arrow keys to move and Enter to confirm.',
),
HelpDoc(
id: 'config',
title: 'Configuration files',
category: 'Advanced',
content: 'Store settings as JSON and load them before configEditor.',
),
],
);
if (doc != null) {
print('Selected help topic: ${doc.id}');
}
}
final docs = [
const HelpDoc(
id: 'deploy',
title: 'Deploy a service',
category: 'Workflows',
content: '''
1. Build the project.
2. Run the smoke tests.
3. Publish the release artifact.
4. Promote the deployment.
''',
),
const HelpDoc(
id: 'rollback',
title: 'Rollback safely',
category: 'Workflows',
content: '''
Use rollback when a deployment fails health checks.
Check logs before promoting the previous artifact.
''',
),
];
final selected = terminice.arcane.helpCenter(
title: 'Operator Help',
docs: docs,
maxVisibleResults: 6,
maxPreviewLines: 5,
);
switch (selected?.id) {
case 'deploy':
print('Opening deploy runbook...');
break;
case 'rollback':
print('Opening rollback runbook...');
break;
}
List<HelpDoc> commandDocs(Map<String, String> commands) {
return [
for (final entry in commands.entries)
HelpDoc(
id: entry.key,
title: entry.key,
category: 'Command',
content: entry.value,
),
];
}
final chosen = terminice.helpCenter(
title: 'Command Reference',
docs: commandDocs({
'init': 'Create a starter project in the current directory.',
'doctor': 'Check whether the local environment is ready.',
'publish': 'Upload the package after validation passes.',
}),
);
print(chosen?.title ?? 'No topic selected.');
Why use this? Use
helpCenterwhen documentation needs search and a meaningful return value. UsecheatSheetwhen a simple static table is enough.
⤴️ Back → The terminice Catalogue
hotkeyGuide - Interactive Shortcut Guide #
Show keyboard shortcuts in a compact framed grid and wait until the user closes it. This is a good companion for complex prompts, editors, command palettes, or any CLI mode with more than a few bindings.
void hotkeyGuide({
required List<List<String>> shortcuts,
String title = 'Hotkeys',
List<String> footer = const ['Esc or ? to close'],
});
hotkeyGuide({required shortcuts, title = 'Hotkeys', footer = const ['Esc or ? to close']})Renders the guide and blocks until a close key is pressed.shortcuts- Rows passed toHintFormat.grid(...). Each row should use the same number of columns for clean alignment.title- Frame title.footer- Hint strings rendered after the grid using comma formatting. Pass an empty list to omit the footer.- Return behavior - Returns
void; it does not report which close key was used. - Controls - Esc, Enter, or
?closes the guide. Ctrl+C is also handled by the standard cancel binding. - Rendering behavior - The guide uses the current
Terminicetheme andHintFormat.gridfor aligned shortcut columns. - Data shape - A two-column row usually works well for key/action pairs; three columns are useful when you want context or notes.
Examples
import 'package:terminice/terminice.dart';
void main() {
terminice.hotkeyGuide(
title: 'Selection Shortcuts',
shortcuts: const [
['↑ / ↓', 'Move focus'],
['Space', 'Toggle item'],
['Enter', 'Confirm selection'],
['Esc', 'Cancel'],
],
);
}
terminice.neon.hotkeyGuide(
title: 'Editor Hotkeys',
shortcuts: const [
['Ctrl+D', 'Save multiline input', 'Editor'],
['Ctrl+R', 'Reveal password', 'Password fields'],
['/', 'Toggle search', 'Lists'],
['?', 'Close this guide', 'Help'],
],
footer: const ['Enter to close', 'Esc to close', '? to close'],
);
void showPromptHelp() {
terminice.compact.hotkeyGuide(
title: 'Prompt Controls',
shortcuts: const [
['← / →', 'Adjust sliders or buttons'],
['↑ / ↓', 'Navigate lists'],
['Type', 'Filter searchable prompts'],
['Ctrl+C', 'Cancel current prompt'],
],
footer: const [],
);
}
Why use this? Use
hotkeyGuidewhen shortcuts are part of the workflow and users need an in-terminal reminder. UsecheatSheetfor broader reference data that is not specifically about keys.
⤴️ Back → The terminice Catalogue
themeDemo - Interactive Theme Gallery #
Preview curated PromptTheme presets in the terminal, then optionally open a sample selector using the highlighted theme. The method is intentionally a demo utility: it does not return the selected theme, but it is handy for screenshots, onboarding, and choosing a palette before wiring a theme into your own CLI.
void themeDemo();
themeDemo()- Opens the theme preview gallery.- Themes shown -
Dark,Matrix,Fire,Pastel,Ocean,Monochrome,Neon,Arcane, andPhantom. - Preview content - Shows theme glyphs for arrows, checkboxes, borders, highlighted text, and inverse text.
- Controls -
↑/↓browse themes,Enteraccepts the highlighted theme and opens a samplesearchSelector, and Esc / Ctrl+C exits without launching the sample prompt. - Return behavior - Returns
void; the chosen theme name is not returned to your code. - Preview prompt behavior - Pressing Enter launches a multi-select
searchSelectorover sample fruit names using the selected theme. - Theming note - The gallery uses its own curated theme map rather than the caller's
defaultTheme, soterminice.fire.themeDemo()still starts from the built-in gallery. - Selection note - If you need a user-selectable theme in a real settings flow, use
ThemeConfigurableinsideconfigEditorinstead.
Examples
import 'package:terminice/terminice.dart';
void main() {
terminice.themeDemo();
}
import 'package:terminice/terminice.dart';
void main() {
terminice.themeDemo();
final theme = terminice.searchSelector(
prompt: 'Use which theme?',
options: const [
'dark',
'matrix',
'fire',
'pastel',
'ocean',
'monochrome',
'neon',
'arcane',
'phantom',
],
);
print('Theme choice: ${theme.isEmpty ? 'none' : theme.first}');
}
import 'package:terminice/terminice.dart';
void main() {
final result = terminice.configEditor(
'Theme Settings',
fields: [
ThemeConfigurable(
key: 'theme',
label: 'Theme',
value: 'ocean',
description: 'Pick a PromptTheme for the rest of the app.',
),
BoolConfigurable(
key: 'compactMode',
label: 'Compact Mode',
value: false,
),
],
);
print(result?.toMap() ?? 'cancelled');
}
Why use this? Use
themeDemoto inspect the visual language of Terminice themes quickly. UseThemeConfigurablewhen your application needs to save the chosen theme.
⤴️ Back → The terminice Catalogue








