Greybeards rise

How a vim colorscheme changed my desktop

2026-05-1914 minWesley Schwenglericingthemingvimicebergi3rofidmenuxscreensaverkittygit

TL;DR

Twenty years of desktop environments — WindowMaker, KDE3, fvwm, i3 — and the lesson is always the same: consistency beats aesthetics. This is how I took one vim colorscheme (iceberg) and spread it across my entire desktop: terminal, prompt, window manager, launcher, screensaver, and even git. One palette aligns the whole UI. The only constraint you lay on yourself: timebox it. Because theming takes a fuck-ton of time.

Iceberg ahead

I’m no ricer by any means. But I can tell you my process. Nothing is planned, but everything I did, I did with a masterplan.

My vim color theme for years has been monokai, but I felt that I needed something else. I went looking and came across iceberg made by cocopon . What I found interesting was that cocopon gave a talk about themes on vimconf 2017 on how to create your own color scheme .
I read it and something clicked. The take home concept is that you pick base colors and instead of picking different colors you look for a “family” of colors on the lighter and darker side of it to create your theme. You pick a shade of blue and you then look at both the darker and lighter spectrum of that blue to create a consistent color theme. And you do this for all of the colors you pick.

I have a forked version of cocopons’ iceberg theme because of a bug and there is no sign that they are going to merge the fix I send them . I also stripped all the light theme support and the neovim from it, because if you fork it, why not strip it to the bare essentials of what you actually need and use :)

Color coding

When I read how cocopon approached theming I decided to make a plunge: unify the whole desktop experience to one single theme. Kitty (my terminal) was next up: how would this have to look. And while I started with kitty I quickly found out that someone else did the hard work for me . So, I used their implementation. For my zsh prompt I quickly looked at alternatives for blue, green and red, and these became: #3E445E , #B4BE82 , and #85512c for when I’m root.

Local:
Kitty with theme

Over ssh (without sendenv/acceptenv):
SSH without colorterm

And xterm
xterm without colorterm

And urxvt
urxvt without colorterm

iii

Changing my i3 was up next. i3 doesn’t have a lot to tweak. It’s a tiling window manager and it’s pretty bare-bones. But it also has a status bar, so we could do some tweaking so my whole screen looks coherent.

