It’s not a bird or a plane—it’s a module for QMK keyboards!

Overview

With QMK’s Leader Key, you map sequences of keys to trigger custom actions. Conveniently, these sequences don’t use real estate in the layout, and they can be mnemonic to help with remembering them. However, unlike Vim’s leader key, QMK’s leader implementation always waits for a timeout before resolving, which feels sluggish, and the sequence definitions are cumbersome. I want a leader key in QMK, but snappy.

Super Leader is a responsive alternative. Response is instant when the sequence is not a prefix of any other sequence; the sequence triggers immediately on completion. Sequences being prefixes of others is still allowed (see how matching works for details). Definitions are concise, typically a single line per leader sequence.

Super Leader sequences can:

Example: The code snippet below implements the following leader sequences.

Input Result
LEADER, V Ctrl+V (C(KC_V)).
LEADER, M, E Types my@email.com.
LEADER, T, H, X Types 🙏.
LEADER, R, G, B Calls user-defined function favorite_rgb().

In super_leader.def

SEQ_KEY(v,   (KC_V),             C(KC_V))
SEQ_STR(me,  (KC_M, KC_E),       "my@email.com")
SEQ_UNI(thx, (KC_T, KC_H, KC_X), "🙏")
SEQ_FUN(rgb, (KC_R, KC_G, KC_B), favorite_rgb)

In keymap.c

static void favorite_rgb(void*) {
  rgb_matrix_enable();
  rgb_matrix_mode(RGB_MATRIX_HUE_BREATHING);
  rgb_matrix_sethsv(HSV_GOLDENROD);
}

Add it to your keymap

Step 1: Module installation

Install my community modules, then enable the getreuer/super_leader module in your keymap.json:

{
  "modules": ["getreuer/super_leader"]
}

Additionally, enable the Combos feature or Repeat Key (or both) in your rules.mk:

COMBOS_ENABLE = yes
REPEAT_KEY_ENABLE = yes

(Either of these features enables the .keycode field in keyrecord_t, which the implementation relies on.)

Step 2: Add a LEADER key

Dedicating a key to LEADER isn’t required, but you can if you want to.

In your keymap.c file, assign keycode LEADER somewhere in your layout. Pressing this key starts a leader sequence.

Or instead of starting a leader sequence with a dedicated key, you can alternatively produce LEADER from a combo or call super_leader_start() from a tap dance or other custom handler (see Programmatic API).

Step 3: Define sequences in super_leader.def

In your keymap folder, create a file super_leader.def to define your leader sequences:

SEQ_KEY(v,   (KC_V),             C(KC_V))
SEQ_KEY(dfu, (KC_D, KC_F, KC_U), QK_BOOT)
SEQ_STR(me,  (KC_M, KC_E),       "my@email.com")

Each “SEQ_*(name, (key1, key2, ...), output)” line defines a leader sequence. The first arg is a unique name for the sequence (any valid C identifier). The second arg (key1, key2, ...) lists the sequence keys. The third arg specifies the output.

Notes:

Sequence outputs

Super Leader sequences can result in Keycode output, String output, Unicode output, or Calling a user-defined function, detailed in the following sections.

Keycode output

SEQ_KEY(name, (key1, key2, ...), keycode)

Defines a sequence that outputs a keycode. Most output keycodes work here, even user-defined custom keycodes.

Key holding: Provided the sequence is not a prefix of any other sequence, the output key is held as long as the last key of the sequence is held. This opens some interesting possibilities, for instance using a leader sequence as a momentary layer switch:

SEQ_KEY(spc, (KC_SPC), MO(3))

With the above: tapping LEADER and then pressing Space activates layer 3. The layer stays active until Space is released.

⚠  Warning

If using a TO or TG layer switch as an output keycode, be sure the target layer includes a TO(0) key or other means to turn it back off. Otherwise, the layer will be stuck!

String output

SEQ_STR(name, (key1, key2, ...), "string")

Defines a sequence that outputs a string via SEND_STRING().

The X_* and SS_* codes are supported as described in the QMK Send String documentation. For instance, this sequence types () and taps left arrow to place the cursor between the ():

SEQ_STR(p, (KC_P), "()" SS_TAP(X_LEFT))

Unicode output

SEQ_UNI(name, (key1, key2, ...), "unicode")

Defines a sequence that outputs a Unicode string via send_unicode_string(). Note that SEQ_UNI requires the QMK Unicode feature. Minimally, set “UNICODE_COMMON = yes” in your rules.mk and configure UNICODE_SELECTED_MODES in config.h.

Calling a user-defined function

SEQ_FUN(name, (key1, key2, ...), fun)

Defines a sequence that calls a custom callback function fun(void* user_data).

Example: Input LEADER, R calls print_random_number(), typing a pseudorandom number between 0 and 99:

In keymap.c

static void print_random_number(void*) {
  send_string(get_u8_str(rand() % 100, ' '));
}

In super_leader.def

SEQ_FUN(r, (KC_R), print_random_number)

Passing user data: Optionally, you can pass a void* pointer to any user data as an additional argument after the function:

In keymap.c

static void html_tags(void* user_data) {
  const char* tag = (const char*)user_data;
  send_char('<'); send_string(tag); send_char('>');     // "<tag>".
  SEND_STRING("</"); send_string(tag); send_char('>');  // "</tag>".
  // Move cursor between the tags.
  for (int8_t count = strlen(tag) + 3; count > 0; --count) {
    tap_code(KC_LEFT);
  }
}

In super_leader.def

SEQ_FUN(hd, (KC_H, KC_D), html_tags, "div")
SEQ_FUN(ht, (KC_H, KC_T), html_tags, "table")
SEQ_FUN(hu, (KC_H, KC_U), html_tags, "ul")

Tapping LEADER, H, T calls html_tags("table"), producing “<table></table>” and positioning the cursor between the tags. The user_data arg parameterizes the tag name, which we use for sequences producing div and ul tags as well.

An aside on callbacks…

It is a common pattern in QMK (and C generally) that a callback function will include a void* arg for user data. This is because a void* is flexible enough to pass any data into the callback.

For instance, suppose you wanted to pass both an integer and a string. This can be done by creating a struct comprising those fields, then pointing user_data at it:

In keymap.c

typedef struct { // Define struct type.
  int16_t i;
  char* str;
} foo_params_t;

static void foo(void* user_data) {
  foo_params_t* params = (foo_params_t*)user_data; 
  int16_t i = params->i;
  char* str = params->str;
  // Use i and str...
}

In super_leader.def

SEQ_FUN(f, (KC_F), foo, &(foo_params_t){.i = 42, .str = "hey"})

How matching works

Super Leader eliminates latency by evaluating keystrokes in real time as they enter the buffer. If a sequence is not a prefix of any other, it triggers immediately. Or when a sequence is a partial match, the system loops back to wait for more input. If you press a non-matching key or the 1000 ms timer expires, Super Leader fires the longest valid match and automatically replays any remaining buffered keys.

Super Leader as a block diagram.

For sake of example, consider these definitions:

SEQ_KEY(x,   (KC_X),             MS_BTN1)
SEQ_KEY(z,   (KC_Z),             C(KC_Z))
SEQ_UNI(zap, (KC_Z, KC_A, KC_P), "⚡")

Super Leader’s sequence matching works as follows:

By default, keys in a sequence must be typed within 1000 ms of each other, and sequences can be up to 5 keys long.

Configuration

Max sequence length

The default max sequence length is 5 keys (excluding the leader key). Adjust in config.h:

#define SUPER_LEADER_MAX_LENGTH  6

Timeouts

Set the max time allowed between sequence keys (default 1000 ms) in config.h:

#define SUPER_LEADER_TIMEOUT  2000 

Analogous to QMK’s LEADER_NO_TIMEOUT option, you can disable timeout between tapping LEADER and tapping the first key of the sequence with:

#define SUPER_LEADER_NO_INIT_TIMEOUT

The idea is that if your leader key is some outer key, far away from the sequence keys, you then have unlimited time to reposition your hand after tapping LEADER to enter the sequence.

Strict key processing

By default, only the tapping key portion of tap-hold keys added to the sequence buffer, for instance, key LSFT_T(KC_F) matches as KC_F. If you would rather match based on the full keycode, define in config.h:

#define SUPER_LEADER_STRICT_KEY_PROCESSING

Callbacks

Optionally, define these callbacks in your keymap.c to respond to leader state changes (e.g. to light an LED or update an OLED screen):

void super_leader_start_user(void) {
  // Leader sequence started.
}

void super_leader_end_user(bool successful_match) {
  if (successful_match) {
    // Leader sequence matched.
  } else {
    // Ended without a match.
  }
}

void super_leader_add_user(
    const uint16_t* seq, uint8_t num_seq, bool* partial) {
  // Current sequence buffer: { seq[0], seq[1], ..., seq[num_seq - 1] }.
}

Programmatic API

These functions are available for programmatic control:

Function Description
super_leader_sequence_active() Whether a leader sequence is active.
super_leader_start() Begins a leader sequence.
super_leader_cancel() Cancels the active leader sequence, if any.
super_leader_reset_timer() Resets the sequence timeout timer.
super_leader_add(kc) Adds a keycode to the sequence buffer.

Programmatic sequences

You can match sequences programmatically through the super_leader_add_user() callback. This might be used for instance to fit general patterns. Sequences implemented this way are considered in addition to those in super_leader.def.

Example:

void super_leader_add_user(
    const uint16_t* seq, uint8_t num_seq, bool* partial) {

  // Sequence "R, E, P, <key>" => Taps <key> 10 times.
  if (SUPER_LEADER_SEQ_STARTS_WITH((KC_R, KC_E, KC_P), partial) && 
      num_seq == 4) {
    static uint16_t key;
    void rep_key(void*) {
      for (uint8_t i = 0; i < 10; ++i) { tap_code16(key); }
    }
    key = seq[3];
    super_leader_set_match(SUPER_LEADER_FUN(rep_key));
  }

  // "A, <key>, <same key>" where <key> is a letter => Taps AltGr+<key>.
  if (SUPER_LEADER_SEQ_STARTS_WITH((KC_A), partial) &&
      KC_A <= seq[1] && seq[1] <= KC_Z) {
    if (num_seq < 3) {
      *partial = true;
    } else if (num_seq == 3 && seq[1] == seq[2]) {
      uint16_t modded_keycode = ALGR(seq[1]);
      super_leader_set_match(SUPER_LEADER_KEY(modded_keycode));
    }
  }
}

Callback Expectations

The super_leader_add_user() callback is called every time a key is added to the sequence buffer. The callback must fulfill these expectations:

In ambiguous cases, it is possible to partially match one sequence and fully match another. The correct handling then is to set *partial = true and call super_leader_set_match().

Match logic is indeed nontrivial. The SUPER_LEADER_SEQ_STARTS_WITH() helper can ease the implementation for some types of patterns. It is strongly recommended to use super_leader.def for fixed sequences of keys.