Understanding Colors and Styles in Terminal Output

Published: September 16, 2024
Written by:
Natan Rolnik
Natan Rolnik

Mobile and frontend developers know how important UI design is for the success of their apps. When working with command line tools, however, visual design is not the first thing that comes to our minds. In the other hand, although the design is much more limited, one could still think of better ways to communicate outputs visually, even when the user is running commands in the Terminal.

In this post, you will learn how styles and colors work in a Terminal output, and how to implement them in your Swift CLI.

How Styles Work

In case you wondered how an executable can output text with different styles, the answer might be simpler than you thought. It does require some effort to understand the pattern, and the different options available, but it’s not rocket science.

An output can receive extra text attributes by being enclosed by specific escape sequences. Any text with these sequences are “translated” into attributes when the Terminal displays them. As a first example (which you’ll learn about it more soon), imagine a Hello, world text being displayed in the red color. To achieve it in Swift, this is what you need:

print("\u{001B}[31mHello, world\u{001B}[0m")

To quickly visualize this, you can use the Swift REPL (which stands for Read-Eval-Print Loop), to run Swift code right in the Terminal, without having to create any Swift files. Run swift repl, and then copy and paste the line of code above. This is what you should see:

Running a print with color in the Swift REPL
Running a print with color in the Swift REPL

You can exit the REPL mode with :q.

Understanding ANSI Escape Codes

As stated in the previous section, these characters are initially scary, but you can understand it by breaking the expression apart:

Breaking apart a red output
Breaking apart a red output

The first sequence of “weird characters” you see is the first command, which stands for “set, from now on, the foreground color to red”. Then, you see the contents of the string, followed by another command: reset the style back to default.

These are called ANSI escape sequences, a standard for controlling display and cursor location in Terminal.

The image below can help understand a single command:

A "Foreground Red" command A "Foreground Red" command
A "Foreground Red" command

