Principles of Good CLI Design

February 22, 2026 · 11 min read · Developer Tools Software Design Terminal

Most CLIs are designed by people who already know how to use them. The flags make sense if you wrote the code. The error messages are obvious if you know the internals. This is how you end up with tools that feel hostile to everyone else.

Good CLI design is a discipline. Here are the principles I keep coming back to.


1. Output for Humans by Default, Machines on Request

Your default output should be readable. When a developer runs a command, they should immediately understand what happened. Save the machine-parseable format for --json or --format=csv.

The rule: don't force people who pipe your output into jq to also grep through ANSI escape codes.


2. Fail Loudly and Precisely

When something goes wrong, the error message should: - Say what failed - Say why it failed (if known) - Suggest what to do next

Vague errors are wasted opportunities.

A good error message reads like a note from a helpful colleague, not a stack trace from a machine.


3. Show Progress for Anything That Takes Time

If a command takes more than half a second, the user needs feedback. Silence feels like a hang. A spinner tells them the process is alive. A progress bar tells them how much longer to wait.

Disappearing progress lines (replaced by a clean completion message) are better than a wall of scrolling output.


4. Respect the Unix Pipeline

Your tool should be a good citizen. That means:

  • Write normal output to stdout
  • Write errors and logs to stderr
  • Exit with non-zero status codes on failure
  • Read from stdin when it makes sense

A tool that mixes errors into stdout breaks every pipeline downstream. Stderr is there for a reason.


5. Sensible Defaults, Escape Hatches Always

The default behavior should be right for 90% of use cases. But that 10% needs an escape hatch—a flag, an env variable, a config file. Don't make them fight the tool.

Precedence order: CLI flags > env vars > config file > built-in defaults. Document this explicitly.


6. Write --help That Actually Helps

--help is often the only documentation a developer reads. Make it count.

Good --help has: a one-line description, usage pattern, subcommands, flags with defaults, and real examples. Every subcommand gets its own --help.


7. Ask Before You Destroy

Destructive operations should require explicit confirmation. But don't nag for routine tasks—that trains people to type y without reading.

Typing the resource name is harder to bypass than y. Reserve it for truly irreversible actions. For recoverable things, a simple --force flag is enough.


Putting It Together

These principles compound. A tool that fails with a clear message, shows progress, respects pipes, and has a real --help is one you trust. You know what it's doing. You know what went wrong when it fails.

The best CLIs feel like a quiet colleague: they do their job without fuss, speak up when something's wrong, and never waste your time.


Good CLI design is mostly about empathy. Build for the person running the command at 2 AM wondering if the deploy is hung or just slow.