A CLI command may be called with a
--verbose or similar flag to increase output, or a
--silent or similar flag that suppresses progress feedback along with other output.
Apart from that, a command should provide enough progress feedback to:
- reassure you that the command has not stalled;
- give you an idea, for long-running tasks, of how much longer you will need to wait.
This is separate from the issue of whether each line of feedback should follow or replace the previous one.
All progress feedback should be printed on stderr.
Grammar and punctuation
Progress text should always start with a capitalised gerund verb with ellipsis — for example, “
Deploying…”, or “
Attaching…”. This minimizes the chance that it will be misinterpreted as an instruction or prompt.
Text update frequency
Don’t assume that a particular command or subtask will always be too quick to need progress indication: someday it may be slowed by a flaky disk or Internet connection.
Progress text should update every 0.5 to two seconds. Quicker than 0.5 seconds, it will be distracting and hard to read, potentially limiting accessibility. Slower than two seconds, it will become less reassuring.
This doesn’t require the command, or a subtask, to calculate whether it will take longer than two seconds. Instead it can start showing more detail if, and when, it already has taken longer than that. For example, if a whole command takes less than two seconds, it needn’t have any progress feedback at all.
↓ (after 2 seconds)
Building… step 2/6: copying
↓ (after another 2 seconds)
Building… step 2/6: copying 214 MB of 1.6 GB
Determinate progress: the progress bar
Don’t print a percentage of task completion. Reserve text for more informative, task-specific units of measurement, such as file size or number of files processed.
Instead, if it is possible to calculate an overall proportion of the task complete, show this with a progress bar: reversing foreground and background colours of the appropriate portion of the whole line.
A progress bar should always fill, for a successful task, exactly once. That is, if the task succeeds, the bar should completely fill; if the task fails, it should not fill; and it should never restart over the course of the command.
Where a command has multiple subtasks, they might deserve a line of progress text each, especially in a
--verbose mode. But in the usual case, where there is just one line of progress text, avoiding restart of the progress bar requires allocating a portion of the line to each subtask. It isn’t necessary for the proportion to be exactly accurate for each invocation — even a progress bar that varies in speed is better than none at all. If the first subtask is indeterminate, the progress bar can begin after that. If any other subtask is indeterminate, its portion can fill asymptotically based on the average duration.
Because a progress bar doesn’t add or replace text, it can update constantly.
Determinate progress: time remaining
If it is possible to make a reasonable estimate of time remaining, consider including this at the far end of the same line as other progress text.
m”, or “
s”, rather than colons, to avoid ambiguity about the units of measurement used in the estimate. An estimate will hardly ever be accurate enough to need more than two units at once.
Update the estimate as frequently as other progress text. If it becomes likely that the estimate is completely wrong, remove it altogether — or replace it with an appropriate word, like “stalled” — rather than increasing it indefinitely.
Indeterminate progress: the spinner
Use a spinner as a last resort, if there is no more informative way of ensuring that the progress line is changing at least every two seconds. A spinner should be:
- a cycle of the characters “⬬”, “●”, “⬮”, and “●”, repeating every second
- positioned at the end of the line, to avoid jumping when other progress text changes in length.
Indeterminate progress: time elapsed
Displaying time elapsed is a last resort if there is no way of providing determinate progress, for a long-running task or subtask where just a spinner is not enough. For example, when this system has contacted another system and is waiting for a reply, with no idea how long it will take. The user can then decide for themselves how long is too long to wait.
To distinguish it from an estimate of time remaining, put time elapsed in brackets, with ellipsis, immediately after the other progress text.