The i3 bar uses a dark background (#272C42 ) with light text (#F8F8F2 ). The focused workspace - the one you’re currently on - gets highlighted with that iceberg green (#B4BE82 ) so it stands out. Inactive workspaces fade into the muted gray (#6B7089 ) so they’re visible but don’t compete for attention.

It’s all about visual hierarchy using the same color family. Active things are green, inactive things are gray, backgrounds are dark. Your eye immediately knows what’s important without thinking about it.

Applying this to windows follows the same logic. For those unfamiliar with i3: i3 organizes windows into containers, which can be split horizontally or vertically, stacked, or tabbed - like browser tabs but for any application. This container system means i3 needs to style multiple states:

  • Focused: The window you’re typing in gets green text #B4BE82 and a purple-gray border #3E445E
  • Focused inactive: A window that’s “selected” in its container (like the active tab) but you’re working elsewhere - gets blue-ish text #84A0C6
  • Unfocused: Everything else fades to gray #6B7089
  • Urgent: Notifications get a red indicator #FF5555
  • Background: Everything sits on dark #161821

Active
active i3 window

Inactive
inactive i3 window

The same color hierarchy: active things are bright and colorful, inactive things fade to gray.

i3 config section
config
# Start i3bar to display a workspace bar (plus the system information i2status
# finds out, if available)
bar {
  position top
  # top right bottom left
  #padding 5px 5px 5px 5px
  separator_symbol "𐄁"

  status_command i3status

  font pango:CommitMono Regular 8
  colors {
    # See rofi/waterkip.rasi for color codes
    # visual: #272C42
    # tabLine: #3E445E
    # modeMsg: #6B7089

    background #272C42
    statusline #F8F8F2
    separator  #6B7089

    # whati            border  bg      text
    focused_workspace  #161821 #B4BE82 #000000
    active_workspace   #161821 #161821 #B4BE82
    inactive_workspace #161821 #161821 #6B7089
    urgent_workspace   #161821 #161821 #6B7089
    binding_mode       #161821 #161821 #6B7089
  }
}

# This defines colors for "clients", aka the actual windows
# class                 border  backgr. text    indicator child_border
client.focused          #3E445E #3E445E #B4BE82 #6272A4  #3E445E
client.focused_inactive #161821 #161821 #84A0C6 #44475A  #161821
client.unfocused        #161821 #161821 #6B7089 #282A36  #161821
client.urgent           #161821 #161821 #6B7089 #FF5555  #161821
client.placeholder      #161821 #161821 #6B7089 #282A36  #161821
client.background       #161821

dmenu.suckless and rofi

I3 uses dmenu, which is provided by suckless (from dwm). I replaced dmenu with rofi. It behaves like dmenu but is fully themeable. It provides a dropin replacement for dmenu, so the transition from dmenu to rofi is painless.

With some Debian-foo called update-alternatives I replaced suckless' implementation of dmenu with rofi. I installed rofi and renamed dmenu from suckless to dmenu.suckless including the manpage. And rofi now is the binary called dmenu.

Ansible configuration for Debian's update alternative configuration

And because I use ansible, here you can see the playbook contents:

yml
- name: Divert dmenu from suckless-tools
  become: true
  community.general.dpkg_divert:
    path: /usr/bin/dmenu
    divert: /usr/bin/dmenu.suckless
    rename: yes
    state: present

- name: Divert dmenu manpage from suckless-tools
  become: true
  community.general.dpkg_divert:
    path: /usr/share/man/man1/dmenu.1.gz
    divert: /usr/share/man/man1/dmenu.suckless.1.gz
    rename: yes
    state: present

I’m doing this because now I can create an update-alternatives group for dmenu:

yml
- name: update-alternative for dmenu from suckless-tools
  become: true
  community.general.alternatives:
    link: /usr/bin/dmenu
    name: dmenu
    path: /usr/bin/dmenu.suckless
    priority: 20
    state: auto
    subcommands:
      - link: /usr/share/man/man1/dmenu.1.gz
        name: dmenu.1.gz
        path: /usr/share/man/man1/dmenu.suckless.1.gz

- name: update-alternative for dmenu from rofi
  become: true
  community.general.alternatives:
    link: /usr/bin/dmenu
    name: dmenu
    path: /usr/bin/rofi
    priority: 50
    state: auto
    subcommands:
      - link: /usr/share/man/man1/dmenu.1.gz
        name: dmenu.1.gz
        path: /usr/share/man/man5/rofi-dmenu.5.gz

Rofi by default looks.. It has a paper beige like color and its.. something, let me put it that way. But it allows for CSS like styling which is awesomesauce.

Rofi
Rofi’s alt-tab view

waterkip.rofi
css
* {
    spacing: 2;

    normalBg:   #161821;
    normalFg:   #C6C8D1;

    comment:    #6B7089;
    function:   #84A0C6;
    identifier: #89B8C2;
    include:    #84A0C6;
    modeMsg:    #6B7089;
    nonText:    #A093C7;
    preproc:    #B4BE82;
    visual:     #272C42;
    tabLine:    #3E445E;
    title:      #E2A478;
    todoBg:     #45493E;
    todoFg:     var(preproc);

    separatorcolor: var(modeMsg);

    background: var(normalBg);
    foreground: var(normalFg);

    background-color: var(background);
    foreground-color: var(foreground);

    urgent-background: var(background);
    urgent-foreground: var(foreground);
    alternate-urgent-background: var(urgent-background);
    alternate-urgent-foreground: var(urgent-foreground);

    normal-background: var(background);
    normal-foreground: var(foreground);
    alternate-normal-background: var(normal-background);
    alternate-normal-foreground: var(normal-foreground);

    // Current selection
    selected-normal-background:  var(visual);
    selected-normal-foreground:  var(function);

    active-background: var(visual);
    active-foreground: var(nonText);
    alternate-active-background: var(active-background);
    alternate-active-foreground: var(active-foreground);

    selected-active-background: var(tabLine);
    selected-active-foreground: var(preproc);

    selected-urgent-background:  var(background);
    selected-urgent-foreground:  var(foreground);
}

window {

  anchor: center;
  width:  30%;
  height: 50%;
  margin: 2px;
  padding: 2px;
  spacing: 0px;

  background-color: @background;
  border-color: @foreground;

  // Fake transparancy by creating a screenshot so our borders work
  // breaks when using this on top of a video, but /care, we don't need a
  // compositor for this, so that's a win.
  transparency: "screenshot";
  border-radius: 10px 10px 10px 10px;
  border: 4px;

  scrollbar: false;
}

mainbox {
    border:  0;
    padding: 0;
}

message {
    border:       1px dash 0px 0px ;
    border-color: @separatorcolor;
    padding:      1px ;
}

textbox {
  text-color: @foreground;
}

listview {
    fixed-height: 0;
    border:       2px dash 0px 0px ;
    border-color: @separatorcolor;
    spacing:      2px ;
    padding:      2px 0px 0px ;
}
element {
    border:  0;
    padding: 1px ;
}
element-text {
    background-color: inherit;
    text-color:       inherit;
}

element normal.normal {
    background-color: @normal-background;
    text-color:       @normal-foreground;
}
element.normal.urgent {
    background-color: @urgent-background;
    text-color:       @urgent-foreground;
}
element.normal.active {
    background-color: @active-background;
    text-color:       @active-foreground;
}
element.selected.normal {
    background-color: @selected-normal-background;
    text-color:       @selected-normal-foreground;
}
element.selected.urgent {
    background-color: @selected-urgent-background;
    text-color:       @selected-urgent-foreground;
}
element.selected.active {
    background-color: @selected-active-background;
    text-color:       @selected-active-foreground;
}
element.alternate.normal {
    background-color: @alternate-normal-background;
    text-color:       @alternate-normal-foreground;
}
element.alternate.urgent {
    background-color: @alternate-urgent-background;
    text-color:       @alternate-urgent-foreground;
}
element.alternate.active {
    background-color: @alternate-active-background;
    text-color:       @alternate-active-foreground;
}

scrollbar {
    border:       0;
    handle-width: 8px ;
    handle-color: @visual;
}

mode-switcher {
    border:       2px dash 0px 0px ;
    border-color: @separatorcolor;
}

button.selected {
    background-color: @selected-normal-background;
    text-color:       @selected-normal-foreground;
}

inputbar {
    spacing:    0;
    text-color: @normal-foreground;
    padding:    1px ;
    children:   [ prompt,textbox-prompt-colon,entry,case-indicator ];
}

case-indicator {
    spacing:    0;
    text-color: @normal-foreground;
}

entry {
    spacing:    0;
    text-color: @normal-foreground;
}

prompt {
    spacing:    0;
    text-color: @normal-foreground;
}

textbox-prompt-colon {
    expand:     false;
    str:        ":";
    margin:     0px 0.3em 0em 0em ;
    text-color: @normal-foreground;
}

Non-theming things in i3

There is one really big thing I have that has nothing to do with theming and that is what I call dynamic workspaces, or context aware workspaces. I’ve been told it is similar to KDE Activities. You can read all about it in the blog post Workspace on Demand in i3wm . But the short intro is: I have workspace groups (or contexts, or activities) and these groups can have predefined layouts and when I switch to a workspace it automatically starts up applications based on the layout. I build it on top of i3’s IPC events.

The fun thing here is that when I start my terminal (kitty) in a context, it automatically starts in a specific directory. It makes use of the X11 root window properties set by my workspace daemon which can be extracted via xprop. A pretty clever hack iyam. The same hack powers a small unit in my i3 bar that tells me which context I’m currently on (including the workspace).

kitty
zsh
#!/usr/bin/env zsh

source $HOME/.zsh/autoloadrc
source $HOME/.zsh/nameddirrc
source $HOME/.env.local

group=$(i3-wod-current-group)
ws=$(i3-wod-current-workspace)

if [ $ws = 'area51' ]
then
  exec /usr/bin/kitty
  return
fi

case $group in
  hiwinds) target=~ahw;;
  opndev) target=~opndev;;
  bta) target=~bta;;
