Synopsis
I will be going over my personal Emacs’ text completion (e.g. company-mode
and its accessories) configuration, which includes the following packages:
- Corfu by Minad — simpler alternative to
company-mode
- Kind-icon by jdtsmith (u/JDRiverRun)— add icons to
corfu
popup (analog tocompany-box-icons
) - Corfu-doc by Galeo — add documentation popup for
corfu
candidates (analog tocompany-box-doc
)
Note: I use straight.el
for package management and general.el
to set my keybindings. Both of these packages have integration with use-package
which come in the form of the :straight
and :general
keywords, respectively.
Corfu
What is corfu
? How does it differ from company
?

Figure 1: The default corfu
popup window. The GIF’s framerate is low, which makes corfu
appear less performant here than in actuality.
Corfu
is a text completion (e.g. completion-at-point
, company-mode
) package. In my opinion, since its release, corfu
has not gotten the attention that it deserves. I prefer it to company
for the following reasons:
- It is easier to configure since
corfu
’s internals rely on the built-incompletion-at-point
. This also means that, unlikecompany
,1…- any built-in invocation of
completion-at-point
orcompletion-in-region
leveragescorfu
, - and any
completion-style
(e.g.orderless
) can be used for filtering candidates.
- any built-in invocation of
Corfu
has been more performant (i.e. fewer stutters, smoother cycling of candidates) in my experience.Corfu
can support anycompany
backend viacape-company-to-capf
, provided by the complementarycape
package. Thus, packages likecompany-yasnippet
can be used withcorfu
easily (see the next post in my Text completion and minibuffer UI series for more details and examples.)
Basic
The following is a basic corfu
configuration with my preferred keybinds:
(use-package corfu
:general
(:keymaps 'corfu-map
:states 'insert
"C-n" #'corfu-next
"C-p" #'corfu-previous
"<escape>" #'corfu-quit
"<return>" #'corfu-insert
"M-d" #'corfu-show-documentation
"M-l" #'corfu-show-location)
:config
(corfu-global-mode))
These keybinds have C-n
and C-p
move through the candidates popup, <return>
choose the current candidate, and <escape>
close the corfu
popup. Moreover, I have corfu
’s documentation command (corfu-show-documentation
; shows the available documentation for the current candidate, if any) bound to M-d
, and corfu
’s location command (corfu-show-location
) to go to the location of the current candidate to M-l
.
Corfu
offers a few variables to configure. You can take a look at each docstring to see its function. Here are my preferences:
:custom
(corfu-auto nil) ; Only use `corfu' when calling `completion-at-point' or
; `indent-for-tab-command'
(corfu-auto-prefix 2)
(corfu-auto-delay 0.25)
(corfu-min-width 80)
(corfu-max-width corfu-min-width) ; Always have the same width
(corfu-count 14)
(corfu-scroll-margin 4)
(corfu-cycle nil)
;; `nil' means to ignore `corfu-separator' behavior, that is, use the older
;; `corfu-quit-at-boundary' = nil behavior. Set this to separator if using
;; `corfu-auto' = `t' workflow (in that case, make sure you also set up
;; `corfu-separator' and a keybind for `corfu-insert-separator', which my
;; configuration already has pre-prepared). Necessary for manual corfu usage with
;; orderless, otherwise first component is ignored, unless `corfu-separator'
;; is inserted.
(corfu-quit-at-boundary nil)
(corfu-preselect-first t) ; Preselect first candidate?
;; Other
;; NOTE 2022-02-05: In my actual configuration, I have this variable set to nil
;; since I use `corfu-doc', whose configuration comes later. But if you don't
;; use `corfu-doc', this might be helpful to you.
(corfu-echo-documentation t) ; Show documentation in echo area?
Additionally, the following two variables not under corfu
but related to completion-at-point
will be useful to set:
;; Works with `indent-for-tab-command'. Make sure tab doesn't indent when you
;; want to perform completion
(tab-always-indent 'complete)
(completion-cycle-threshold nil) ; Always show all candidates in popup menu
Working with the recent changes to corfu
On February 7, 2022, corfu
introduced an important change2, particularly the interaction between corfu
and orderless
. You can read more on their README, but, essentially, orderless
now introduces the corfu-insert-separator
command that inserts the corfu-separator
character into the buffer. This character is what delimits orderless
components (see this Reddit comment for a more lengthy description of this behavior.) A corfu
workflow in which corfu-auto
is set to t
leverages this change, for without it users could not realistically use corfu
with a multi-component completion-style
like orderless
.
I do not use this workflow3, but if this behavior is desirable, you can set corfu-separator
to your orderless
separator character to properly delimit orderless
components. I personally use the regular space character. You can make the following modifications to your configuration:
:general
;; NOTE 2022-02-28: `general-override-mode-map' is necessary to override local
;; binds to SPC in evil-mode's insert mode. May not be necessary if you don't use `evil'
(:keymaps 'corfu-map
:states 'insert
"H-SPC" #'corfu-insert-separator ; I have a hyper key so this is an alternative keybind I use sometimes
"SPC" #'corfu-insert-separator)
:custom
(corfu-quit-at-boundary 'separator) ; a non-nil value is necessary
(corfu-separator ?\s) ; Use space
(corfu-quit-no-match 'separator) ; Don't quit if there is `corfu-separator' inserted
(corfu-preview-current 'insert) ; Preview current candidate?
:config
;; NOTE 2022-03-01: This allows for a more evil-esque way to have
;; `corfu-insert-separator' work with space in insert mode without resorting to
;; overriding keybindings with `general-override-mode-map'. See
;; https://github.com/minad/corfu/issues/12#issuecomment-869037519
;; Alternatively, add advice without `general.el':
;; (advice-add 'corfu--setup :after 'evil-normalize-keymaps)
;; (advice-add 'corfu--teardown :after 'evil-normalize-keymaps)
(general-add-advice '(corfu--setup corfu--teardown) :after 'evil-normalize-keymaps)
(evil-make-overriding-map corfu-map)
Further configuration in minibuffers and with lsp
Corfu
’s README provides a way to be able to use corfu
completion in the minibuffer:
;; Enable Corfu more generally for every minibuffer, as long as no other
;; completion UI is active. If you use Mct or Vertico as your main minibuffer
;; completion UI. From
;; https://github.com/minad/corfu#completing-with-corfu-in-the-minibuffer
(defun corfu-enable-always-in-minibuffer ()
"Enable Corfu in the minibuffer if Vertico/Mct are not active."
(unless (or (bound-and-true-p mct--active) ; Useful if I ever use MCT
(bound-and-true-p vertico--input))
(setq-local corfu-auto nil) ; Ensure auto completion is disabled
(corfu-mode 1)))
(add-hook 'minibuffer-setup-hook #'corfu-enable-always-in-minibuffer 1)
This means that in commands like eval-expression
, corfu
is able to be used (via <tab>
) and provide completion.

Figure 2: Using corfu
in the minibuffer prompt for eval-expression
.
Additionally, for lsp-mode
buffers, I have the following lines (this is entirely optional and preferential):
:hook (lsp-completion-mode . kb/corfu-setup-lsp) ; Use corfu for lsp completion
:custom
(lsp-completion-provider :none) ; Use corfu instead the default for lsp completions
:config
;; Setup lsp to use corfu for lsp completion
(defun kb/corfu-setup-lsp ()
"Use orderless completion style with lsp-capf instead of the
default lsp-passthrough."
(setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
'(orderless)))
End product
Putting it together, we end with my actual configuration:
(use-package corfu
:hook (lsp-completion-mode . kb/corfu-setup-lsp) ; Use corfu for lsp completion
:general
(:keymaps 'corfu-map
:states 'insert
"C-n" #'corfu-next
"C-p" #'corfu-previous
"<escape>" #'corfu-quit
"<return>" #'corfu-insert
"H-SPC" #'corfu-insert-separator
;; "SPC" #'corfu-insert-separator ; Use when `corfu-quit-at-boundary' is non-nil
"M-d" #'corfu-show-documentation
"C-g" #'corfu-quit
"M-l" #'corfu-show-location)
:custom
;; Works with `indent-for-tab-command'. Make sure tab doesn't indent when you
;; want to perform completion
(tab-always-indent 'complete)
(completion-cycle-threshold nil) ; Always show candidates in menu
(corfu-auto nil)
(corfu-auto-prefix 2)
(corfu-auto-delay 0.25)
(corfu-min-width 80)
(corfu-max-width corfu-min-width) ; Always have the same width
(corfu-count 14)
(corfu-scroll-margin 4)
(corfu-cycle nil)
;; `nil' means to ignore `corfu-separator' behavior, that is, use the older
;; `corfu-quit-at-boundary' = nil behavior. Set this to separator if using
;; `corfu-auto' = `t' workflow (in that case, make sure you also set up
;; `corfu-separator' and a keybind for `corfu-insert-separator', which my
;; configuration already has pre-prepared). Necessary for manual corfu usage with
;; orderless, otherwise first component is ignored, unless `corfu-separator'
;; is inserted.
(corfu-quit-at-boundary nil)
(corfu-separator ?\s) ; Use space
(corfu-quit-no-match 'separator) ; Don't quit if there is `corfu-separator' inserted
(corfu-preview-current 'insert) ; Preview first candidate. Insert on input if only one
(corfu-preselect-first t) ; Preselect first candidate?
;; Other
(corfu-echo-documentation nil) ; Already use corfu-doc
(lsp-completion-provider :none) ; Use corfu instead for lsp completions
:init
(corfu-global-mode)
:config
;; NOTE 2022-03-01: This allows for a more evil-esque way to have
;; `corfu-insert-separator' work with space in insert mode without resorting to
;; overriding keybindings with `general-override-mode-map'. See
;; https://github.com/minad/corfu/issues/12#issuecomment-869037519
;; Alternatively, add advice without `general.el':
;; (advice-add 'corfu--setup :after 'evil-normalize-keymaps)
;; (advice-add 'corfu--teardown :after 'evil-normalize-keymaps)
(general-add-advice '(corfu--setup corfu--teardown) :after 'evil-normalize-keymaps)
(evil-make-overriding-map corfu-map)
;; Enable Corfu more generally for every minibuffer, as long as no other
;; completion UI is active. If you use Mct or Vertico as your main minibuffer
;; completion UI. From
;; https://github.com/minad/corfu#completing-with-corfu-in-the-minibuffer
(defun corfu-enable-always-in-minibuffer ()
"Enable Corfu in the minibuffer if Vertico/Mct are not active."
(unless (or (bound-and-true-p mct--active) ; Useful if I ever use MCT
(bound-and-true-p vertico--input))
(setq-local corfu-auto nil) ; Ensure auto completion is disabled
(corfu-mode 1)))
(add-hook 'minibuffer-setup-hook #'corfu-enable-always-in-minibuffer 1)
;; Setup lsp to use corfu for lsp completion
(defun kb/corfu-setup-lsp ()
"Use orderless completion style with lsp-capf instead of the
default lsp-passthrough."
(setf (alist-get 'styles (alist-get 'lsp-capf completion-category-defaults))
'(orderless))))
Kind-icon
Kind-icon
is essentially company-box-icons
for corfu
. It adds icons to the left margin of the corfu
popup that represent the ‘function’ (e.g. variable, method, file) of that candidate.

Figure 3: Using corfu-doc
in java-mode
with completion candidates provided by lsp-mode
.
The following is my configuration:
(use-package kind-icon
:after corfu
:custom
(kind-icon-use-icons t)
(kind-icon-default-face 'corfu-default) ; Have background color be the same as `corfu' face background
(kind-icon-blend-background nil) ; Use midpoint color between foreground and background colors ("blended")?
(kind-icon-blend-frac 0.08)
;; NOTE 2022-02-05: `kind-icon' depends `svg-lib' which creates a cache
;; directory that defaults to the `user-emacs-directory'. Here, I change that
;; directory to a location appropriate to `no-littering' conventions, a
;; package which moves directories of other packages to sane locations.
(svg-lib-icons-dir (no-littering-expand-var-file-name "svg-lib/cache/")) ; Change cache dir
:config
(add-to-list 'corfu-margin-formatters #'kind-icon-margin-formatter) ; Enable `kind-icon'
;; Add hook to reset cache so the icon colors match my theme
;; NOTE 2022-02-05: This is a hook which resets the cache whenever I switch
;; the theme using my custom defined command for switching themes. If I don't
;; do this, then the backgound color will remain the same, meaning it will not
;; match the background color corresponding to the current theme. Important
;; since I have a light theme and dark theme I switch between. This has no
;; function unless you use something similar
(add-hook 'kb/themes-hooks #'(lambda () (interactive) (kind-icon-reset-cache))))
Corfu-doc
Corfu-doc
is basically company-quickhelp
for corfu
. It shows the documentation of the selected candidate in an adjacent popup window.

Figure 4: Using corfu-doc
in a corfu
popup. Called from a java
file with completion candidates provided by lsp-mode
.
Here is a sample configuration4:
(use-package corfu-doc
;; NOTE 2022-02-05: At the time of writing, `corfu-doc' is not yet on melpa
:straight (corfu-doc :type git :host github :repo "galeo/corfu-doc")
:after corfu
:hook (corfu-mode . corfu-doc-mode)
:general (:keymaps 'corfu-map
;; This is a manual toggle for the documentation popup.
[remap corfu-show-documentation] #'corfu-doc-toggle ; Remap the default doc command
;; Scroll in the documentation window
"M-n" #'corfu-doc-scroll-up
"M-p" #'corfu-doc-scroll-down)
:custom
(corfu-doc-delay 0.5)
(corfu-doc-max-width 70)
(corfu-doc-max-height 20)
;; NOTE 2022-02-05: I've also set this in the `corfu' use-package to be
;; extra-safe that this is set when corfu-doc is loaded. I do not want
;; documentation shown in both the echo area and in the `corfu-doc' popup.
(corfu-echo-documentation nil))
From my experience, corfu-doc
is perfect for most. However, it should be noted that for those who have a high repeat rate5, rapidly scrolling through candidates causes stuttering and/or lag. This is why I find setting a keybind for corfu-doc-toggle
to be useful.
Changelog
- Added link to Reddit comment in Working with the recent changes to
corfu
section. Also update description of newcorfu
behavior. - Added configuration for using
corfu
in the minibuffer. - Listed more benefits to
corfu
, provided by u/JDRiverRun.
- Added link to Reddit comment in Working with the recent changes to
- Added link to relevant
corfu
GitHub Issue. - Changed
corfu
configuration to avoid setting keybinds ingeneral-override-mode-map
, suggested by a comment to this point. - Added a note and GIF to
corfu-doc
section.
- Added link to relevant
- Update to include new compatibility with
corfu-insert-separator
andcorfu-quit-at-boundary
functionality.
- Update to include new compatibility with
Text completion and minibuffer UI series
- Vertico, Marginalia, All-the-icons-completion, and Orderless
- Corfu, Kind-icon, and Corfu-doc (this post!)
- Cape
Kudos to u/JDRiverRun, the current maintainer of
kind-icon
for providing a few benefits I didn’t originally list. ↩︎This change was initially motivated by jdtsmith (u/JDRiverRun) and is described in this GitHub issue. ↩︎
See this commit. Also see this GitHub issue which reimplemented the old
corfu-quit-at-boundary
functionality alongside the then newcorfu-insert-separator
functionality. ↩︎This is not exactly my configuration, but is quite close to it. ↩︎
I personally use a repeat rate of 37 ms with a delay rate of 225, set by
xset r rate 225 37
. ↩︎