Back to blog

My Journey from i3 to Hyprland

Updated: 8 min read by Titus Soporan

System specifications running on Hyprland

After years of rock-solid i3 on X11 (and previously AwesomeWM), I decided to tackle moving from i3 to Hyprland. My requirements were pretty basic: keep my exact workflow intact, maintain my vi-style navigation muscle memory, and get proper fractional scaling for my 4K display.

Why Switch Now?#

A few things pushed me toward making the jump:

  • 4K fractional scaling - X11’s fractional scaling has always been janky, apps looked blurry or tiny
  • Picom compositing issues - I was having weird glitches with picom on i3 and wanted to test how Wayland handled compositing
  • Future-proofing - Wayland is the future, not sure if X11 development is still active, figured I should bite the bullet

My Non-Negotiables#

I’ve spent years using my i3 setup and any migration had to preserve:

  1. My vi-style navigation - j/k/l/; for movement because I’m fast with these keys
  2. Password management workflow - rofi-pass integration I rely on daily + with pass
  3. Clipboard behavior - primary and clipboard selections need to stay in sync, (middle mouse + ctrl+shift+v)

Configuration Translation: i3 → Hyprland#

Preserving Navigation#

I prefer a vi-style navigation for my WM and CLI. Instead of hjkl, I use jkl; (shifted one key right) which lets me keep my right hand in perfect typing position. This muscle memory is sacred.

# My custom movement mapping
set $left j
set $down k
set $up l
set $right semicolon
 
# i3 bindings I've been using for years
bindsym $mod+$left focus left
bindsym $mod+$down focus down
bindsym $mod+$up focus up
bindsym $mod+$right focus right
 
# Hyprland translation (different syntax, same muscle memory)
bind = $mainMod, j, movefocus, l
bind = $mainMod, k, movefocus, d
bind = $mainMod, l, movefocus, u
bind = $mainMod, semicolon, movefocus, r

The important thing here isn’t the specific keys - it’s that I can navigate between windows without thinking. Window management shouldn’t interrupt flow state.

Back-and-Forth Workspace Switching#

One behavior I heavily relied on in i3, and carried over from Awesome, was mod+Esc to toggle between the current and previous workspace. Turns out Hyprland has this built-in, much cleaner than my initial script approach:

# Enable workspace cycling
misc {
    allow_workspace_cycles = true
}
 
# Bind to previous workspace
bind = $mainMod, Escape, workspace, previous

This preserves the exact behavior I had in i3 - press mod+Esc to quickly toggle between two workspaces.

Terminal Switch: Wezterm → Ghostty#

I’ve been using wezterm for years, but decided to trial ghostty during this migration. The performance claims and simplified config are appealing, plus I’ve been using Zellij-based dev environment more lately.

# Updated terminal binding
bind = $mainMod, Return, exec, ghostty

My thoughts on this switch are still pending - probably material for a future article once I’ve used it longer.

Layout Management: Groups vs Containers#

i3’s container model with stacking, tabbed, and split layouts needed to be recreated using Hyprland’s group system:

# i3 layout switching
bindsym $mod+s layout stacking
bindsym $mod+w layout tabbed
bindsym $mod+e layout toggle split
 
# Hyprland groups (closest equivalent)
bind = $mainMod, s, togglegroup
bind = $mainMod, w, changegroupactive
bind = $mainMod, e, layoutmsg, togglesplit

Component Replacements#

Status Bar: i3bar + i3status-rs → Waybar#

My i3 setup used i3status-rs with a custom TOML configuration. Waybar required a JSON config and CSS styling:

// ~/.config/waybar/config
{
  "layer": "top",
  "position": "bottom",
  "height": 30,
  "modules-right": [
    "tray",
    "network",
    "cpu",
    "disk",
    "memory",
    "pulseaudio",
    "clock",
    "custom/dunst"
  ],
  "network": {
    "interface": "eno1",
    "format": " {ifname} {ipaddr}",
    "interval": 30
  }
  // ... more modules
}

The CSS maintained my Dracula theme:

/* ~/.config/waybar/style.css */
window#waybar {
  background-color: #111111;
  color: #dddddd;
}
 
#workspaces button.focused {
  background-color: #0088cc;
  color: #ffffff;
}

Keeping My Password Workflow: rofi → wofi#

I rely heavily on rofi-pass for password management. The transition to wofi needed to preserve this workflow completely:

# i3 launcher with icons
bindsym $mod+d exec "rofi -show drun -lines 30 -show-icons"
 
# Hyprland wofi
bind = $mainMod, d, exec, wofi --show drun --lines 30
 
# wofi-pass
bind = $mainMod, T, exec, wofi-pass

