Justin WyneComment

Ultimate Mac Keymap Guide

Justin WyneComment
Ultimate Mac Keymap Guide

Introduction

Ever since I learned vim in my university Operating Systems course, I've been hooked on mouse-less computing. Vim accomplishes this well for text editing, but the desire to have that efficiency of keyboard-only capabilities expanding to the rest of computing was a natural consequence.

Over the years, I've gone overboard in this mission trying to eliminate the use of a mouse completely with browser extensions like cvim, vimperator, qutebrowser, emulating mouse movements through key presses, and others. But practically speaking, you will end up needing to use a mouse in a modern computing environment anyway. So after many attempts at overdoing it, I've settled back on a practical balance between keyboard-use optimization and efficient mouse bindings. Below are the results of that journey.

A warning on practicality

The amount of time spent on this endeavor, I admit to the reader, is arguably not a worthwhile one. If someone else has already done the work for you, you can skip some of the trials and tribulations of those errors and get to something meaningful more quickly. However, the concepts here tend to be very personal; specific to your own workflows and preferences. So don't strictly do this for a "productivity gain". I've invested time into tuning all of this over a decade because I enjoy it. If you attempt to do the same, it will undoubtedly become a rabbit hole for you as well. But taking inspiration from others and coming up with your own approach is half the fun.

Principles

To achieve balance without excess as mentioned above, there are a few main principles I stick to:

  • Portability - I want the setup to be easily transferrable between multiple computers and environments. More on this in the Software First section.
  • Home Row First - Try to put as many of the commonly used shortcuts into the home row to minimize
  • Left or Right hand - Enable the ability to do as many common tasks as possible with my hands in both standard positions. These positions are 1 - both hands on the keyboard and 2 - left on keyboard, right on mouse.
  • Leverage existing keymap conventions and avoid conflicts - Many programs follow conventions on keybindings. The approaches below try to leverage those conventions while also staying out of the way of application specific shortcuts of software that I most often use.

Software First

The most important principle above is of portability. I certainly acknowledge the allure of mechanical keyboards and their custom buttons, layouts, and personalization software (QMK/VIA). But, I often take my laptop with me while traveling or even around the house. I don't want to have to carry around a keyboard with me or feel like I'm missing out on functionality when I don't have it with me. So I've chosen to stick to software solutions that work on any keyboard.

I'm sure you can do this with lots of different software, but the ones I use are:

Karabiner elements is my go-to workhorse for creating custom keybindings. My configuration is a 3k line json file. There are some UIs (complex modification) that help with that, but I always end up editing the file directly in vim. Code folding is a huge help here.

Hammerspoon is used primarily for the grid window management.

I use Keyboard Maestro for niche cases that Karabiner doesn't yet support.

You can back up your configurations with yadm and also share them with your other computers.

 

Raycast is an awesome app for all sorts of things. For the purposes of this article, it has an especially useful command to switch between profiles of Karabiner Elements.

Configurations

And now onto the actual configurations. I'll start with the most basic and work up to the more complex.

Basics

  • Remap Caps Lock to Control and Escape.

The first thing I do on any new computer is remap the Caps Lock key to Control and Escape. This is a common convention in the vim community and is a great way to get started with keyboard optimization.

  • ctrl when held down and pressed with another key
  • escape when pressed and released on its own

Arrow Keys

Now that we have the contorl key in the right place, the second-most used of all these bindings is putting arrow keys on the home row. I've found that it is quite reliable and doesn't conflict with existing key mappings.

<!-- Number Row --> ` 1 2 3 4 5 6 7 8 9 0 - = delete <!-- QWERTYUIOP --> tab Q W E R T Y U I O P [ ] \ <!-- ASDFGHJKL; --> ctrl / esc A S D F G H J K L ; ' return <!-- ZXCVBNM --> shift Z X C V B N M , . / shift <!-- Bottom Row --> fn ctrl opt cmd space cmd opt
  • ctrl + h - Left
  • ctrl + j - Down
  • ctrl + k - Up
  • ctrl + l - Right

Karabiner Elements Configuration:

{
    "description": "Control + h/j/k/l to Arrows",
    "manipulators": [
        {
            "from": {
                "key_code": "h",
                "modifiers": {
                    "mandatory": [
                        "control"
                    ],
                    "optional": [
                        "caps_lock"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "left_arrow"
                }
            ],
            "type": "basic"
        },
        {
            "from": {
                "key_code": "j",
                "modifiers": {
                    "mandatory": [
                        "control"
                    ],
                    "optional": [
                        "caps_lock"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "down_arrow"
                }
            ],
            "type": "basic"
        },
        {
            "from": {
                "key_code": "k",
                "modifiers": {
                    "mandatory": [
                        "control"
                    ],
                    "optional": [
                        "caps_lock"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "up_arrow"
                }
            ],
            "type": "basic"
        },
        {
            "from": {
                "key_code": "l",
                "modifiers": {
                    "mandatory": [
                        "control"
                    ],
                    "optional": [
                        "caps_lock"
                    ]
                }
            },
            "to": [
                {
                    "key_code": "right_arrow"
                }
            ],
            "type": "basic"
        }
    ]
}

Application Launcher

For launching apps, I chose ; as the meta key because it's easy to reach and doesn't conflict with any other shortcuts I use.

Also, the choice you pick here shouldn't often have another key that follows it naturally and rolls into another key. For example, another alpha character. Because it will trigger accidentally when you're typing other words quickly and press the next key before you raise the original.

` 1 2 3 4 5 6 7 8 9 0 - = delete tab Q W E R T Y U I O P [ ] \ ctrl / esc A S D F G H J K L ; ' return shift Z X C V B N M , . / shift fn ctrl opt cmd space cmd opt

