Project 5: Layers, Tap-Dance, and Advanced Behaviors
Build a power-user keymap with three layers, mod-tap keys, tap dance actions, and combos that feel reliable at speed.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Level 3: Intermediate |
| Time Estimate | 1-2 weeks |
| Main Programming Language | C (QMK) |
| Alternative Programming Languages | None |
| Coolness Level | Level 4: Wizard |
| Business Potential | Level 2: Productivity Booster |
| Prerequisites | Project 4, QMK keymap basics |
| Key Topics | Layer stack, mod-tap, tap-dance, combos |
1. Learning Objectives
By completing this project, you will:
- Design a three-layer layout that covers navigation and symbols.
- Implement mod-tap keys with tuned tapping terms.
- Create at least two tap-dance actions and one combo.
- Resolve conflicts between tap-hold and combo behaviors.
- Validate reliability with real typing tests.
2. All Theory Needed (Per-Concept Breakdown)
2.1 Layer Stack Semantics and Transparent Keys
Fundamentals
QMK uses a layer stack to decide which keycode is active. Layers can be momentary, toggled, or persistent. When a key is pressed, QMK checks the highest active layer; if the key is KC_TRNS, it falls through to lower layers. If the key is KC_NO, it is disabled. This model lets you create a small keyboard that behaves like a larger one by shifting layers for symbols, navigation, and media. The key to a reliable layout is understanding how these layer priorities interact.
Deep Dive into the Concept
Layers in QMK are not just alternate keymaps; they are a stack with priority rules. The top-most active layer wins unless the key is transparent. If multiple layers are active (for example, a momentary layer on top of a toggled layer), the search continues downward until a non-transparent key is found. This allows you to build reusable layers that override only specific keys and leave others unchanged.
Layer activation can be momentary (MO()), toggle (TG()), or set/clear (TO(), DF()). Momentary layers are active only while a key is held. This is ideal for symbol layers or navigation layers. Toggle layers stay active until toggled off, which can be useful for numpads but can also cause confusion if not indicated clearly. Default layers (DF()) are persistent across reboots and are usually used for alternative base layouts like QWERTY and Colemak.
The practical design challenge is balancing convenience with mental load. A small keyboard becomes powerful when you place frequently used keys on easy-to-reach layer positions, but it can become frustrating if you stack too many layers or make them inconsistent. This is why many advanced layouts use a consistent layer structure: base, navigation, and symbols. Each layer has a defined purpose and uses transparent keys to preserve base behavior. In this project, you’ll intentionally design a three-layer system with a small set of overrides.
Transparent keys are the glue. If you forget to make a key transparent, it becomes KC_NO and effectively dead on that layer. This is a common source of “my key doesn’t work on layer X” bugs. Conversely, using too many transparent keys can lead to unexpected behavior if you forget which layers are active. A good practice is to explicitly set KC_TRNS on keys you intend to pass through and KC_NO on keys you want to disable. This makes your intent clear.
Layer state is represented as a bitmask. QMK stores a bit per layer and uses bit operations to decide which layers are active. This means you can have multiple layers active simultaneously. You can also define layer indicators (LEDs or OLED) based on the current highest layer, which you’ll do in Project 6. Understanding the bitmask model helps you reason about how a mod-tap that activates a layer behaves when combined with other keys.
Additional layer practices: many power-user layouts use a tri-layer where two layers held together activate a third (e.g., NAV + SYM = ADJUST). QMK supports this with update_tri_layer_state, which is a clean way to expose rarely used functions without adding extra keys. Use layer_state_set_user to centralize layer logic and ensure your UI indicators follow the highest active layer. When documenting your layout, list each layer’s purpose and keep a quick reference card; this reduces cognitive load and makes testing more systematic.
Extra design guidance: consider creating a dedicated “adjust” layer that appears only when two layers are held simultaneously (tri-layer). This lets you access rarely used functions without cluttering the base layer. In QMK, you can manage this with update_tri_layer_state inside layer_state_set_user. If you add this, document the trigger combo clearly and add a visual indicator so you do not get lost in the layer stack.
How this fits on projects
Layer semantics are central to Project 5 and appear in Project 6 (visual layer feedback) and Project 8 (layer sync across split halves).
Definitions & key terms
- Layer stack: Ordered list of active layers used to resolve keycodes.
- Transparent key:
KC_TRNS, pass-through to lower layers. - Default layer: Base layer selected with
DF(). - Momentary layer: Active only while a key is held.
Mental model diagram (ASCII)
Active layers (top wins):
L2 (NAV) -> L1 (SYM) -> L0 (BASE)
Key pressed: if L2 key = TRNS -> check L1 -> check L0
How it works (step-by-step, with invariants and failure modes)
- Determine active layers bitmask.
- For a key press, search from highest to lowest active layer.
- If keycode is transparent, continue to lower layer.
- Use first non-transparent keycode.
Invariant: a key is always resolved to a single keycode. Failure modes include unintended dead keys due to KC_NO or forgotten transparency.
Minimal concrete example
[_BASE] = LAYOUT(KC_A, KC_S, KC_D),
[_NAV] = LAYOUT(KC_TRNS, KC_LEFT, KC_RGHT)
Common misconceptions
- “Layers override everything”: Only non-transparent keys override.
- “Toggle layers are always better”: They can lead to stuck layers.
- “KC_NO is the same as KC_TRNS”:
KC_NOdisables the key.
Check-your-understanding questions
- What happens if a key is
KC_TRNSon the top layer? - Why might you use a default layer?
- How does QMK decide which layer is active if multiple are on?
Check-your-understanding answers
- QMK looks at the next lower layer for that key.
- To switch base layouts permanently (e.g., QWERTY/Colemak).
- The highest active layer with a non-transparent key wins.
Real-world applications
- Compact keyboards with layered navigation and symbols.
- Ergonomic layouts with multiple base layers.
Where you’ll apply it
- In this project: §3.2 Functional Requirements and §5.10 Phase 1.
- Also used in: Project 6, Project 8.
References
- QMK docs: Layers.
- “Making Embedded Systems” Ch. 5.
Key insights
Layers are a priority stack; transparency is the mechanism that makes layers composable.
Summary
Layer semantics let a small keyboard do the work of a large one, but only if you manage transparency and activation carefully.
Homework/Exercises to practice the concept
- Draw a base and symbol layer for a 34-key board.
- Mark which keys should be transparent and which should be overridden.
Solutions to the homework/exercises
- Place letters on base; symbols on layer with transparent keys for letters you keep.
- Transparent keys should be those you do not want to change on that layer.
2.2 Tap-Hold, Mod-Tap, Tap Dance, and Combo Timing
Fundamentals
Advanced QMK behaviors depend on timing. A mod-tap key acts as a normal key when tapped and as a modifier when held. Tap dance lets a key perform different actions based on tap count or hold. Combos allow chords of multiple keys to produce a new action. All of these features require precise timing thresholds, such as TAPPING_TERM and COMBO_TERM. If these values are wrong, keys misfire, combos trigger accidentally, or modifiers stick. Understanding the timing model is critical to making a layout feel reliable.
Deep Dive into the Concept
QMK distinguishes between tap and hold by measuring how long a key is pressed. If the press is shorter than TAPPING_TERM (typically 150-200 ms), it is considered a tap; if longer, it is a hold. Mod-tap keys such as MT(MOD_LCTL, KC_A) use this rule. But real typing is messy: users might press keys quickly in sequences, hold keys for roll-over, or chord keys together. QMK uses additional rules like PERMISSIVE_HOLD and IGNORE_MOD_TAP_INTERRUPT to resolve ambiguous cases. These settings change how QMK interprets a key when another key is pressed during its tapping term.
Tap dance adds another layer: the same key performs different actions depending on how many times it is tapped within a window. For example, tap once for ;, twice for :, hold for Shift. Tap dance uses a state machine that counts taps and determines whether the action should be sent immediately or deferred until the tap window expires. This can introduce latency if not tuned correctly. A good tap dance action sends a result quickly while still allowing double taps, which is why some users use TAP_DANCE_ENABLE plus a custom finished callback.
Combos are detected when multiple keys are pressed within a combo term. QMK records the timestamps of key presses and compares them. If the combination matches a defined combo array, QMK suppresses the original keys and sends the combo action instead. The challenge is that mod-tap keys and combos both depend on timing, and they can conflict. For example, pressing J and K quickly might trigger a combo, but if J is a mod-tap, QMK might interpret it as a hold. The solution is to tune COMBO_TERM and optionally set per-combo terms, and to decide which keys are safe as combo members.
Reliable behavior requires a testing loop. You should pick a base tapping term, then test common sequences. If you type fast, you may need a higher tapping term. If you use combos frequently, you may need a shorter combo term to avoid accidental triggers during normal typing. Some users also use per-key tapping term overrides to customize behavior for specific keys. QMK provides hooks like get_tapping_term and get_combo_term for this purpose.
The key insight is that these features are not just “enabled”; they are tuned. A layout feels great when tap-hold behavior matches your typing rhythm. It feels terrible when the firmware misclassifies your intent. This project forces you to collect real typing data, adjust timing, and converge on settings that work for you.
Additional timing nuance: QMK provides per-key settings for tapping term and quick tap. Keys on the home row often need a slightly higher tapping term than thumb keys because they are involved in rolls. You can implement get_tapping_term to return different values for specific keys. For combos, use COMBO_ONLY_FROM_LAYER to avoid accidental triggers on other layers, and consider COMBO_TERM_PER_COMBO for chords that need a tighter window. Collect real typing logs and adjust in small increments; large changes make it hard to isolate cause and effect.
Extra timing tactics: for tap dance, you can shorten the maximum tapping window by using TAP_DANCE_FN_ADVANCED_TIME and a custom timeout to reduce perceived lag. For combos, COMBO_ALLOW_ACTION_KEYS controls whether action keys like mod-tap can be part of combos. This can reduce accidental triggers during rolls. Collect actual typing logs or run repeated typing tests after each change; stable settings emerge from data, not guesswork.
How this fits on projects
This concept is central to Project 5 and informs Project 6 (feedback on layers) and Project 8 (split keyboard latency constraints).
Definitions & key terms
- Tapping term: Time threshold for distinguishing tap vs hold.
- Mod-tap: A key that acts as a modifier when held, normal key when tapped.
- Tap dance: A key that performs different actions based on tap count.
- Combo: A chord of multiple keys that triggers a single action.
Mental model diagram (ASCII)
Time axis (ms)
Key down ----- key up
< TAPPING_TERM => tap
>= TAPPING_TERM => hold
Combo: if K1 and K2 pressed within COMBO_TERM -> combo action
How it works (step-by-step, with invariants and failure modes)
- Record key press timestamp.
- If key released before tapping term -> tap action.
- If key held past tapping term -> hold action.
- For combos, compare press timestamps across keys.
Invariant: timing windows must be consistent and measurable. Failure modes include misclassification due to overlapping timing windows or scan rate jitter.
Minimal concrete example
#define TAPPING_TERM 200
const uint16_t PROGMEM esc_combo[] = {KC_J, KC_K, COMBO_END};
Common misconceptions
- “Default tapping term works for everyone”: Typing styles differ.
- “Combos are always safe”: They can trigger during rolls.
- “Tap dance is instant”: It waits to confirm tap count.
Check-your-understanding questions
- What happens if you press another key during a mod-tap key’s tapping term?
- Why can tap dance feel laggy?
- How does combo term affect accidental triggers?
Check-your-understanding answers
- Depending on settings, QMK may treat the mod-tap as a hold.
- It waits for the tap window to decide if it is a single or double tap.
- Longer combo terms increase accidental triggers during fast typing.
Real-world applications
- High-performance compact layouts for programmers.
- Stenography-style chorded input.
Where you’ll apply it
- In this project: §3.2 Functional Requirements and §5.10 Phase 2.
- Also used in: Project 6.
References
- QMK docs: mod-tap, tap dance, combos.
- “Making Embedded Systems” Ch. 4-6.
Key insights
Advanced QMK features are timing systems; tuning is as important as enabling.
Summary
Mod-tap, tap dance, and combos turn a small keyboard into a powerful one, but only when timing thresholds are tuned to your typing style.
Homework/Exercises to practice the concept
- Measure how fast you tap a key in ms using a stopwatch or typing software.
- Create a combo and test if it triggers during normal typing.
Solutions to the homework/exercises
- Use the average tap time to set
TAPPING_TERMwith a small safety margin. - Shorten
COMBO_TERMor change combo keys if accidental triggers occur.
3. Project Specification
3.1 What You Will Build
A three-layer QMK keymap with:
- Mod-tap keys on home row.
- At least two tap-dance actions.
- At least one combo chord.
- Tuned timing settings for reliable use.
3.2 Functional Requirements
- Layers: Base, navigation, and symbols.
- Mod-tap: At least four home-row mod-tap keys.
- Tap dance: Two keys with different actions per tap count.
- Combos: One or more combos with clear utility.
- Tuning:
TAPPING_TERMandCOMBO_TERMadjusted based on testing.
3.3 Non-Functional Requirements
- Reliability: No accidental combo triggers in normal typing.
- Usability: Layer transitions are easy to remember.
- Performance: No noticeable latency from tap dance.
3.4 Example Usage / Output
Tap: home row key -> outputs character
Hold: home row key -> acts as modifier
Combo: J + K -> Esc
Tap dance: ; -> ;, ;; -> :
3.5 Data Formats / Schemas / Protocols
- QMK keymap arrays and combo definitions.
3.6 Edge Cases
- Mod-tap misclassifies fast rolls as holds.
- Combos trigger when typing bigrams.
- Tap dance double-tap not recognized.
3.7 Real World Outcome
A layout that feels reliable at speed and provides more functionality than the physical key count.
3.7.1 How to Run (Copy/Paste)
qmk compile -kb lily58/rev1 -km poweruser
qmk flash -kb lily58/rev1 -km poweruser
3.7.2 Golden Path Demo (Deterministic)
- Tap HOME key: outputs “h”.
- Hold HOME key: acts as Ctrl.
- Tap J+K together: sends Esc.
3.7.3 If CLI: exact terminal transcript
$ qmk compile -kb lily58/rev1 -km poweruser
Creating hex file: .build/lily58_rev1_poweruser.hex
exit_code=0
$ qmk flash -kb lily58/rev1 -km poweruser
Flashing complete
exit_code=0
$ qmk compile -kb lily58/rev1 -km badname
error: keymap not found
exit_code=2
4. Solution Architecture
4.1 High-Level Design
Matrix Scan -> Layer Stack -> Feature Handlers (mod-tap, combos) -> HID Report
4.2 Key Components
| Component | Responsibility | Key Decisions |
|---|---|---|
| Layer Stack | Resolve keycodes | Base + two layers |
| Mod-tap | Tap vs hold classification | Tapping term tuning |
| Combos | Chord detection | Combo term, key selection |
| Tap Dance | Multi-tap behavior | Per-key tap dance actions |
4.3 Data Structures (No Full Code)
const uint16_t PROGMEM esc_combo[] = {KC_J, KC_K, COMBO_END};
4.4 Algorithm Overview
Key Algorithm: Tap-Hold Decision
- On key press, record timestamp.
- If released before tapping term -> tap.
- If held past tapping term -> hold (modifier).
Complexity Analysis:
- Time: O(1) per key event
- Space: O(1) per key
5. Implementation Guide
5.1 Development Environment Setup
qmk setup
qmk doctor
5.2 Project Structure
keyboards/<board>/keymaps/poweruser/
├── keymap.c
└── rules.mk
5.3 The Core Question You’re Answering
“How can a small physical keyboard feel larger than a full-size board?”
5.4 Concepts You Must Understand First
- Layer stack semantics.
- Tap-hold timing and mod-tap.
- Combo detection rules.
5.5 Questions to Guide Your Design
- Which keys should be mod-tap vs normal?
- What chords are safe for combos?
- How will you tune tapping term for your typing speed?
5.6 Thinking Exercise
Design a 34-key layout with base, navigation, and symbols. Mark which keys are mod-tap and which are layer toggles.
5.7 The Interview Questions They’ll Ask
- How does QMK distinguish tap vs hold?
- What is a transparent key and why is it useful?
- How do combos interact with mod-tap keys?
5.8 Hints in Layers
Hint 1: Start with a simple 2-layer layout Add the third layer only after the first two are stable.
Hint 2: Tune tapping term Increase by 20 ms increments until misfires stop.
Hint 3: Choose safe combos Avoid common letter bigrams.
Hint 4: Use per-key tapping term overrides Customize for keys that feel inconsistent.
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| Embedded timing | “Making Embedded Systems” | Ch. 4-6 |
| C macros | “The C Programming Language” | Ch. 4 |
| Debugging | “The Art of Debugging with GDB” | Ch. 1-2 |
5.10 Implementation Phases
Phase 1: Layer Design (2-3 days)
Goals: stable base and navigation layers
Tasks:
- Define base layer layout.
- Add navigation layer with arrows and modifiers.
Checkpoint: You can type normally and navigate without confusion.
Phase 2: Advanced Behaviors (3-4 days)
Goals: mod-tap, tap dance, combos
Tasks:
- Add home-row mod-tap keys.
- Add two tap-dance actions.
- Add at least one combo.
Checkpoint: Features work without accidental triggers.
Phase 3: Tuning (2-3 days)
Goals: timing stability
Tasks:
- Tune
TAPPING_TERMandCOMBO_TERM. - Add per-key overrides if needed.
Checkpoint: Layout feels reliable during fast typing.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale |
|---|---|---|---|
| Tapping term | 150-250 ms | 200 ms | balanced default |
| Combo keys | adjacent vs non-adjacent | adjacent | easier to press |
| Tap dance usage | many vs few | few | minimize latency |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples |
|---|---|---|
| Typing Tests | Evaluate misfires | typing test paragraphs |
| Feature Tests | Validate combos/tap dance | specific key sequences |
| Edge Case Tests | Stress timing thresholds | rapid taps vs holds |
6.2 Critical Test Cases
- Tap vs hold on home-row mod-tap keys in fast rolls.
- Combo triggers only when intended.
- Tap dance recognizes double tap without noticeable lag.
6.3 Test Data
Typing test: 200-word paragraph
Combo test: J+K -> Esc
Tap dance: ; -> ;, ;; -> :
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution |
|---|---|---|
| Tapping term too short | Mod-tap misfires | Increase tapping term |
| Combo conflicts | Accidental combos | Change combo keys or term |
| Tap dance lag | Delayed output | Simplify tap dance or adjust term |
7.2 Debugging Strategies
- Enable QMK debug: inspect key events and timing.
- Use a typing test: measure real-world misfires.
7.3 Performance Traps
Too many tap-dance actions can add latency. Keep them minimal and meaningful.
8. Extensions & Challenges
8.1 Beginner Extensions
- Add a layer toggle LED or OLED indicator.
- Add a macro key on the symbol layer.
8.2 Intermediate Extensions
- Add per-key tapping term overrides.
- Add combos that output multi-key sequences.
8.3 Advanced Extensions
- Create a chorded input layer for shortcuts.
- Implement custom tap dance state machines.
9. Real-World Connections
9.1 Industry Applications
- Compact ergonomic keyboards used by developers.
- Specialized input devices for gaming and design.
9.2 Related Open Source Projects
- QMK community keymaps with home-row mods.
- Miryoku layout configurations.
9.3 Interview Relevance
- Demonstrates ability to reason about timing and UX in embedded systems.
10. Resources
10.1 Essential Reading
- QMK docs: layers, mod-tap, combos.
- “Making Embedded Systems” Ch. 4-6.
10.2 Video Resources
- Home-row mod tutorials.
10.3 Tools & Documentation
- QMK CLI and QMK Configurator.
10.4 Related Projects in This Series
11. Self-Assessment Checklist
11.1 Understanding
- I can explain how the layer stack resolves keys.
- I can describe how mod-tap timing works.
11.2 Implementation
- Three layers work as intended.
- Combos and tap dance are reliable.
11.3 Growth
- I can tune timing values based on typing behavior.
12. Submission / Completion Criteria
Minimum Viable Completion:
- Three layers defined.
- At least one mod-tap and one combo.
Full Completion:
- Two tap-dance actions.
- Timing tuned with real typing tests.
Excellence (Going Above & Beyond):
- Per-key tuning and documented layout rationale.