More importantly, wofi-pass works perfectly as a rofi-pass replacement. My password workflow stayed identical - same keybindings, same muscle memory.

Improving Copy/Paste#

Copy/paste management on Linux is notoriously annoying. I had two main issues to solve: clipboard vs primary selection fragmentation, and getting mod+c/mod+v to work universally.

First, syncing clipboard and primary selection:

# Sync primary selection with clipboard
exec-once = wl-paste -p -t text --watch wl-copy

This ensures that when I select text, it automatically goes to the clipboard too. No more clipboard/primary selection confusion.

Second, making mod+c/v work like every other OS:

# Use sendshortcut to make mod+c/v work universally
bind = $mainMod, c, sendshortcut, CTRL+SHIFT, c,
bind = $mainMod, v, sendshortcut, CTRL+SHIFT, v,
 
# Clipse clipboard manager
exec-once = clipse -listen
bind = $mainMod, p, exec, ghostty --class=clipse -e clipse
 
# Make clipse window float and look nice
windowrulev2 = float,title:(clipse)
windowrulev2 = center,title:(clipse)
windowrulev2 = size 800 600,title:(clipse)

The sendshortcut command forwards the key combination to the focused application. So mod+c becomes ctrl+c in whatever app has focus. For paste, I use ctrl+shift+v because most terminal apps expect that.

Clipse is a simple clipboard manager that stores everything in a history. Press mod+p and get a floating terminal with your clipboard history.

Screenshot Tool: flameshot → grim + slurp#

# i3 screenshots
bindsym $mod+Shift+s exec --no-startup-id "flameshot gui"
 
# Hyprland equivalent
bind = $mainMod SHIFT, s, exec, grim -g "$(slurp)" - | wl-copy

A quick aside, careful with your envs#

This drove me crazy for the better part of an hour. Lutris launched perfectly from my terminal but clicking it in wofi did nothing. No error, no feedback, just silence.

The Real Problem#

After creating a debug script to capture the environment, I found the issue:

# From terminal (working)
$ python3 --version
Python 3.13.7
$ which python3
/bin/python3
 
# From Hyprland launcher (broken)
Python version: Python 3.12.3
/home/tsoporan/.rye/py/cpython@3.12.3/lib/python3.12/site-packages
ModuleNotFoundError: No module named 'lutris'

The culprit: rye was setting up a Python 3.12 environment in my .zshrc. My terminal inherited this environment, but system Python 3.13 (where Lutris was actually installed) was what I thought I was using.

When launching from wofi, Hyprland processes didn’t load my .zshrc, so they tried to use rye’s Python 3.12 - which didn’t have Lutris installed.

Visual Consistency: Dracula Theme Throughout#

Maintaining my beloved Dracula theme across components:

# Hyprland window borders
general {
    col.active_border = rgba(6272A4ff)     # Dracula blue
    col.inactive_border = rgba(44475Aff)   # Dracula comment
}

The Waybar CSS also follows the Dracula color scheme (shown in the earlier Waybar section).

Performance and Polish#

Font Scaling for 4K#

With fractional scaling, font sizes needed adjustment:

# Ghostty terminal
font-family = JetBrainsMono Nerd Font
font-size = 14  # Increased from default for 4K readability

Autostart Applications#

exec-once = waybar
exec-once = hyprpaper
exec-once = dunst
exec-once = swayidle -w timeout 300 'swaylock'
exec-once = wl-paste --type text --watch cliphist store
exec-once = wl-paste --type image --watch cliphist store
exec-once = wl-paste -p -t text --watch wl-copy
exec-once = dbus-update-activation-environment --all

What I Actually Gained#

My Hyprland setup now feels pretty much like my old i3 workflow, but with bonuses:

  • Improved 4K handling - it looks pretty crisp, and the weird picom/compositing issues seem to be gone
  • Same muscle memory - every keybinding works identically
  • Better clipboard behavior - clipse is nicer than what I was using before, it also supports images which is a benefit

The migration was surprisingly straight forward, took about 2hrs with Claude Code helping with the config translation and research (typically would spend a weekend on this!). If you’re interested in how I use AI tools for development, check out my post on Building with Claude Code: Dev Philosophy.

Updates#

  • I could not stand the layout management with Hyprland, but luckily found hy3 - highly recommended. This gives me back the tabbed/tiling toggle that I’ve grown accustomed to, e.g:
$mod+s - swaps between tabbed vs tiled mode
  • Swapped from dunst to swaync for notifications, just works with Wayland + some minor theme tweaks

  • I had to update my scaling from auto to at least 1.5x in the hyprland config, otherwise some apps, which do support Wayland, look super tiny (e.g Chromium, Signal)


System specs: Arch Linux, AMD Radeon RX 7800 XT, 4K display with fractional scaling