Skip to content
Go back

How to Build a Beautiful Powerline Status Line for Claude Code

Edit page

Retro tv that shows color palette

If you use Claude Code (Anthropic’s CLI tool), you might have noticed something: the default status line is… functional. But functional doesn’t mean beautiful. And if you’re someone who spends hours in the terminal, you deserve something that looks as good as your Starship prompt.

I decided to fix this. Here’s what I built:

Beautiful Powerline Status Line

A Powerline-style status line with:

Let me show you how to build this yourself.


Prerequisites

Before we start, you’ll need:

  1. A Nerd Font installed - I use Hack Nerd Font, but any Nerd Font works
  2. A terminal that supports true colors - Ghostty, iTerm2, Kitty, Alacritty, or WezTerm
  3. Claude Code installed - Obviously!
  4. Basic understanding of bash - We’ll write a shell script

Check Your Font

Open your terminal and run:

echo -e "\ue0b0 \ue0b2 \ue0b6"

You should see three arrow shapes. If you see boxes or question marks, you need to install a Nerd Font and configure your terminal to use it.


The Architecture

Claude Code’s status line works through a simple mechanism:

  1. You define a command in ~/.claude/settings.json
  2. Claude Code runs this command and displays the output
  3. The command receives JSON data about the current session via stdin

The JSON includes:

We’ll write a bash script that:

  1. Reads this JSON
  2. Adds git information
  3. Formats everything with colors and icons
  4. Outputs a beautiful Powerline status line

Installation

Step 1: Create the Script

Create a file at ~/.claude/statusline-command.sh:

#!/bin/bash

input=$(cat)
current_dir=$(echo "$input" | jq -r '.workspace.current_dir // .cwd')
ARROW_RIGHT=$(printf '\xee\x82\xb0')
ARROW_LEFT=$(printf '\xee\x82\xb2')
ARROW_ROUND=$(printf '\xee\x82\xb6')
ICON_PLUS=$(printf '\xef\x81\x95')
ICON_MINUS=$(printf '\xef\x81\x96')
ICON_SONNET=$(printf '\xee\xb8\xb4')
ICON_OPUS=$(printf '\xee\xb5\xa2')
ICON_HAIKU=$(printf '\xee\x9f\x95')
ICON_GIT=$(printf '\xee\x82\xa0')
ICON_DIFF=$(printf '\xf3\xb0\xa6\x93')
FG="251;241;199"
BG="60;56;54"
COLOR_DIR="47;62;70"
COLOR_GIT="53;79;82"
COLOR_MODEL="62;98;89"
COLOR_CTX="80;106;86"
COLOR_LINES="90;120;97"

if [ "$current_dir" = "$HOME" ]; then
  dir_display="~"