Here’s what this command means, bit by bit:

  1. Start a command with the escape character, represented by the unicode literal character 001B
  2. Using the square bracket [ as the Control Sequence Introducer, noting the sequence is about to begin
  3. Denote foreground color with the digit 3, and 1 for the color itself, red
  4. Finally, end the command with the m character

In the end of the print, the reset command was used. You can notice it also starts with the escape unicode character, and finishes with the m character. The digit for that command is 0:

The reset command The reset command
The reset command

Other ANSI Commands

In a similar way, other commands follow the same pattern. What changes is the digit between the escape and m characters:

DigitFunction
0Reset
1Bold
2Dimmed
3Italic
4Underline
30-37Foreground color
40-47Background color
90-97Bright foreground color
100-107Bright background color

For the color commands, the second digit represents the color. 1 is red, 2 is green, 3 is yellow. For the full list of commands and colors, you can see the this Wikipedia page.

Additionally, multiple commands can be concatenated in the same escape sequence, separated by the ; character. For example, \u{001B}[1;3m will be bold and italic.

Applying ANSI Codes on String

Now that you understand how to use escape codes, it gets easier to apply them on Swift.

To make it easier to follow this tutorial, you can download the starter sample project and follow along.

The sample project, named stylish, is a tool that receives a string and a color name, and outputs it to the console. After opening the Package.swift file and the project, you can notice there are a few existing files prepared beforehand:

  • stylish.swift is the entrypoint, containing already the properties that the Swift Argument Parser will convert the executable input to
  • Color is an enum that represents the supported colors. It conforms to ExpressibleByArgument, so it can be passed as an argument
  • In Style.swift, there are string extension methods that add the commands for setting colors, background colors and bold styles

In the Color and ANSICommand enums you’ll find a few properties and methods that haven’t been yet implemented. Don’t run yet the tool: it will crash because they haven’t been implemented yet, and this is what you’ll do next.

Implementing the ANSI Code for Colors

In the Color enum, you can notice that there’s a ansiCode property. Delete the fatalError, and replace it with the following code:

extension Color {
    var ansiCode: Int {
        switch self {
        case .black: 0
        case .red: 1
        case .green: 2
        case .yellow: 3
        case .blue: 4
        case .white: 7
        }
    }
}

These are the codes for the colors in the existing enum.

Implementing ANSI Commands

The second part is to implement the ANSI commands, in the ANSICommand.swift file.

Start at the bottom of the file, by implementing the extension on String. Replace the fatal error in the ansi property with the code below:

private extension String {
    var ansi: String {
        "\u{001B}[" + self + "m"
    }
}

This will convert a command code into an ANSI command, prepending the unicode character and the Control Sequence Introducer ([), and appending the m, which closes the command.

Next, implement the command for the styles. Starting by the simpler ones, add the following code inside the ANSICommand enum:

    static let reset = "0".ansi
    static let bold = "1".ansi
    static let italic = "3".ansi
    static let underline = "4".ansi

These are the same digits you saw in the table a few sections above.

To apply colors, you’ll need functions instead of properties:

    static func foregroundColor(_ color: Color) -> String {
        "3(color.ansiCode)".ansi
    }
    
    static func backgroundColor(_ color: Color) -> String {
        "4(color.ansiCode)".ansi
    }

Remember how foreground colors are in the 30-37 range, and background colors 40-47? This is where you implement those numbers.

Applying the Styles

The final bit left to implement is actually applying the styles in the strings.

Open the Style.swift, and implement the emtpy methods:

extension String {
    mutating func applyColor(_ color: Color) {
        self = ANSICommand.foregroundColor(color) + self + ANSICommand.reset
    }

    mutating func applyBackground(_ color: Color) {
        self = ANSICommand.backgroundColor(color) + self + ANSICommand.reset
    }

    mutating func bold() {
        self = ANSICommand.bold + self + ANSICommand.reset
    }
}

You can see that, for each function, there’s the opening command (foreground, background, and bold), followed by the string itself, which is appended by the reset command. Notice how the function needs to be marked with mutating, as self is redefined. You could also implement a function that returns the modified string, without mutating the reference to the string.

You just finished implementing all the missing pieces! Time to run the tool.

Running Stylish

In the root of the project, now you can run the executable.

Back to terminal, you can run the following command:

swift run stylish "Hello, from Terminal" --color yellow

If you added all the correct bits of code, you should see the following result:

Voilá! Yellow output!
Voilá! Yellow output!

You can also use the other parameters combined:

swift run stylish "Hello, from Terminal" --color black --background red --bold

Which results in:

White on red background, and bold
White on red background, and bold

Wrapping Success, Warning and Error Messages

Back to a real world scenario: sometimes when commands your tool runs finish successfully or errors (or generate warnings) you could use a UI enum to wrap these messages:

enum UI {
    static func success(_ string: String) {
        print(string.applyingColor(.green))
    }

    static func warning(_ string: String) {
        print(string.applyingColor(.yellow))
    }

    static func error(_ string: String, emphasize: Bool = false) {
        if emphasize {
            print(string.applyingBackground(.red).applyingColor(.white).applyingBold())
        } else {
            print(string.applyingColor(.red))
        }
    }
}

The code above requires a non-mutating implementation of the functions in Style.swift

With that, you could replace your print calls in your tools and communicate the output to the user in a better way:

UI.success("Upload succeeded")
UI.warning("Verify lint in the following files: (files)")
UI.error("Authentication required")

Alternative Packages

If you are a developer who likes to avoid dependencies, you can use the code in this article to create your own types and methods for helping you with adding styles to your executables.

However, if you prefer to just drop a package, avoid reinventing the wheel, there are also two great options:

  • ColorizeSwift - a single file package by Michal Tynior
  • Console-Kit - a more robust package, maintained and used by Vapor, with a more comprehensive set of APIs for interactive CLI tools

Explore Further

We hope you expanded your knowledge with this post. After learning how to do add styles and colors, one realizes it is easier than initially thought!

In case you have any suggestions, comments or questions, reach us at X or Mastodon.

As always, you can checkout the starter and final sample projects on GitHub.

Here are some things you can practice now:

  • Add support for more styles in the sample project, based on the full list of codes
  • Add more cases to the Color enum, as well as flags for italic and other styles
  • When having multiple styles, try merging them under the same command with the ; separator, instead of having nested commands
  • If you’re feeling adventurous, you can explore the Console-Kit source code to understand how to do more complex UI elements in the Terminal, as well as outputs
See you at the next post. Have a good one!
Swift, Xcode, the Swift Package Manager, iOS, macOS, watchOS and Mac are trademarks of Apple Inc., registered in the U.S. and other countries.

© Swift Toolkit, 2024-2025