Thoughtful CLI design πŸ”—

A command-line interface (CLI) is one of the oldest, and still one of the most useful, ways to interact with a computer. CLIs provide a text-based interface to the user (whether a human or program) to perform various functions or can provide starting values used to launch a graphical user interface (GUI).

A familiar example of a CLI is the ls command that is ubiquitous across Unix and Linux operating systems, including macOS. Short for "list", its function is to list files and directories on your system.

Anatomy of a CLI πŸ”—

A basic CLI is a program that takes commands, flags, and arguments, and produces some sort of output in response.

  • Commands (and sub-commands) often represent a particular action to take. For some CLIs, the only command is the CLI name itself. In other cases, a CLI could have various functions that it can perform, each represented by a sub-command. Sub-commands can have further sub-commands of their own.

  • Flags, also referred to as switches, are optional operators that can modify the operation of the command they are attached to. Flags can stand alone, in which case they behave as boolean values (either the flag is present or is not), but can also take data in the form of arguments.

    Flags in Linux/Unix programs are prefixed with - (dash, or hyphen) for "short"-style flags, which consist of a single character and which can often be combined together (for example: cp -uav to copy files using the -u, -a, and -v switches), and -- (two consecutive dashes) for "long" commands (--update, --archive, and --verbose, respectively), which are more descriptive but which cannot be combined as short flags are. For example, these are the same:

    cp -uarv ~/src/myprog /mnt/Archive/src/
    
    cp --update --archive --recursive --verbose ~/src/myprog /mnt/Archive/src/
    

    Clearly, one of these lines is easier to type, while the second is easier to understand. This is often the trade-off between these flag styles.

    On Microsoft Windows, flags are typically prefixed with / and are rarely combined together as they are on Linux/Unix systems.

  • Arguments are data provided by the user to satisfy the needs of either a command or flag. Arguments can be either required---something needed by the command or flag to function at all---or optional, in the case where there is either a default value that's used in the absence of an argument, or where the value can be auto-generated by the program unless the user wants to use a value of their own choosing.

  • Output can be in text form as output to the user (printing to stdout), but may also produce a file or perform other tasks.

Commands and subcommands πŸ”—

Commands and subcommands represent a particular action, or heirarchy of actions, to take.

A good example of this is the git CLI, where you type:

git subcommand [optional flags]

For example:

git commit -am "My commit message"

Here, the command is git, the sub-command is commit, and the flags and argument(s) are -a, -m and "My commit message" (the argument for -m).

Subcommands are not always required, especially in the case of a simple CLI, but can provide a nice way to structure your program and make its interface easy to understand.

Required vs optional input πŸ”—

The inputs to your CLI are its user interface. Keeping them comprehensible and easy to understand is a benefit to both your users and to you, the designer and maintainer of the CLI. Some reasonable rules help to thwart interface chaos:

  • If a command or argument is required to perfom a given function, it should be either a positional argument or a sub-command name. Avoid cases where flags are required input! In some cases, an optional flag may be required when using another optional flag, but these conditions should be minimized where possible.

  • Reasonable defaults should be chosen for commands so that extra flags aren't required for typical (designed) use of your CLI. This makes it easier for your users by allowing them to input the minimal amount of information in order to get the CLI to function as expected.

  • Flags should be reserved for optional (which, by extension, should represent non-typical) use of the given command or sub-command.

Designing flags and switches πŸ”—

Flags and switches represent the optional parts of your CLI interface, providing additional capability to users beyond the CLI's default behavior.

Here are what I consider to be best practices:

CLI flags (switches) may provide both a long and/or short form. For example, the --help command typically uses -h as the "short" form. A --version flag might use either -v or -V.

CLI success or error codes πŸ”—

A CLI will produce a return code to the environment it's run within: Typically 0 (zero) to signal successful completion of the task, or any non-zero integer for unsuccessful completion.

In C, this code is what's returned from the main function:

int main(int argc, char** argv) {

   int retval = 0;

   /* Maybe something happens that changes 'retval' to a non-zero state...? */

   return retval;
}

In other languages, it's returned by the main process, in whatever form provided by that language.

Return codes 126 and above have particular meanings in a Linux/Unix shell, so shouldn't be reported by your CLI. However, that leaves plenty of other numbers to choose from! Many programs will only return a generic result of 1 when an error occurs.

Help text πŸ”—

Help text should always be provided when the user enters either -h or --help. Additionally, provide helpful instructions to the user if the user enters incorrect input.

