Skip to content

Config guide

Your config is one YAML file: ~/.config/keychord/keychord.yml. Menu bar › Edit config opens it; saving reloads it. A mistake never breaks a working setup: keychord keeps the last good config and shows the error with its line number.

Check it any time from Terminal:

Terminal window
keychord check # or: keychord check --json
chords:
- keys: sig
text: |
Ada Lovelace
Analytical Engines Ltd
  • 1–8 characters, a-z and 0-9. Short and mnemonic: sig, addr, ll.
  • Matched exactly on release, so t and tt coexist.
  • Keys must be unique; a duplicate is a config error.
  • apps: [ghostty, code] limits a chord to those apps (name or bundle id).

Multiline text works inline with |. For longer text, put it in its own file and point the chord at it with file:. Both work; use whichever is easier to edit.

chords:
- keys: sig # inline
text: |
Ada Lovelace
Analytical Engines Ltd
- keys: nda # from a file
file: snippets/nda.md
  • The path is relative to the YAML file that declares the chord; ~ works too.
  • The file is read when you fire the chord (cached), so edits apply at once, with no reload.
  • The kind comes from the extension: .md pastes formatted, .csv/.tsv as a table, .svg/.png as a picture, .mmd as a diagram, anything else as text. Add kind: to override.
  • ${vars}, {tab} and ${secret:NAME} work inside the file. A file with a secret is typed, never pasted, like any secret chord.
  • The limit is 1 MB. One trailing newline is dropped, so your editor’s last newline doesn’t press Enter.
  • The path is followed as written (absolute paths, ../ and symlinks too).

Define a value once at the top, use it anywhere as ${name}:

vars:
me: Ada Lovelace
work_email: ada@example.com
sign: "${me} · ${work_email}" # vars can use vars
chords:
- keys: em
text: "${work_email}"

Lookup order for ${name}: vars, then the environment. Cycles are a load error.

${GITHUB_USER} reads an environment variable when the chord fires. A menu-bar app doesn’t see your shell’s ~/.zshrc, so keychord also reads ~/.config/keychord/env (NAME=value lines). A missing variable aborts the chord: nothing is typed and the menu bar tells you.

Built-ins: ${date:%Y-%m-%d} (any strftime format), ${clipboard}, ${input:Label} (asks in a small box when fired). $${ types a literal ${.

Inside text, braces are key presses:

write presses
{tab} {enter} {esc} Tab, Return, Escape
{up} {down} {left} {right} arrows
{backspace} {delete} {home} {end} editing keys
{delay:80} waits 80 ms (slow web forms, autocomplete)

Text is typed as real keystrokes, so web forms (React and friends) see every field.

Split your config into files. Later files add chords and override vars with the same name:

include:
- packs/presenter.yml
- ~/work/team-chords.yml

Passwords and tokens never go in the YAML. They live in an encrypted vault, secrets.kcv, next to your config (XChaCha20-Poly1305; the key comes from your vault passphrase via Argon2id). The file shows names, never values, so it is safe to sync.

Terminal window
keychord secret set WORK_PW # prompts, hidden; stores in the vault
- keys: lg
text: "${work_email}{tab}${secret:WORK_PW}{enter}"

You unlock the vault once per device; the key is then kept in the macOS Keychain. Secret values are always typed, never pasted, never logged, and shown as •••• in the hint.

text is typed. The other kinds are pasted as rich content, then your clipboard is put back.

chords:
- keys: wsig # Word / Outlook / Mail: real bold and italics
md: "**Ada Lovelace** · Engines Ltd\n_${work_email}_"
- keys: xt # Excel: real cells. Word / PowerPoint: a real table
table: |
Name,Team,Score
Ada,Core,9
Linus,Mac,8
- keys: flow # a picture of a mermaid flowchart
diagram: mermaid
source: |
flowchart LR
Idea --> Build --> Ship
- keys: ok # one shape with a label
shape: circle # circle | rect | rounded | diamond | arrow
text: Done
color: "#e4572e"
fill: lightyellow

Shapes and diagrams paste as a picture everywhere, and as editable shapes in Excalidraw. Diagrams are rendered when you save the config, so firing one is instant.

Shape keys: c r d and 2ca … 9da (built in)

Section titled “Shape keys: c r d and 2ca … 9da (built in)”

No config needed. The keys say the shape:

keys draws
c / r / d one circle / box / diamond
2ca … 9ca that many circles in a row, joined by arrows
2ra … 9ra boxes with arrows
2da … 9da diamonds with arrows

Labels come from your selection, or else what you just typed, split on spaces, commas and lines. Select login api, press super+2ra: you get [login] → [api]. With no text you get blank shapes. The text is replaced by the picture; ⌘Z brings it back. In a terminal the chord is refused. Theme them with shape_color: and shape_fill: under vars:. A chord of your own with the same keys wins over the built-in.

Act on text that’s already there:

chords:
- keys: b
text: "**${selection}**" # select "urgent" → **urgent**
- keys: tk
text: "- [ ] ${last}" # type "fix login" → - [ ] fix login
  • ${selection}: the selected text. If nothing is selected, what you just typed.
  • ${last}: what you typed on the current line since the last Enter, Tab, click or app switch (200 characters at most).
  • The original text is deleted and replaced; undo brings it back.
  • Neither exists → the chord does nothing.

Privacy: the ${last} buffer lives only in memory, is never logged, is cleared when you switch apps, and is off in password fields. (macOS only for now.)

A chord can run an action through the bundled aos tool instead of typing:

chords:
- keys: vs
run: aos open "Visual Studio Code"
- keys: lh
run: aos window snap left
- keys: gh
run: aos open https://github.com
- keys: b
run: aos subtitle show "Thank you everyone" --position=center --size=XL --seconds=4 --detach

run: is not a shell: the first word must be aos, and arguments are passed as-is (vars and secrets are filled in per argument). Actions run in the background, so typing chords stay instant. Captions (aos subtitle) need Screen Recording permission.

A pack is a YAML file of chords you include:. Two examples ship in packs/: presenter.yml (on-screen captions) and agent-prompts.yml (a prompt library).

Bring your existing snippets:

Terminal window
keychord import espanso ~/Library/Application\ Support/espanso/match/base.yml
keychord import csv my-snippets.csv --file packs/mine.yml
keychord pack new work # an empty packs/work.yml, already included

Formats: CSV, TSV, JSON, YAML, Markdown, Espanso. Or use menu bar › Import…. Import writes a new pack file and adds it to include:; it never touches your own file. Text that looks like a password or token is refused — put it in the vault instead.

write gets
${name} a var, else environment variable
${secret:NAME} a value from the vault (typed, never pasted)
${date:%Y-%m-%d} · ${clipboard} built-ins
${selection} · ${last} selected / just-typed text, replaced
${input:Label} asks when fired
{tab} {enter} {delay:80} keys
$${ a literal ${