Authoring Layouts
In the Playground, choose New layout to start from a blank layout or duplicate an existing layout to use it as a starting point. The layout editor keeps the TOML source, diagnostics, preview, and analysis together. Resolve any diagnostics before saving; use Save As when you want the edited layout to remain separate from the document you opened.
Every layout source starts with schema_version = 1 and includes name, author, languages, and year. List one or more distinct supported language tags; the currently accepted tag is en-US. This metadata describes the layout and does not change its bindings or analysis behavior. Optional metadata includes author_link and links.
languages = ["en-US"]Board and key grid
Section titled “Board and key grid”The board supplies slots, coordinates, finger assignments, and home positions. Use board = "ansi" for the built-in ANSI board. Ortholinear and column-staggered boards use the board builder’s row and column counts around the home positions.
The key grid assigns stable key IDs to those slots. Tokens are separated by whitespace inside multiline strings. An ID can be a number or punctuation such as $ or ,; it does not have to look like a programming-language identifier. Each retained key ID must be unique within the layout. Key IDs and chord names share one namespace, so a chord cannot reuse a retained key ID.
Use skip for unused board slots. A skipped slot has no key and does not appear in any layer. Built-ins model output-producing keys and their activators rather than every key found on a physical keyboard.
Our built-in naming convention favors lowercase semantic IDs: yl and yr for two Y keys, lsh and rsh for shift activators, and spc for space. This convention is not a restriction on user-authored key names.
Compact layers
Section titled “Compact layers”A literal default makes a key output its ID, with a few overrides for duplicate outputs or activators:
[layers.base]default = "literal"bindings = { yl = "y", yr = "y", lsh = { type = "momentary", layer = "shift" }, rsh = { type = "momentary", layer = "shift" } }
[layers.shift]default = "transparent"case_map = "us-english"spc resolves to a space. none is a no-output binding, and ____ is transparent. Spell out "none" and "transparent" in the default field. To output reserved text literally, use an explicit object such as { type = "output", value = "none" }.
A case map transforms output supplied by a transparent mapped layer. "us-english" supplies letter pairs such as aA and US symbol pairs such as 2@ and /?. An explicit map is an array of two-grapheme strings, for example case_map = ["aA", "bB", "2@"]. Pair endpoints must be distinct and cannot occur in multiple pairs.
The source defaults to base, regardless of other active layers. Use source_layer when a second alphabet has its own mapped Shift:
[layers.a2]default = "literal"bindings = { a = "x" }
[layers.shift2]default = "transparent"case_map = "us-english"source_layer = "a2"The named source can appear before or after the mapped destination and does not need to be active. It must exist, differ from the destination, and have no case map of its own. A transparent destination reads the source layer’s authored behavior. Source none and a missing combo are opaque. If the source is transparent, ordinary lookup continues below the destination without applying that destination’s map. An explicit destination binding bypasses the source and map.
Mapping applies to every grapheme produced by the source behavior, including contextual defaults and rule outputs. A held or locked mapped layer maps the complete output; one-shot maps its first grapheme. A rule’s key context is unchanged by case mapping: a base after_keys = ["a"] can match mapped Shift+A, while { press = "a", layer = "shift" } requires that precise mapped-layer action.
See the Layout TOML Reference for field forms, validation constraints, and the exact transparent-key and transparent-combo boundaries.
For a sparse symbol layer, use a none default and explicit key bindings:
[layers.symbols]default = "none"bindings = { q = "!", w = "@", e = "#" }Built-in symbol layers use none for unused keys rather than falling through to another layer.
Put inferred inside a layer table to record physical key positions absent from the original source on that layer:
[layers.base]default = "literal"inferred = ["minus"]Every listed ID must exist in the physical key grid. The marker is layer-local metadata and does not change behavior. It does not propagate through transparent lookup or case mapping, so place inferred in each affected layer and list that layer’s inferred positions explicitly.
Inferred keys use a subdued background in the key grid and retain their finger-colored outlines. Inspect also identifies them in Notes. A shared note lists all affected keys together; a key with several notes appears beside each relevant note.
Layer grids and aliases
Section titled “Layer grids and aliases”A layer can instead provide a whitespace-delimited grid. Its rows follow the key grid after removing skip slots and empty rows. If the key grid has named groups, the layer supplies matching groups.
Tokens in a layer grid are outputs or aliases local to that layer’s bindings table. They do not have to be the stable key IDs. In a layer with a default, however, bindings is keyed by stable key ID. A layer uses either a default or a grid, not both.
Key sets, activations, and chords
Section titled “Key sets, activations, and chords”Named key sets live at the top level. An activation’s anchors can reference a set name or an inline list of stable key IDs:
[key_sets]right_letters = ["j", "k", "l"]
[layers.base]default = "literal"bindings = { lsh = { type = "momentary", layer = "shift", anchors = "right_letters" } }Key sets can select keys directly, by hand or finger, or by behavior on one layer. They can also reuse an earlier set and combine expressions with union, intersection, and difference:
[key_sets]right_side = { hand = "right", except = ["spc"] }index_fingers = { union = [{ finger = "left_index" }, { finger = "right_index" }] }typing_keys = { layer = "base" }letters = { diff = [{ ref = "typing_keys" }, ["lsh", "rsh", "spc"]] }left_letters = { intersect = [{ ref = "letters" }, { hand = "left" }] }letters_and_thumbs = { union = [{ ref = "letters" }, { union = [{ finger = "left_thumb" }, { finger = "right_thumb" }] }] }A ref may name only an earlier declaration. Layer selectors may name a later layer, and include physical keys with authored behavior on that layer other than none or transparent. They do not inherit lower-layer outputs or add combo members. Nested empty literals, unions, and intersections are allowed, but the complete expression for every named set must contain at least one key. Use diff for exclusions; there is no selector for a home row or an absolute complement.
Character classes and equivalence groups are separate top-level declarations for output-pattern rules:
[character_classes]vowels = ["a", "e", "i", "o", "u"]accented = ["é", "ê"]front_vowels = { intersect = [{ ref = "vowels" }, ["e", "i"]] }
[character_equivalences]e_variants = ["e", "é", "ê"]Class expressions use ref, union, intersect, and diff, with backward references. Nested expressions may be empty, but each complete named class must contain a character. Equivalence groups are disjoint, and every member must also appear in a character class, a layout output, or a literal output-condition slot. They are used only when an output-pattern slot explicitly names { ref = "class" } or a member such as { equivalent = "e" }; the member selects its group. Literals remain literal, and matching never changes the output.
Omitting anchors leaves the activation unrestricted. Anchor sets constrain entry into the activated layer; they do not constrain release. Every member of an anchor chord must belong to the set. Built-in shifts use opposite-hand sets, excluding keys whose base output is space. These constraints define accepted analysis paths; Try Out still lets a manually pressed wrong-hand Shift activate.
Chords name two or more stable keys, with their behavior assigned separately in each layer’s combos table:
[chords]jc = ["j", "c"]
[layers.base]default = "literal"combos = { jc = ";" }The authoring schema also supports one-shot activation, repeat, skip-repeat, contextual magic, and tap-hold bindings. A tap-hold’s hold branch may activate a momentary layer, but cannot activate a one-shot layer. Layer names are references, not hidden display instructions: naming a layer shift does not assign it a special icon.
Alternate fingerings
Section titled “Alternate fingerings”[[alt_fingerings]] adds an exact native event fragment that analysis may choose as another path. The ordinary board fingering remains available; typing-path and cost resolution decide which path is used. Matching uses physical key or chord identity, not output text or a layer-specific binding. A layout can declare at most 4096 alternate fingerings, each with one to eight events.
This fragment changes the finger used for two existing physical keys:
# `a` and `b` must already be retained physical keys with bindings.[[alt_fingerings]]events = [ { press = "a", fingers = { a = "RM" } }, { press = "b", fingers = { b = "RI" } },]A chord press assigns fingers by constituent physical key ID, rather than chord order or chord name:
[chords]th = ["t", "h"]
[layers.base]default = "literal"combos = { th = "x" }
[[alt_fingerings]]events = [{ press = "th", fingers = { t = "LM" } }]Omitted chord members use their board-assigned fingers. Several members may use the same finger. Finger codes are LP, LR, LM, LI, LT, RT, RI, RM, RR, and RP. Across the declaration, at least one press assignment must actually differ from the board finger.
Set hold = true to select the text-producing hold branch of an existing tap-hold binding:
[layers.base]default = "literal"bindings = { qu = { type = "tap_hold", tap = { type = "output", value = "qu" }, hold = { type = "output", value = "q" } } }
[[alt_fingerings]]events = [{ press = "qu", hold = true, fingers = { qu = "RI" } }]hold defaults to false. This branch choice is separate from an acquired hold, which keeps a key or chord held across later events:
[layers.base]default = "literal"bindings = { lsh = { type = "momentary", layer = "shift" } }
[layers.shift]default = "transparent"
[[alt_fingerings]]events = [ { acquire = ["lsh"] }, { press = "a", fingers = { a = "RM" }, sustained = ["lsh"] }, { release = ["lsh"] },]Each event is exactly one of press, acquire, or release. acquire and release take one to nine unique key or chord names and do not accept finger overrides. sustained names the exact triggers held unchanged across an event. Omission means none are held, not any held set. A changed trigger cannot also be sustained. A press accepts at most nine sustained triggers; for acquire or release, changed and sustained triggers together cannot exceed nine.
The sequence is a fragment, so its first event may begin with triggers held and its last may leave triggers held. Between adjacent events, the earlier post-event held set and later pre-event held set must agree exactly.
Contextual rules
Section titled “Contextual rules”Keep ordinary defaults in bindings and combos, then add conditional changes to the layer’s rules array:
[layers.base]default = "literal"bindings = { magic = "x", again = { type = "repeat", repeat_kind = "output" } }rules = [ { after_keys = ["t"], set = { magic = "h" } }, { after_keys = ["x", {}], swap = ["w", "e"] }, { after_output = "qu", case_map = "shift", cycle = ["q", "y", "u"] },]Every rule has one context and one operation. after_keys contains one or two actions immediately before the target, ordered oldest to newest. {} is a wildcard, but a context made entirely of wildcards is invalid. A bare key belongs to the containing layer. Use { press = "a", layer = "shift" } for a precise layer action or { keyset = "left", layer = "base" } for a named key set.
after_output matches a produced-text suffix without deleting that text. A compact string remains a literal multi-grapheme suffix. An array consumes one grapheme cluster per slot, so ["q", { ref = "vowels" }, { equivalent = "e" }] selects a literal q, a vowel, and any member of the group containing e. Add word_start = true to anchor either form before its first slot at sequence start or immediately after a space; punctuation is not a separator. If the layout declares one case map, it is selected automatically for case-aware matching. With several maps, case_map names the layer whose map applies; otherwise no automatic case equivalence is added, while explicit classes and equivalence slots still apply. An explicit equivalence slot still uses its declared group when case_sensitive = true.
set accepts a string shorthand or a structured outcome. swap and cycle
route targets through their original defaults: [a, b, c] makes a produce
b’s default, b produce c’s, and c produce a’s. These operations act
on the defaults that existed before the rules, not on another rule’s result.
rules = [ { after_keys = ["a"], set = { magic = { type = "output", value = "qu", delete_previous_output = true } } }, { after_keys = ["t"], set = { again = { type = "repeat", repeat_kind = "keystroke" } } }, { after_keys = ["x"], set = { magic = { type = "none" } } },]An output outcome with delete_previous_output = true replaces the entire output of the immediately preceding ordinary press. The immediate after_keys slot must be named; a key set qualifies, while a wildcard in that slot does not. It does not delete one character from a multi-grapheme output, and it cannot be triggered by after_output or a default. A completed deletion cannot be deleted or output-repeated again. Repeat and skip-repeat use repeat_kind = "output" or "keystroke"; the latter runs the earlier key or chord behavior again. none completes a no-output action without advancing completed-output history or consuming a one-shot. blocked refuses execution.
Add branch = "tap" when a target is a tap-hold key or chord. Tap branches may use none or blocked; contextual hold branches are invalid. Add on = "combos" when the targets are chord names; bindings are the default. Rule targets must have ordinary output, none, blocked, repeat, or skip-repeat defaults, or an unanchored one-shot default. Momentary, lock, anchored activation, transparent, and contextual defaults are not valid targets.
For one target and tap branch, the more-specific valid context wins; authored order does not make the first broad match win. prefer = "after_output" is the default; prefer = "after_keys" reverses that priority. case_sensitive = false is the default and permits automatic case-equivalent matching without changing output; true requires exact case, while explicit mapped-layer contexts remain exact. All rules for one target and tap branch must use the same preference and case-sensitivity setting. default_when_shadowed = "miss" offers the default only as a separate miss when the matching winner is not a blocked outcome; default_when_shadowed = "blocked" suppresses that shadowed fallback. Neither setting turns the selected outcome into blocked, and there is no layer fallthrough. All rules for one target and tap branch must use the same shadow policy.
Costs and validation
Section titled “Costs and validation”Each layout supplies its complete cost model. These expressions price one keystroke, two adjacent keystrokes, and three adjacent keystrokes respectively:
[cost]uni = "5"bi = "akp.sf.count * 5"tri = "ends.akp.sf.count"The example charges 5 per keystroke, 5 per qualifying adjacent same-finger key
pair, and 1 per qualifying pair between trigram endpoints. akp abbreviates
active_key_pairs: keys actively involved in movement within the window. It
includes first-event press, release, or transient sustain and second-event
press, acquire, or transient sustain, while excluding keys held continuously
throughout the window. ends abbreviates endpoints.
Every uni result must be a positive integer. bi and tri results must be
nonnegative integers; use "0" to disable either contribution. These expressions
use the same predicates and arithmetic as metric queries, but operate on one
candidate n-gram before a typing path is chosen. Corpus frequencies and weights
are therefore unavailable. There is no automatic scaling or additional default
penalty, and an undefined result must be handled explicitly before it can be a
cost.
Use the editor diagnostics and preview while you work. When the source is valid, save the current document or use Save As to create an independent saved layout. The Layout TOML Reference lists every accepted field, form, default, and validation rule.