esac

exec /usr/bin/kitty $target

In a similar fashion. When using newsboat I also look for a browser in the current workspace. If it exists I activate it and than send the URL it so it opens. If I can’t find a browser it starts logic to find the default browser and opens the browser with the URL.

Newsboat
zsh
#!/usr/bin/env zsh

[ -z "$1" ] && echo "Provide a URI" >&2 && exit 1

fpath=(~/.zsh/autoload/i3 $fpath) && autoload i3-wod-current-workspace

ws=$(i3-wod-current-workspace)

#echo "Checking workspace $ws"

browser_id=$(i3-msg -t get_tree | jq ".. | objects | select(.name? == \"$ws\") | .. | objects | select(.window_properties?.window_role? == \"browser\") | .window" | head -1)

if [ -z $browser_id ]
then
  default_browser=$(xdg-settings get default-web-browser)
  p=$(find ~/.local/share/applications /usr/share/applications -name $default_browser)

  autoload regexp-replace
  bin=$(grep Exec $p)
  regexp-replace bin ' %u' ""
  regexp-replace bin 'Exec=' ""
  #echo "Opening new browser $bin in $ws"
  i3-msg exec "$bin --new-window $*"
  return
fi

class=$(xprop -id $browser_id WM_CLASS | awk -F, '{print $NF}' | tr -d '"' | tr -d ' ')
#echo "Opening new browser $class in $ws on $browser_id"
xdotool windowactivate $browser_id
exec $class "$*"

