This is a living document. Everything below is copy-paste ready and tested on a fresh Arch install. No fluff — only what I actually run.
// contents
1. Package installation
One pacman invocation for everything needed. No AUR manager
required initially — add paru for nerd fonts afterward.
pacman -S --needed \
base-devel git xorg-server xorg-xinit xorg-xsetroot xorg-xrandr \
xorg-xrdb libx11 libxft libxinerama freetype2 fontconfig \
zsh zsh-completions fzf ripgrep fd eza tmux \
dunst picom xclip xdotool acpi brightnessctl \
ttf-jetbrains-mono noto-fonts noto-fonts-emoji \
neovim nodejs npm ntfs-3g ranger rust gcc gdb make cmake \
wget curl jq htop lsof strace
# AUR — install paru first, then:
paru -S --needed nerd-fonts-jetbrains-mono
2. Build & install
Clone from suckless directly. Never use distro packages for dwm or st.
mkdir -p ~/.builds && cd ~/.builds
# dwm
git clone https://git.suckless.org/dwm
cd dwm
# apply patches → edit config.h → build
sudo make clean install && cd ..
# st
git clone https://git.suckless.org/st
cd st
sudo make clean install && cd ..
# dmenu (suckless version for consistency)
git clone https://git.suckless.org/dmenu
cd dmenu && sudo make clean install
Directory layout after cloning:
~
├── .builds/
│ ├── dwm/
│ └── st/
├── .config/
│ ├── zsh/
│ └── nvim/
└── .local/bin/ # custom scripts
3. Patches
dwm
| patch | why |
|---|---|
| autostart | run ~/.dwm/autostart.sh on launch |
| backlight | Controll screen brightness using brightnessctl |
| pertag | independent layout per tag |
| vanitygaps | inner/outer gaps for visual breathing room |
| fullscreen | true fullscreen toggle (no bar) |
| attachbottom | new clients below master, not above |
| statusbar | clickable status bar segments |
st
| patch | why |
|---|---|
| alpha | background transparency |
| scrollback | scroll without tmux dependency |
| font2 | fallback font for emoji/symbols |
| boxdraw | clean box-drawing character rendering |
| undercurl | wavy underlines for LSP diagnostics |
Apply patches before editing config.h:
cd ~/.builds/dwm
curl -O https://dwm.suckless.org/patches/autostart/dwm-autostart-20210120-cb3f58a.diff
patch -p1 < dwm-autostart-20210120-cb3f58a.diff
# resolve any conflicts in config.def.h manually
4. dwm config.h
Use config from github which will be the most uptodateGruvbox dark palette throughout. Super as modifier. ThinkPad brightness and volume keys wired to hardware controls.
/* appearance */
static const unsigned int borderpx = 2;
static const unsigned int gappx = 6;
static const unsigned int snap = 32;
static const int showbar = 1;
static const int topbar = 1;
static const char *fonts[] = {
"JetBrainsMono Nerd Font:size=10",
"Noto Color Emoji:size=10"
};
/* gruvbox dark */
static const char col_bg[] = "#1d2021";
static const char col_bg1[] = "#282828";
static const char col_fg[] = "#ebdbb2";
static const char col_accent[] = "#d79921";
static const char col_blue[] = "#458588";
static const char *colors[][3] = {
[SchemeNorm] = { col_fg, col_bg, col_bg1 },
[SchemeSel] = { col_bg, col_accent, col_blue },
};
/* tags */
static const char *tags[] = {
"1","2","3","4","5","6","7","8","9"
};
/* layout — tile default, monocle, float */
static const float mfact = 0.55;
static const int nmaster = 1;
static const int resizehints = 0; /* ignore size hints */
See the full customisation reference for all available fields.
5. st config.h
/* font */
static char *font = "JetBrainsMono Nerd Font:pixelsize=14:antialias=true";
static char *font2[] = { "Noto Color Emoji:pixelsize=14" };
static int borderpx = 12;
/* transparency (alpha patch) */
static const float alpha = 0.92;
/* gruvbox colours — indices 0-15 */
static const char *colorname[] = {
"#1d2021", "#cc241d", "#98971a", "#d79921",
"#458588", "#b16286", "#689d6a", "#a89984",
"#928374", "#fb4934", "#b8bb26", "#fabd2f",
"#83a598", "#d3869b", "#8ec07c", "#ebdbb2",
[255] = 0,
"#ebdbb2", /* defaultfg */
"#1d2021", /* defaultbg */
"#d79921", /* cursor */
};
static char *shell = "/bin/zsh";
static int scroll_history = 4000;
6. Shell setup
No plugin manager. Two plugins cloned manually.
Configuration split across ~/.zshenv and ~/.config/zsh/.zshrc.
chsh -s /bin/zsh
mkdir -p ~/.config/zsh/plugins
~/.zshenv
export PATH="$HOME/.local/bin:$PATH"
export EDITOR=nvim
export VISUAL=nvim
export PAGER=less
export MANPAGER="nvim +Man!"
export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git'
export FZF_DEFAULT_OPTS='--height 40% --layout=reverse --border'
~/.zshrc (essentials)
# history
HISTSIZE=50000; SAVEHIST=50000; HISTFILE="~/.zsh_history"
setopt HIST_IGNORE_DUPS HIST_IGNORE_SPACE SHARE_HISTORY
# completion
autoload -Uz compinit && compinit
# use -d to specify .zcompdump file
zstyle ':completion:*' menu select
# vi mode
bindkey -v
export KEYTIMEOUT=1
bindkey '^R' history-incremental-search-backward
# new github conf uses starship as prmpt
# prompt (no external deps)
autoload -Uz vcs_info
precmd() { vcs_info }
zstyle ':vcs_info:git:*' formats ' %b'
setopt PROMPT_SUBST
PROMPT='%F{yellow}%~%F{blue}${vcs_info_msg_0_}%f %(?.%F{green}.%F{red})❯%f '
# plugins
source "/usr/share/zsh/plugins/fast-syntax-highlighting/fast-syntax-highlighting.plugin.zsh"
source "/usr/share/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh"
ZSH_AUTOSUGGEST_STRATEGY=(history completion)
# fzf
source /usr/share/fzf/key-bindings.zsh
source /usr/share/fzf/completion.zsh
source "/.aliases.zsh"
Clone plugins
download from repo instead of cloneinggit clone https://github.com/zdharma-continuum/fast-syntax-highlighting \
~/.config/zsh/plugins/fast-syntax-highlighting
git clone https://github.com/zsh-users/zsh-autosuggestions \
~/.config/zsh/plugins/zsh-autosuggestions
Key aliases
# replacements
alias ls='eza --group-directories-first'
alias ll='eza -la --git'
alias cat='bat --style=plain'
alias grep='rg'
alias find='fd'
alias vim='nvim'
# git shortcuts
alias gs='git status -sb'
alias gl='git log --oneline --graph --decorate'
alias gd='git diff'
# config quick-edit
alias edwm='nvim ~/.builds/dwm/config.h'
alias est='nvim ~/.builds/st/config.h'
alias ec='nvim ~/.config/zsh/.zshrc'
7. Essential tools
lf (file manager)
paru -S lf
mkdir -p ~/.config/lf
# ~/.config/lf/lfrc
set icons true
set hidden true
set drawbox true
cmd open ${{
case $(file --mime-type "$f" -bL) in
text/*|application/json) $EDITOR "$f" ;;
image/*) sxiv "$f" ;;
application/pdf) zathura "$f" ;;
*) xdg-open "$f" ;;
esac
}}
map <enter> open
map D delete
map R rename
map . set hidden!
8. Keybindings
| binding | action |
|---|---|
| Super+Shift+Return | spawn terminal (st) |
| Super+P | dmenu launcher |
| Super+Shift+B | firefox |
| Super+Shift+C | kill client |
| Super+J / K | focus next / prev |
| Super+H / L | resize master |
| Super+Return | zoom (swap to master) |
| Super+Tab | last tag |
| Super+T | tile layout |
| Super+M | monocle layout |
| Super+F | float layout |
| Super+1–9 | switch tag |
| Super+Shift+1–9 | move client to tag |
| Super+B | toggle bar |
| Super+Shift+L | lock screen (slock) |
| Super+Shift+Q | quit dwm |
st
| binding | action |
|---|---|
| Ctrl+Shift+C / V | copy / paste |
| Ctrl+Shift+PgUp / Dn | zoom in / out |
| Shift+PgUp / PgDn | scroll history |
9. Fonts & theme
Gruvbox dark hard — consistently applied across dwm, st, nvim, and dmenu. One palette, zero cognitive switching cost.
| role | hex |
|---|---|
| bg | #1d2021 |
| bg1 | #282828 |
| fg | #ebdbb2 |
| accent (yellow) | #d79921 |
| blue | #458588 |
| red | #cc241d |
| green | #98971a |
Font: JetBrainsMono Nerd Font — 10pt UI, 14px terminal.
# ~/.Xresources
Xft.dpi: 96
Xft.antialias: true
Xft.hinting: true
Xft.hintstyle: hintslight
Xft.rgba: rgb
Xcursor.size: 16
Xcursor.theme: Adwaita
10. Startup (.xinitrc)
#!/bin/sh
xrdb -merge ~/.Xresources
# keyboard
xset r rate 200 40
setxkbmap -layout us -option caps:escape # Caps → Escape
# display (adjust to your output name)
xrandr --output eDP-1 --mode 1920x1080 --dpi 96
# natural scrolling
xinput set-prop "SynPS/2 Synaptics TouchPad" \
"libinput Natural Scrolling Enabled" 1 2>/dev/null
# compositor
picom --daemon --backend glx --vsync \
--inactive-opacity 0.9 --active-opacity 1.0 \
--shadow --shadow-radius 8 &
dunst &
~/.local/bin/dwmstatus &
exec dwm
Auto-start X on login — append to ~/.config/zsh/.zprofile:
if [ -z "$DISPLAY" ] && [ "$XDG_VTNR" = "1" ]; then
exec startx
fi
11. Status bar
Pure bash. Sets the root window name every 5 seconds. dwm reads it for the bar. No external deps — battery, volume, network, clock.
#!/bin/bash
# ~/.local/bin/dwmstatus
interval=5
get_bat() {
bat=$(cat /sys/class/power_supply/BAT0/capacity)
status=$(cat /sys/class/power_supply/BAT0/status)
case "$status" in
Charging) echo " ${bat}%" ;;
Discharging) echo " ${bat}%" ;;
*) echo " FULL" ;;
esac
}
get_vol() {
vol=$(pactl get-sink-volume @DEFAULT_SINK@ | grep -oP '\d+%' | head -1)
mute=$(pactl get-sink-mute @DEFAULT_SINK@ | grep -c 'yes')
[ "$mute" -gt 0 ] && echo " mute" || echo " $vol"
}
get_date() { date +" %a %d %b %H:%M"; }
while true; do
xsetroot -name "$(get_vol) $(get_bat) $(get_date)"
sleep "$interval"
done
chmod +x ~/.local/bin/dwmstatus
That's the full setup. From pacstrap to a working dwm session,
this is everything — no steps omitted, nothing assumed.
Rebuild time from scratch: under 30 minutes.