Home Row

  • ; + a - Zoom
  • ; + s - Slack
  • ; + d - Code
  • ; + f - Fantastical
  • ; + h - Home
  • ; + j - 1Password

High Row

  • ; + e - Reminders
  • ; + r - Brave
  • ; + t - iTerm
  • ; + o - Obsidian

Low Row

  • ; + m - Music

Karabiner Elements Configuration:

{
    "description": "Semicolon as modifier layer",
    "manipulators": [
        {
            "from": {
                "key_code": "semicolon"
            },
            "to": [
                {
                    "set_variable": {
                        "name": "semicolon_modifier",
                        "value": 1
                    }
                }
            ],
            "to_after_key_up": [
                {
                    "set_variable": {
                        "name": "semicolon_modifier",
                        "value": 0
                    }
                }
            ],
            "to_if_alone": [
                {
                    "key_code": "semicolon"
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "name": "semicolon_modifier",
                    "type": "variable_if",
                    "value": 1
                }
            ],
            "from": {
                "key_code": "h"
            },
            "to": [
                {
                    "shell_command": "open -a Home"
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "name": "semicolon_modifier",
                    "type": "variable_if",
                    "value": 1
                }
            ],
            "from": {
                "key_code": "m"
            },
            "to": [
                {
                    "shell_command": "open -a Music"
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "name": "semicolon_modifier",
                    "type": "variable_if",
                    "value": 1
                }
            ],
            "from": {
                "key_code": "t"
            },
            "to": [
                {
                    "shell_command": "open -a Telegram"
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "name": "semicolon_modifier",
                    "type": "variable_if",
                    "value": 1
                }
            ],
            "from": {
                "key_code": "r"
            },
            "to": [
                {
                    "shell_command": "open -a Brave\\ Browser"
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "name": "semicolon_modifier",
                    "type": "variable_if",
                    "value": 1
                }
            ],
            "from": {
                "key_code": "j"
            },
            "to": [
                {
                    "shell_command": "open -a 1Password"
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "name": "semicolon_modifier",
                    "type": "variable_if",
                    "value": 1
                }
            ],
            "from": {
                "key_code": "e"
            },
            "to": [
                {
                    "shell_command": "open -a Reminders"
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "name": "semicolon_modifier",
                    "type": "variable_if",
                    "value": 1
                }
            ],
            "from": {
                "key_code": "f"
            },
            "to": [
                {
                    "shell_command": "open -a Fantastical"
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "name": "semicolon_modifier",
                    "type": "variable_if",
                    "value": 1
                }
            ],
            "from": {
                "key_code": "d"
            },
            "to": [
                {
                    "shell_command": "open -a Visual\\ Studio\\ Code\\ -\\ Insiders"
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "name": "semicolon_modifier",
                    "type": "variable_if",
                    "value": 1
                }
            ],
            "from": {
                "key_code": "o"
            },
            "to": [
                {
                    "shell_command": "open -a Obsidian"
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "name": "semicolon_modifier",
                    "type": "variable_if",
                    "value": 1
                }
            ],
            "from": {
                "key_code": "i"
            },
            "to": [
                {
                    "shell_command": "open -a iterm"
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "name": "semicolon_modifier",
                    "type": "variable_if",
                    "value": 1
                }
            ],
            "from": {
                "key_code": "a"
            },
            "to": [
                {
                    "shell_command": "open -a zoom.us"
                }
            ],
            "type": "basic"
        },
        {
            "conditions": [
                {
                    "name": "semicolon_modifier",
                    "type": "variable_if",
                    "value": 1
                }
            ],
            "from": {
                "key_code": "s"
            },
            "to": [
                {
                    "shell_command": "open -a slack"
                }
            ],
            "type": "basic"
        }
    ]
}

Resize and Move Windows

The next two are accomplished with a Hammerspoon script.

Resizing