If -h or --help is used, then print help based on the amount of command/sub-command completion provided. In other words, if I type to the terminal:

dhop forget --help

I expect to get help about the dhop forget command/subcommand, not general help for dhop.

If the user, instead, tries to use the command but provides incorrect output, explain the problem and any possible solutions. This may include printing the expected command inputs, or a reminder to use --help to get the full help text. For example:

$ dhop forget unknown

ERROR: 'unkown' is not a named location! To get a list of named locations,
use 'dhop list'. Run 'dhop forget --help' for more information.

Help text format πŸ”—

When designing help text for a command or sub-command, it's useful to provide some common sections:

Description

Provide a brief description of your CLI or sub-command as directly and in as few words as possible.

Usage

The "Usage" section should describes the interface of your CLI at the current sub-command level.

Required arguments should be described here.

Flags

Provide a list of optional flags that modify the default behavior at the current command level. Each flag should include its long and short form.

Subcommands

If there are any subcommands at this level of your CLI's interface, then describe them, providing a short description of each subcommand (a longer description can be provided when the user uses --help for that subcommand).

CLI accessibility πŸ”—

CLIs are, in general, seen as less accessible for users with visual impairments, though there are choices you can make in your design that will either contribute to, or help alleviate, some of the issues with accessibility.

Command and flag names πŸ”—

For accessibility, command and flag names should:

  • Be lower-case, or case-insensitive

    Screen-readers don't typically distinguish between upper-case and lower-case letters, and will usually just read back the text that is displayed on the screen, word-for-word. If your CLI requires the use of capitalization in some parts of its interface, it will be extremely difficult for blind users to use the CLI at all.

    It also follows from this that short-form flags should be lower-case or case insensitive. Never use a capitalized version of a short flag to mean anything different than the lower-case version.

    If you just use lower-case for everything in your CLI interface, then these concerns are made moot.

  • Use hyphens or underscores to separate words

    This aids both a screen reader in discerning the words that make up a longer command or flag name, and also aids those who are visually impaired (not necessarily blind), to read the command/flag name, as well. Don't use capitalization to separate words, as explained in the previous point.

  • Be simple and pronounceable

    Screen readers will try to pronounce the words that it reads. If the command name isn't a discernable word (or made of discernable words, separated by dashes or underscores), the reader will often read each letter in sequence, which is much more difficult to understand and remember than a simple, easy to understand word.

  • Be consistent, and adhere to CLI standards

    This means to be internally-consistent with your commands and flags (for example: the flag -v and its upper-case variant -V should mean the same thing wherever it is used, and not mean --verbose for some commands, and --version in others). Subcommands should be likewise internally consistent. A subcommand list should do the same thing, and behave similarly for all commands in which it appears (its output should always be in the same format).

    It also means to be as consistent with other CLIs as is practical. Follow common practices, such as using a single-dash (-) prefix for short-form flags (ex: -h), or a double -- prefix (ex: --help) for long-form flags. Always include a -h/--help flag to print command-line help.

    Inconsistency creates confusion and makes a CLI more difficult to use. Consistency allows both users and the creators of accessibility aids to readily identify patterns in your CLI's interface and navigate it successfully.

Output πŸ”—

For accessibility, the output of your CLI should generally be:

  • Plain text, and human-readable

    Screen readers make a mess of structured text formats such as grid tables, JSON, HTML, and the like. Output should be simple and readable unless it is specified in your interface's help text that output follows a particular strucured output format.

    For output in structured formats, consider adding a separate flag (such as --json) to emit these outputs and make your CLI interface more consistent and discernable.

  • Present whether the command succeeds or fails

    Commands should not silently succeed or fail. Don't rely on error-codes to signal success or failure. It should always output something, which should be a clear message about what the command did or what error occured. If it wrote a file, or changed the state of anything ("changed X instances of Y to Z"), report that to the user upon success.

  • Not rely on color or other visual formatting to get the point across

    Color and visual formatting such as bold text can add cues which can add both meaning and visual appeal to your interface (for example, using different colors for informational output, warnings, and errors). Such information is not usually reported by screen readers, and colors can cause reading issues for those with contrast impairments or may be unseen at all by those with varieties of color blindness or true blindness.

    Use clear output and signify in words ("Error:", "Important:", "Success:") what any such colors or visual formatting means.

Read more πŸ”—

Further articles on the subject of CLI design:

External resources πŸ”—

Oft-cited and worth reading: