Welcome to my Emacs blog
The TAB Key in Org Mode, Reimagined
I don't know about you, but when I'm reading something in an Org file and spot a link I want to follow, I instinctively press TAB
to jump to it—just like I would in an Info or Help buffer. Using TAB
for such field navigation is a common pattern across many applications, including Emacs. It’s also nicely symmetric with Shift-TAB (S-TAB
), which typically navigates backward. But in Org mode, TAB
triggers local visibility cycling: folding and unfolding text under the current headline. S-TAB
cycles visibility globally, folding and unfolding all the headlines. (Granted, if you don’t use Info or navigate Help buffers with TAB
, you might not miss that behavior in Org mode.)
See, we have this dichotomy in Org mode: it's both an authoring tool and a task/notes manager. For document authoring, Org markup serves as a source format that's later exported for publishing. In this context, visibility cycling is essential for managing structure and reducing distractions while writing. As a task and notes manager, Org is used to track notes, TODO lists, schedules, and data—content that's often read in place and never exported. Visibility cycling still helps, but it's generally less critical than in authoring mode.
This reading workflow within Org files makes me long for features found in more reading-focused modes. Sure, I don’t treat my Org files as read-only; reading and editing are fluidly intertwined. Still, when I'm focused on reading, I want the TAB
key to handle navigation, not headline visibility cycling. And I don't want to switch to another mode like View mode just to get a better reading experience.
It's well known that the TAB
key is heavily overloaded in Emacs, especially in Org mode. Depending on context and configuration, it can perform one of four types of actions: line indentation, candidate completion (during editing), or field navigation and visibility cycling (during reading). As mentioned earlier, TAB
is commonly used for field navigation in Info and Help modes. But note that even Org mode uses it this way within tables. Its association with visibility cycling was unique to Org mode until recently, when it was made an option in Outline mode too.
Personally, I want to move in the opposite direction: removing visibility cycling from the list of TAB
-triggered actions. Three types of behavior are already plenty. I'd rather assign visibility control to a more complex keybinding and prioritize field navigation instead. I'm not a big fan of cycling in general (see my previous blog post), and would prefer to jump directly to specific folding levels. I also value consistency in keybindings, so unifying TAB
behavior across modes is important to me.
TAB
: indentation and completion when editing; field navigation when reading
I decided to give it a try and remap TAB
in Org mode to primarily perform field navigation. What exactly is considered a “field” is largely up to the user. In general, it should be a structural element in a file where a non-trivial action can be performed, making it useful to have an easy way to jump between them. For my setup, I chose to treat only links and headlines as fields, similar to how Info handles navigation. Of course, others might include property drawers, code blocks, custom buttons, or other interactive elements. I wouldn't overdo it though—too many fields and TAB
navigation loses its utility.
I remapped TAB
in Org mode to navigate to the next visible heading or link, and S-TAB
to move to the previous one. Headlines and links inside folded sections are skipped. For visibility cycling, I now rely on Org Speed Keys (a built in feature of Org mode).
Speed Keys let you trigger commands with a single keystroke when the point is at the beginning of a headline. They’re off by default but incredibly handy once enabled. A number of keys are predefined out of the box; for example, c
is already mapped to org-cycle
, which is what TAB
normally does in Org mode.
I’ve had Speed Keys enabled for ages (mainly using them for forward/backward headline navigation), but I had never used c
for visibility cycling—until now. And it gets even better: the combination of TAB
/ S-TAB
to jump between fields, followed by a speed key at the headline, turns out to be quite powerful.
What about the other actions TAB
usually performs in Org files? For now, I rely on M-x org-cycle
when needed. The org-cycle
command is quite sophisticated and can fall back to other TAB
behaviors like indentation when appropriate. That said, I’ve been using my custom TAB
/ S-TAB
bindings for months now and haven’t run into any situations where I missed the default behavior.
Want to give it a try? Here’s the code you can drop into your init.el
:
(defun /org-next-visible-heading-or-link (&optional arg) "Move to the next visible heading or link, whichever comes first. With prefix ARG and the point on a heading(link): jump over subsequent headings(links) to the next link(heading), respectively. This is useful to skip over a long series of consecutive headings(links)." (interactive "P") (let ((next-heading (save-excursion (org-next-visible-heading 1) (when (org-at-heading-p) (point)))) (next-link (save-excursion (when (/org-next-visible-link) (point))))) (when arg (if (and (org-at-heading-p) next-link) (setq next-heading nil) (if (and (looking-at org-link-any-re) next-heading) (setq next-link nil)))) (cond ((and next-heading next-link) (goto-char (min next-heading next-link))) (next-heading (goto-char next-heading)) (next-link (goto-char next-link))))) (defun /org-previous-visible-heading-or-link (&optional arg) "Move to the previous visible heading or link, whichever comes first. With prefix ARG and the point on a heading(link): jump over subsequent headings(links) to the previous link(heading), respectively. This is useful to skip over a long series of consecutive headings(links)." (interactive "P") (let ((prev-heading (save-excursion (org-previous-visible-heading 1) (when (org-at-heading-p) (point)))) (prev-link (save-excursion (when (/org-next-visible-link t) (point))))) (when arg (if (and (org-at-heading-p) prev-link) (setq prev-heading nil) (if (and (looking-at org-link-any-re) prev-heading) (setq prev-link nil)))) (cond ((and prev-heading prev-link) (goto-char (max prev-heading prev-link))) (prev-heading (goto-char prev-heading)) (prev-link (goto-char prev-link))))) ;; Adapted from org-next-link to only consider visible links (defun /org-next-visible-link (&optional search-backward) "Move forward to the next visible link. When SEARCH-BACKWARD is non-nil, move backward." (interactive) (let ((pos (point)) (search-fun (if search-backward #'re-search-backward #'re-search-forward))) ;; Tweak initial position: make sure we do not match current link. (cond ((and (not search-backward) (looking-at org-link-any-re)) (goto-char (match-end 0))) (search-backward (pcase (org-in-regexp org-link-any-re nil t) (`(,beg . ,_) (goto-char beg))))) (catch :found (while (funcall search-fun org-link-any-re nil t) (let ((folded (org-invisible-p nil t))) (when (or (not folded) (eq folded 'org-link)) (let ((context (save-excursion (unless search-backward (forward-char -1)) (org-element-context)))) (pcase (org-element-lineage context '(link) t) (link (goto-char (org-element-property :begin link)) (throw :found t))))))) (goto-char pos) ;; No further link found nil))) (defun /org-shifttab (&optional arg) "Move to the previous visible heading or link. If already at a heading, move first to its beginning. When inside a table, move to the previous field." (interactive "P") (cond ((org-at-table-p) (call-interactively #'org-table-previous-field)) ((and (not (bolp)) (org-at-heading-p)) (beginning-of-line)) (t (call-interactively #'/org-previous-visible-heading-or-link)))) (defun /org-tab (&optional arg) "Move to the next visible heading or link. When inside a table, re-align the table and move to the next field." (interactive) (cond ((org-at-table-p) (org-table-justify-field-maybe) (call-interactively #'org-table-next-field)) (t (call-interactively #'/org-next-visible-heading-or-link)))) (use-package org :config ;; RET should follow link when possible (moves to next field in tables) (setq org-return-follows-link t) ;; must be at the beginning of a headline to use it; ? for help (setq org-use-speed-commands t) ;; Customize some bindings (define-key org-mode-map (kbd "<tab>") #'/org-tab) (define-key org-mode-map (kbd "<backtab>") #'/org-shifttab) ;; Customize speed keys: modifying operations must be upper case (custom-set-variables '(org-speed-commands '(("Outline Navigation and Visibility") ("n" . (org-speed-move-safe 'org-next-visible-heading)) ("p" . (org-speed-move-safe 'org-previous-visible-heading)) ("f" . (org-speed-move-safe 'org-forward-heading-same-level)) ("b" . (org-speed-move-safe 'org-backward-heading-same-level)) ("u" . (org-speed-move-safe 'outline-up-heading)) ("j" . org-goto) ("c" . org-cycle) ("C" . org-shifttab) (" " . org-display-outline-path) ("s" . org-toggle-narrow-to-subtree) ("Editing") ("I" . (progn (forward-char 1) (call-interactively 'org-insert-heading-respect-content))) ("^" . org-sort) ("W" . org-refile) ("@" . org-mark-subtree) ("T" . org-todo) (":" . org-set-tags-command) ("Misc") ("?" . org-speed-command-help)))) )
A few comments about the code, for those interested:
- This is more of a proof-of-concept than optimized code ready for upstreaming.
- My
/org-next-visible-link
is a simplified version of the built-inorg-next-link
, tailored to the specific cases I care about. Honestly, I was surprised thatorg-next-link
doesn’t already do what I need. It jumps to the next link even if it’s inside a folded section, causing it to unfold. I have a hard time imagining why would anyone need that. - In
/org-tab
and/org-shifttab
, I preserved the default behavior oforg-cycle
within a table: it navigates between table fields. - I’ve also customized
org-speed-commands
to only bind editing actions to keys that require the Shift modifier. I like keeping lowercase keys reserved for non-destructive commands. As a next step, I may remap Space and Shift-Space to scroll the buffer. That would bring me even closer to a more consistent reading experience.
Enjoy the malleability of Emacs and the freedom it gives you!
Discuss this post on Reddit.
Speed Dialing Your Favorite Files
I may be dating myself, but I vividly remember setting up speed dials for my most frequently called numbers on my AT&T landline phone. In the early '90s, you could store a phone number in a numbered memory slot (referred to as "programming") and later dial your grandma, for example, by pressing SPD+2
. Retro is in—so if you're too young to remember that and want to know more, just ask your favorite LLM chatbot to fill you in.
Speed-dialing as a user experience concept is widespread, although we don't normally call it that anymore. It is implemented as a feature that I use many times a day in my web browser. I use Safari on a Mac and typically keep many tabs open. I pin the first few to frequently visited URLs, like https://planet.emacslife.com. I can quickly switch to one of them using the keyboard shortcut CMD+1..9
, always knowing which website I'll get. Other browsers offer similar functionality, though they may use different shortcuts, like CTRL+1..9
.
The two apps I use most often on my Mac are Safari and Emacs, and I wondered, “Why don't I have a similar speed-dialing feature in Emacs?” It would be incredibly useful to switch instantly to my important files for reading or jotting down notes. I also like to optimize my keybindings, and consistency plays a big role in that—whether it’s adopting Emacs keybindings elsewhere or bringing external shortcuts into Emacs. It would be great to use the same CMD+1..9
shortcut to recreate this functionality in Emacs.
But doesn’t Emacs already have Tab Bar and Tab Line features? Maybe one of them (I can never remember which is which) could be adapted or enhanced to do what I want. Note, however, that I’m talking about speed dialing files, not tabs. I don’t want to select a tab or cycle through them—I want to jump directly to a specific buffer that’s visiting a specific file. Tabs feel a bit unnatural in Emacs; they make sense in browsers, but in Emacs, we typically work with buffers by name.
Direct addressing—using a name or a short index—is both powerful and highly efficient. Cycling is the least efficient method (looking at you, CMD+TAB
). Completion is a middle ground—it requires extra keystrokes compared to direct addressing and is less predictable when the candidate list changes (in how many characters must be typed to get a single match). However, it’s essential when the list of candidates is long.
Direct Addressing > Completion > Cycling
In general, I prefer direct addressing whenever possible, completion when necessary, and cycling only as a last resort. Emacs' built-in bookmark-jump
falls into the completion category. It would be my next choice if the number of my frequently used files was above ten.
Another reason I avoid using tabs for this in Emacs is that I don’t want to waste screen real estate on a tab bar if I don’t have to. My speed dials are mostly static—I may change them occasionally, but if I assign 1
to school.org
and 2
to house.org
, I want to stick with that. Thanks to muscle memory, I don’t need to see the list in front of me at all times. Plus, accidentally switching to the wrong frequently used file isn’t a big deal—I can quickly flip through a few of them to find what I need.
The beauty of Emacs is that I can create a Safari-like speed-dial experience with just a couple of elisp expressions in my init.el
file.
;; ;; Speed Dialing Favorite Files ;; (defvar /speed-dial-list '(("⓵-todo" . "~/todo.org") ("⓶-emacs" . "~/para/areas/emacs.org") ("⓷-family" . "~/para/areas/family.org") ("⓸-house" . "~/para/areas/house.org") ("⓹-garden" . "~/para/areas/garden.org") ("⓺-42" . "~/para/areas/42.org") ("⓻-init" . "~/.emacs.d/init.el") ("⓼-O1" . "~/para/projects/proj1.org") ("⓽-O2" . "~/para/projects/proj2.org") ("⓾-O3" . "~/para/projects/proj3.org")) "List of speed-dial entries as (LABEL . FILENAME).") ;; Global keybindings for speed dialing using '<Super>' + digit (let ((i 1)) (dolist (entry /speed-dial-list) (keymap-global-set (format "s-%d" (mod i 10)) `(lambda() (interactive) (find-file-existing ,(cdr entry)))) (setq i (1+ i))))
As you can see, I use the <Super>
key modifier to define bindings that match my Safari shortcuts, CMD+1..9
. Note a little trick: using the mod function inside keymap-global-set
to get s-0
to invoke the tenth speed-dial entry.
Currently, the speed-dial bindings simply call the find-file-existing
function to switch to the corresponding buffer, opening the file if needed. But you can customize this further by using your own function for tailored behavior.
For example, you might use repeated presses of the same CMD+0..9
to change folding in an Org buffer, jump to a predefined heading, switch to a related buffer, or perform other context-specific actions.

Rather than visualizing the speed-dial entries as tabs, I found a way to display them without taking up valuable screen real estate. I simply splice the speed-dial labels into the Emacs frame title bar, which I don't really use for anything else. By default, it shows the current buffer name, but that information is also displayed in the mode line, which is where my eyes naturally go.
;; Inject my speed-dial list into the frame title (setq frame-title-format (concat (mapconcat #'car /speed-dial-list " ") " - %b"))
For my needs, displaying speed-dial entries in the Emacs frame title, followed by the current buffer name, works perfectly. My main Emacs frame is always wide enough to accommodate it. If I couldn’t use the frame title, I’d probably just open my init.el
whenever I needed to check which speed-dial number maps to which file. But you might find an even better approach that works for you.
Enjoy the malleability of Emacs and the freedom it gives you!
Discuss this post on Reddit.