Old X11 applications get loving too

To create consistency across the board I also decided to theme my screensaver, which is xscreensaver. It must be configured via your .Xresources. Another application that must be configured via this file is ssh-askpass. The only problem is, that I was unable to set a nice TrueType font so I had to go with Helvetica bold, which is an X11 bitmap font. The colors are there, the font is still… up for improvement: if you know a better font or know how to change this to TrueType: Hit me up!

Text uses #84A0C6 and the background is a dark blue #161821 . And I added some green to break the (dark) blue a bit #B4BE82 , and for ssh-askpass I added the orange-y #E2A478 as a text color for the cancel button to signal “scary” action.

In reality ssh-askpass is one of the latest themed things in my universe. I don’t see it that often, so it lagged behind a little.

.Xresources
xdefaults
! The defaults are True
!*passwd.uname: False
!*passwd.asterisks: False
xscreensaver.splash: false

! Set to nothing makes user switching not possible
*.newLoginCommand:

*default.Dialog.background: #161821
*default.Dialog.foreground: #84A0C6

*default.Dialog.text.background: #242940
*default.Dialog.text.foreground: #84A0C6

*default.Dialog.button.background: #242940
*default.Dialog.button.foreground: #84A0C6

*default.Dialog.borderColor: #D2D4DE
*default.Dialog.borderWidth: 0

*default.Dialog.thermometer.background: #242940
*default.Dialog.thermometer.foreground: #B4BE82
*default.Dialog.thermometer.width: 10

! Could go for 0 too if honest
*default.Dialog.shadowWidth: 2

*default.Dialog.logo.background: #242940
*default.Dialog.error.foreground: #E2A478

*default.Dialog.topShadowColor: #3E445E
*default.Dialog.bottomShadowColor: #3E445E

! TODO: Figure out how to change the logo
! The logo is hardcoded or perhaps..
! /usr/share/pixmaps/xscreensaver.png
! /usr/share/pixmaps/xscreensaver.svg
!*default.Dialog.logo.width: 210
!*default.Dialog.logo.height: 210
*default.Dialog.internalPadding: 30

*dateFormat: %Y-%m-%d %H:%M
! Upstream thinks this is cool, but it isnt
*grabDesktopImages: False

!*Dialog.headingFont:	sans-serif bold 16
!*Dialog.errorFont:	sans-serif bold 14
!*Dialog.labelFont:	sans-serif bold 14
!*Dialog.unameFont:	sans-serif 12
!*Dialog.buttonFont:	sans-serif bold 14
!*Dialog.dateFont:	sans-serif 9
!*Dialog.bodyFont: Inter Display 20


SshAskpass*background: #161821
SshAskpass*foreground: #84A0C6

SshAskpass.Dialog.background: #161821
SshAskpass.Dialog.foreground: #84A0C6

SshAskpass*Button.background: #242940
SshAskpass*Button.foreground: #84A0C6

SshAskpass*cancel.background: #242940
SshAskpass*cancel.foreground: #E2A478

SshAskpass*cancelButton.background: #242940
SshAskpass*cancelButton.foreground: #E2A478

SshAskpass*Indicator.foreground: #B4BE82
SshAskpass*Indicator.background: #242940