<!-- Number row --> ` 1 2 3 4 5 6 7 8 9 0 - = delete <!-- Keyboard keys --> tab Q W E R T Y U I O P [ ] \ <!-- second row --> ctrl / esc A S D F G H J K L ; ' return <!-- next row --> shift Z X C V B N M , . / shift <!-- bottom row --> fn ctrl opt cmd space cmd opt
  • ctrl + cmd + h - Shrink Left
  • ctrl + cmd + j - Grow Down
  • ctrl + cmd + k - Shrink Up
  • ctrl + cmd + l - Grow Right

Moving

<!-- Number row --> ` 1 2 3 4 5 6 7 8 9 0 - = delete <!-- Keyboard keys --> tab Q W E R T Y U I O P [ ] \ <!-- second row --> ctrl / esc A S D F G H J K L ; ' return <!-- next row --> shift Z X C V B N M , . / shift <!-- bottom row --> fn ctrl opt cmd space cmd opt
  • ctrl + opt + h - Shrink Left
  • ctrl + opt + j - Grow Down
  • ctrl + opt + k - Shrink Up
  • ctrl + opt + l - Grow Right

Hammerspoon Configuration

-- GRID
hs.window.animationDuration=0.2
local hotkey = require "hs.hotkey"
local grid = require "hs.grid"

grid.MARGINX = 20
grid.MARGINY = 20
grid.GRIDHEIGHT = 4
grid.GRIDWIDTH = 6

local mod_resize = {"ctrl", "cmd"}
local mod_move = {"ctrl", "alt"}

-- Move Window
hotkey.bind(mod_move, 'j', grid.pushWindowDown)
hotkey.bind(mod_move, 'k', grid.pushWindowUp)
hotkey.bind(mod_move, 'h', grid.pushWindowLeft)
hotkey.bind(mod_move, 'l', grid.pushWindowRight)

-- Resize Window
hotkey.bind(mod_resize, 'k', grid.resizeWindowShorter)
hotkey.bind(mod_resize, 'j', grid.resizeWindowTaller)
hotkey.bind(mod_resize, 'l', grid.resizeWindowWider)
hotkey.bind(mod_resize, 'h', grid.resizeWindowThinner)

Tabs

Most applications use a similar keyboard shortcut

Tabs are already quite accessible with the nearly universal keyboard shortcut of cmd + shift + [ and cmd + shift + ].

To add compatibility with the principle of supporting both hand positions, I also change the forward/back buttons on the mouse to tab switching. I rarely use the forward/back buttons on the mouse, and switching tabs is much more common for me.

  • Forward -> cmd + shift + [ Left one tab
  • Backward -> cmd + shift + ] Right one tab

Karabiner Elements snippet:

{
    "description": "Mouse forward/back to switch tabs",
    "manipulators": [
        {
            "from": {
                "modifiers": {},
                "pointing_button": "button4"
            },
            "to": [
                {
                    "key_code": "close_bracket",
                    "modifiers": [
                        "command",
                        "shift"
                    ]
                }
            ],
            "type": "basic"
        },
        {
            "from": {
                "modifiers": {},
                "pointing_button": "button5"
            },
            "to": [
                {
                    "key_code": "open_bracket",
                    "modifiers": [
                        "command",
                        "shift"
                    ]
                }
            ],
            "type": "basic"
        }
    ]
}

Text Navigation

These may not look or feel quite as natural, but they are the same bindings as in the terminal. Except, f and b are swapped for word jumps instead of character jumps.

<!-- Number row --> ` 1 2 3 4 5 6 7 8 9 0 - = delete <!-- Keyboard keys --> tab Q W E R T Y U I O P [ ] \ <!-- second row --> ctrl / esc A S D F G H J K L ; ' return <!-- next row --> shift Z X C V B N M , . / shift <!-- bottom row --> fn ctrl opt cmd space cmd opt
  • ctrl + a - Move to beginning of line
  • ctrl + e - Move to end of line
  • ctrl + f - Move forward one word
  • ctrl + b - Move backward one word

Mac Menu Bar

Accessing the menu bar from the keyboard is really handy for quick lookups such as calendar, weather, and system performance. The benefit of this is that you don't have to switch applications and can quickly get back to what you were doing because the popup will disappear.

<!-- Number row --> ` 1 2 3 4 5 6 7 8 9 0 - = delete <!-- Keyboard keys --> tab Q W E R T Y U I O P [ ] \ <!-- second row --> ctrl / esc A S D F G H J K L ; ' return <!-- next row --> shift Z X C V B N M , . / shift <!-- bottom row --> fn ctrl opt cmd space cmd opt
  • cmd + ctrl + u Application Windows
  • cmd + ctrl + i Mission Control
  • cmd + ctrl + o Show Desktop
  • ctrl + cmd + = Notification Center
  • ctrl + cmd + - Battery
  • ctrl + cmd + 0 Weather
  • ctrl + cmd + 9 Calendar
  • ctrl + cmd + 8 iStat group
  • ctrl + cmd + 7 Fantastical

Conclusion

As I mentioned above, these are just some of the things I've found useful, they are highly personal, and likely to change often. Let me know if you have any questions or comments. I'd love to hear about your setup and how you use it.