else
  if [[ "$current_dir" == "$HOME"/* ]]; then
    rel_path="${current_dir#$HOME/}"
    depth=$(echo "$rel_path" | tr -cd '/' | wc -c | tr -d ' ')
    if [ "$depth" -gt 0 ]; then
      dir_display="…/$(basename "$current_dir")"
    else
      dir_display="$(basename "$current_dir")"
    fi
  else
    dir_display="$(basename "$current_dir")"
  fi
fi

git_branch=""
git_changes="0"
git_root=""
if git -C "$current_dir" rev-parse --git-dir > /dev/null 2>&1; then
  git_root=$(git -C "$current_dir" rev-parse --show-toplevel 2>/dev/null)
  git_branch=$(git -C "$git_root" symbolic-ref --short HEAD 2>/dev/null || git -C "$git_root" rev-parse --short HEAD 2>/dev/null)
  modified_count=$(git -C "$git_root" status --short --untracked-files=all 2>/dev/null | wc -l | tr -d ' ')
  git_changes="$modified_count"
fi

model_display=$(echo "$input" | jq -r '.model.display_name // "Unknown"')
model_id=$(echo "$input" | jq -r '.model.id // ""')

if echo "$model_display" | grep -qi "sonnet" || echo "$model_id" | grep -qi "sonnet"; then
  model_icon="$ICON_SONNET"
elif echo "$model_display" | grep -qi "haiku" || echo "$model_id" | grep -qi "haiku"; then
  model_icon="$ICON_HAIKU"
elif echo "$model_display" | grep -qi "opus" || echo "$model_id" | grep -qi "opus"; then
  model_icon="$ICON_OPUS"
else
  model_icon="$ICON_SONNET"
fi

context_data=$(echo "$input" | jq '.context_window.current_usage')
context_percentage=""

if [ "$context_data" != "null" ]; then
  input_tokens=$(echo "$context_data" | jq '.input_tokens // 0')
  cache_creation=$(echo "$context_data" | jq '.cache_creation_input_tokens // 0')
  cache_read=$(echo "$context_data" | jq '.cache_read_input_tokens // 0')
  context_size=$(echo "$input" | jq '.context_window.context_window_size // 200000')

  current_usage=$((input_tokens + cache_creation + cache_read))
  percentage=$((current_usage * 100 / context_size))
  context_percentage="$percentage"

  filled_segments=$((percentage / 10))
  if [ $filled_segments -gt 10 ]; then
    filled_segments=10
  fi
  empty_segments=$((10 - filled_segments))

  progress_bar=""
  for i in $(seq 1 $filled_segments); do
    progress_bar="${progress_bar}━"
  done

  fade_idx=0
  for i in $(seq 1 $empty_segments); do
    case $fade_idx in
      0) progress_bar="${progress_bar}\033[38;2;180;190;170m━" ;;
      1) progress_bar="${progress_bar}\033[38;2;140;155;135m━" ;;
      2) progress_bar="${progress_bar}\033[38;2;110;125;105m━" ;;
      3) progress_bar="${progress_bar}\033[38;2;85;100;82m━" ;;
      *) progress_bar="${progress_bar}\033[38;2;65;80;65m━" ;;
    esac
    fade_idx=$((fade_idx + 1))
  done
  progress_bar="${progress_bar}\033[38;2;${FG}m"
fi

lines_added=$(echo "$input" | jq -r '.cost.total_lines_added // 0')
lines_removed=$(echo "$input" | jq -r '.cost.total_lines_removed // 0')

printf "\033[38;2;${COLOR_DIR}m${ARROW_ROUND}\033[0m"
printf "\033[38;2;${FG}m\033[48;2;${COLOR_DIR}m $dir_display \033[0m"

if [ -n "$git_branch" ]; then
  printf "\033[38;2;${COLOR_DIR}m\033[48;2;${COLOR_GIT}m${ARROW_RIGHT}\033[0m"
  printf "\033[38;2;${FG}m\033[48;2;${COLOR_GIT}m $ICON_GIT $git_branch \033[0m"
  printf "\033[38;2;${COLOR_GIT}m\033[48;2;${COLOR_MODEL}m${ARROW_RIGHT}\033[0m"
else
  printf "\033[38;2;${COLOR_DIR}m\033[48;2;${COLOR_MODEL}m${ARROW_RIGHT}\033[0m"
fi

printf "\033[38;2;${FG}m\033[48;2;${COLOR_MODEL}m $model_icon $model_display \033[0m"

if [ -n "$context_percentage" ]; then
  printf "\033[38;2;${COLOR_MODEL}m\033[48;2;${COLOR_CTX}m${ARROW_RIGHT}\033[0m"
  printf "\033[38;2;${FG}m\033[48;2;${COLOR_CTX}m "
  printf "%b" "$progress_bar"
  printf " ${context_percentage}%% \033[0m"

  if [ "$lines_added" != "0" ] || [ "$lines_removed" != "0" ] || [ -n "$git_changes" ]; then
    printf "\033[38;2;${COLOR_CTX}m\033[48;2;${COLOR_LINES}m${ARROW_RIGHT}\033[0m"
    files_part=""
    [ "$git_changes" != "0" ] && files_part="$ICON_DIFF $git_changes "
    printf "\033[38;2;${FG}m\033[48;2;${COLOR_LINES}m ${files_part}$ICON_PLUS $lines_added $ICON_MINUS $lines_removed \033[0m"
    printf "\033[38;2;${COLOR_LINES}m\033[48;2;75;95;78m${ARROW_RIGHT}\033[0m"
    printf "\033[38;2;75;95;78m\033[48;2;55;70;58m${ARROW_RIGHT}\033[0m"
    printf "\033[38;2;55;70;58m\033[48;2;40;50;42m${ARROW_RIGHT}\033[0m"
    printf "\033[38;2;40;50;42m${ARROW_RIGHT}\033[0m\n"
  else
    printf "\033[38;2;${COLOR_CTX}m\033[48;2;65;85;70m${ARROW_RIGHT}\033[0m"
    printf "\033[38;2;65;85;70m\033[48;2;50;65;52m${ARROW_RIGHT}\033[0m"
    printf "\033[38;2;50;65;52m\033[48;2;35;45;37m${ARROW_RIGHT}\033[0m"
    printf "\033[38;2;35;45;37m${ARROW_RIGHT}\033[0m\n"
  fi
else
  if [ "$lines_added" != "0" ] || [ "$lines_removed" != "0" ] || [ -n "$git_changes" ]; then
    printf "\033[38;2;${COLOR_MODEL}m\033[48;2;${COLOR_LINES}m${ARROW_RIGHT}\033[0m"
    files_part=""
    [ "$git_changes" != "0" ] && files_part="$ICON_DIFF $git_changes "
    printf "\033[38;2;${FG}m\033[48;2;${COLOR_LINES}m ${files_part}$ICON_PLUS $lines_added $ICON_MINUS $lines_removed \033[0m"
    printf "\033[38;2;${COLOR_LINES}m\033[48;2;75;95;78m${ARROW_RIGHT}\033[0m"
    printf "\033[38;2;75;95;78m\033[48;2;55;70;58m${ARROW_RIGHT}\033[0m"
    printf "\033[38;2;55;70;58m\033[48;2;40;50;42m${ARROW_RIGHT}\033[0m"
    printf "\033[38;2;40;50;42m${ARROW_RIGHT}\033[0m\n"
  else
    printf "\033[38;2;${COLOR_MODEL}m\033[48;2;50;75;70m${ARROW_RIGHT}\033[0m"
    printf "\033[38;2;50;75;70m\033[48;2;40;55;50m${ARROW_RIGHT}\033[0m"
    printf "\033[38;2;40;55;50m\033[48;2;30;40;35m${ARROW_RIGHT}\033[0m"
    printf "\033[38;2;30;40;35m${ARROW_RIGHT}\033[0m\n"
  fi
fi

Step 2: Make It Executable

chmod +x ~/.claude/statusline-command.sh

Step 3: Configure Claude Code

Add this to your ~/.claude/settings.json:

{
  "statusLine": {
    "type": "command",
    "command": "bash ~/.claude/statusline-command.sh"
  }
}

If you already have settings, just add the statusLine block to your existing JSON.

Step 4: Restart Claude Code

Close and reopen Claude Code. You should see your beautiful new status line!


Customizing Colors

Using Coolors

I designed my forest gradient using Coolors. This amazing tool lets you:

  1. Generate random palettes
  2. Lock colors you like
  3. Adjust individual colors
  4. Export in any format

For Powerline segments, you want colors that:

My Forest Palette

SegmentHexRGBDescription
Directory#2f3e4647;62;70Darkest - the forest floor
Git#354f5253;79;82Deep moss
Model#3e625962;98;89Forest shade
Context#506a5680;106;86Dappled sunlight
Changes#5a786190;120;97Lightest - canopy

Creating Your Own Palette

  1. Go to Coolors
  2. Generate or create a 5-color gradient
  3. Copy the hex codes
  4. Convert to RGB using any converter (or just extract from the hex)
  5. Update the COLOR_* variables in the script

Pro tip: For a cohesive look, check out Starship’s preset gallery. The Gruvbox Rainbow preset was a major inspiration for this design.

Converting Hex to RGB

Hex #2f3e46 becomes RGB 47;62;70:

Or just use an online converter!


Customizing Icons

The Nerd Fonts Cheat Sheet

The ultimate resource for Nerd Font icons is the Nerd Fonts Cheat Sheet. Here you can:

  1. Search for icons by name
  2. Preview how they look
  3. Copy the character directly
  4. Get the Unicode code point

Converting Unicode to Bash

Here’s where it gets tricky. Bash doesn’t handle \uXXXX the same way JavaScript does. You need to convert Unicode code points to UTF-8 byte sequences.

For characters in the BMP (U+0000 to U+FFFF):

Example: U+E0B0 (Powerline arrow)

  1. The code point is E0B0 (3 bytes in UTF-8)
  2. Calculate the UTF-8 bytes:
    • First byte: 0xE0 | ((0xE0B0 >> 12) & 0x0F) = 0xEE
    • Second byte: 0x80 | ((0xE0B0 >> 6) & 0x3F) = 0x82
    • Third byte: 0x80 | (0xE0B0 & 0x3F) = 0xB0
  3. Use in bash: printf '\xee\x82\xb0'

For characters outside BMP (U+10000+):

Example: U+F0993 (from a surrogate pair like \uDB83\uDD93)

These need 4 UTF-8 bytes. Use an online converter or:

# Python one-liner
python3 -c "print(''.join(f'\\x{b:02x}' for b in '󰦓'.encode('utf-8')))"
# Output: \xf3\xb0\xa6\x93

Icons Used in This Status Line

UnicodeUTF-8 BytesPurpose
U+E0B0\xee\x82\xb0Powerline arrow right
U+E0B6\xee\x82\xb6Powerline rounded left
U+F418\xef\x90\x98Git branch
U+F055\xef\x81\x95Circle plus (lines added)
U+F056\xef\x81\x96Circle minus (lines removed)
U+F0993\xf3\xb0\xa6\x93Files changed
U+EE34\xee\xb8\xb4Sonnet model
U+E7D5\xee\x9f\x95Haiku model
U+ED62\xee\xb5\xa2Opus model (king)

Finding Your Own Icons

  1. Visit nerdfonts.com/cheat-sheet

  2. Search for what you need (e.g., “git”, “folder”, “clock”)

  3. Click the icon to copy it

  4. Get the codepoint (e.g., f0993)

  5. Convert to UTF-8 using Python:

    python3 -c "print(''.join(f'\\\\x{b:02x}' for b in chr(0xf0993).encode('utf-8')))"
  6. Use the output in your script


Advanced Customizations

Adding More Segments

Want to add a clock? RAM usage? Custom data? Here’s the pattern:

# Define the color
COLOR_NEW="100;120;100"

# Add the segment
printf "\033[38;2;${PREV_COLOR}m\033[48;2;${COLOR_NEW}m${ARROW_RIGHT}\033[0m"
printf "\033[38;2;${FG}m\033[48;2;${COLOR_NEW}m YOUR_CONTENT \033[0m"

Conditional Segments

The script already shows/hides the git segment based on whether you’re in a repo. You can do the same for any data:

if [ some_condition ]; then
  # Show segment
else
  # Skip segment or show alternative
fi

Different Themes

Create multiple scripts and switch between them:

~/.claude/statusline-forest.sh
~/.claude/statusline-ocean.sh
~/.claude/statusline-fire.sh

Then update settings.json to use the one you want.


Troubleshooting

Icons Show as Boxes/Question Marks

  1. Make sure you have a Nerd Font installed
  2. Configure your terminal to use that font
  3. Verify with: echo -e "\ue0b0 \ue0b2 \ue0b6"

Colors Look Wrong

  1. Ensure your terminal supports true colors
  2. Test with: printf "\033[38;2;255;100;0mOrange text\033[0m\n"
  3. If that looks orange, true colors work!

Script Errors

  1. Make sure jq is installed: brew install jq or apt install jq
  2. Check script permissions: chmod +x ~/.claude/statusline-command.sh
  3. Test the script manually: echo '{}' | ~/.claude/statusline-command.sh

Git Info Not Showing

  1. Make sure you’re in a git repository
  2. The script uses git -C to work from any subdirectory
  3. Check if git is available: which git

Resources


Have questions or want to share your customization? Feel free to reach out!

Comments


Edit page
Share this post on:

Previous Post
How to Create Stunning Web Designs Using Claude Code and Modern AI Tools