SshAskpass*borderColor: #D2D4DE
SshAskpass*borderWidth: 0
SshAskpass*shadowWidth: 2
SshAskpass*topShadowColor: #3E445E
SshAskpass*bottomShadowColor: #3E445E

SshAskpass*font: -*-helvetica-bold-r-*-*-21-*-*-*-*-*-*-*

Other applications

One of the things I also themed was git. And you might be surprised how well git is themeable. You can colorize diff, branch, status, and decorate. You are seemingly a bit constrained in which colors you can pick, but with some trial and error I managed to get most things in line with my theme. You have some modifiers to make colors popup (bold) or dim (dim). This allows you to play a bit with how you present things.

Seemingly? You can go nuts if you use hex colorcodes, which git also supports, the limitation is mostly your terminal emulator. This way you can theme git in exactly the same style as your theme. There is a fun caveat, or “caveat” (emphasis mine), it’s something you can use and abuse. In kitty for example you can tell the terminal what your red must look like. So for example if one is color blind, you can take these optimized colors for blue, red, yellow, and green. I took this example from a CSS system I’m building, hence the info/success/warn/error notations:

  • info (blue): #2f6fed
  • success (green): #0f766e
  • warn (yellow): #d97706
  • error (red): #b91c1c

So we have these colors defined in kitty to tell it, use these values as these colors. Now git will happily show you #d97706 as yellow. So your terminal configuration dictates your gitconfig. Which means, git can lean on that configuration and a change in kitty, changes the way git looks! Free inheritance!

.gitconfig color sections
ini
[color]
  # Possible colors are:
  # black, red, green, yellow, blue, magenta, cyan, white and default.
  # This depends on your terminal tho.
  # Also supported are ansi 256 colors
  # And 24bit aka hex
  ui = auto
  advice = never

[color "branch"]
  current = green
  local   = yellow
  remote  = yellow reverse
  # Or hex
  current  = "#89B8C2"
  local  = "#E2A478"
  remote  = "#E2A478" reverse

[color "diff"]
  meta   = yellow bold
  frag   = magenta bold
  old    = yellow dim
  new    = green dim
  commit = green

[color "status"]
  added     = green
  changed   = yellow dim
  untracked = white dim
  branch    = blue
  remoteBranch = yellow dim
  localBranch = yellow dim

[color "decorate"]
  # branch, remoteBranch, tag, stash or HEAD for local branches,
  # remote-tracking branches, tags, stash and HEAD,
  # respectively and grafted for grafted commits.
  branch       = blue
  remoteBranch = yellow dim
  tag          = magenta
  stash        = blue
  HEAD         = blue

I also configured tig to be sort of compliant with my theme. But I don’t use it that much and it has also limited color support from the last time I tried to theme it. I do see they read colors from git’s config, ymmv.

.tigrc color
config
# This is a nice view with the branching readable and inspectable
set main-view = \
    date author:abbreviated,width:10 \
    id:1,width:0 \
    commit-title:graph:v2,overflow:1

set tree-view = \
    line-number:no mode \
    id:yes,width:6 \
    date:default \
    file-size:units \
    file-name:always,width:0

# https://jonas.github.io/tig/doc/tigrc.5.html
# Color command > UI colors
#       area                fg    bg      attriutes
color   cursor              252   237
color   title-focus         237   magenta
color   title-blur          237   magenta reverse
color   file                237   252

# files in the diff stats
color   diff-stat           252   default
color   "Signed-off-by:"    white   default
color   "Signed-off-by"     white   default

Iteration is key

Why do I say iteration? Well, because I look at tweaking your environment not as a single day exercise. That has two reasons:

  1. Theming is a huge time sink. You need to timebox your efforts.
  2. Theming is a huge time sink. You really need to timebox your efforts.

And for myself, things that are still open? I have some GTK themes that look like iceberg, but I do want to end up at something consistent so I have Firefox, Thunderbird and Chrome running the same theme. The biggest thing is going to be to support a light theme for some work related mail and browsers. For which I need to have a look again at the light theme from cocopon which I stripped from my own fork (I can feel the irony while typing this).

But honestly, I really want to change my grub and my lightdm to be in the same style. Much more nerdy to change than GTK I think. When I do: expect an update!