Emacs Config From Scratch, Part 1: Foundations

Welcome to my new series Emacs From Scratch. I’m far from an Emacs expert, so join me in my quest to figure out how to create a useful Emacs setup from nothing1.

In this part, we’ll install Emacs, set up sane defaults, packaging and do some basic UI tweaks to build a solid foundation.

Table of Contents

Install Emacs

On macOS, everyone recommends Emacs Plus. For other systems, check out Doom’s Emacs & dependencies documentation.

We’re running this command:

brew reinstall emacs-plus \
  --with-savchenkovaleriy-big-sur-icon \

And this is what it looks like when we start Emacs for the first time:

A default Emacs window showing outdated (euphemismus) butons and generally looking like it screams to be customized.

Remove UI elements

We want to remove everything but the text. To do so, we first create a file in $HOME/.emacs.d/init.el.

(tool-bar-mode -1)             ; Hide the outdated icons
(scroll-bar-mode -1)           ; Hide the always-visible scrollbar
(setq inhibit-splash-screen t) ; Remove the "Welcome to GNU Emacs" splash screen
(setq use-file-dialog nil)      ; Ask for textual confirmation instead of GUI

If you start Emacs now, you’ll see the GUI elements for a few milliseconds. Let’s fix that by adding these lines to $HOME/.emacs.d/early-init.el2:

(push '(menu-bar-lines . 0) default-frame-alist)
(push '(tool-bar-lines . 0) default-frame-alist)
(push '(vertical-scroll-bars) default-frame-alist)

This is better:

Emacs without GUI elements and the scratch buffer open.

We’ll take care of the default scratch text and the C-h C-a hint down below.

Configure the package manager

We’ll be using straight.el for package management.

This is the installation code from the straight.el README:

(defvar bootstrap-version)
(let ((bootstrap-file
      (or (bound-and-true-p straight-base-dir)
    (bootstrap-version 7))
  (unless (file-exists-p bootstrap-file)
       'silent 'inhibit-cookies)
    (goto-char (point-max))
  (load bootstrap-file nil 'nomessage))

The docs also recommend adding this to our early-init.el to prevent package.el from loading:

(setq package-enable-at-startup nil)

Next we’ll install use-package for tidier specification and better performance:

(straight-use-package 'use-package)

Then we’ll make use-package use straight.el by default and always :defer t for lazy loading:

(setq straight-use-package-by-default t)
(setq use-package-always-defer t)

Set sane defaults

It’s good practice to specify Emacs-specific settings in a use-package block, even though this doesn’t change anything functionally. In the following, I’ll repeat the use-package emacs function, but you can, and probably should, move these all into a single use-package block.

Let’s start without the default scratch message and the text at the bottom saying “For information about GNU Emacs and the GNU system, type C-h C-a”:

(use-package emacs
  (setq initial-scratch-message nil)
  (defun display-startup-echo-area-message ()
    (message "")))

In confirmation dialogs, we want to be able to type y and n instead of having to spell the whole words:

(use-package emacs
  (defalias 'yes-or-no-p 'y-or-n-p))

Make everything use UTF-8:

(use-package emacs
  (set-charset-priority 'unicode)
  (setq locale-coding-system 'utf-8
        coding-system-for-read 'utf-8
        coding-system-for-write 'utf-8)
  (set-terminal-coding-system 'utf-8)
  (set-keyboard-coding-system 'utf-8)
  (set-selection-coding-system 'utf-8)
  (prefer-coding-system 'utf-8)
  (setq default-process-coding-system '(utf-8-unix . utf-8-unix)))

Use spaces, but configure tab-width for modes that use tabs (looking at you, Go):

(use-package emacs
  (setq-default indent-tabs-mode nil)
  (setq-default tab-width 2))

Map the correct keybindings for macOS:

(use-package emacs
	(when (eq system-type 'darwin)
		(setq mac-command-modifier 'super)
		(setq mac-option-modifier 'meta)
		(setq mac-control-modifier 'control)))

Become evil

I’m used to Vim keybindings and want to keep them, so we’ll use evil:

(use-package evil
  :demand ; No lazy loading
  (evil-mode 1))

Set font and theme

We’ll be using the PragmataPro typeface:

(use-package emacs
  (set-face-attribute 'default nil
    :font "PragmataPro Mono Liga"
    :height 160))

For themes, I can recommend the Doom Themes, we’ll be using doom-challenger-deep3:

(use-package doom-themes
  (load-theme 'doom-challenger-deep t))

Finally, we want relative line numbers in prog mode:

(use-package emacs
  (defun ab/enable-line-numbers ()
    "Enable relative line numbers"
    (setq display-line-numbers 'relative))
  (add-hook 'prog-mode-hook #'ab/enable-line-numbers))

Add a nicer modeline

We’ll install doom-modeline:

(use-package doom-modeline
  :ensure t
  :init (doom-modeline-mode 1))

For pretty icons, we need to install nerd-icons as well:

(use-package nerd-icons)

After restarting Emacs, run M-x nerd-icons-install-fonts (Option-x on macOS) to install the icon font.

And we’ll install Nyan Mode, a minor mode which shows a Nyan Cat (which is 12 years old at the point of writing this) in your modeline to indicate position in the open buffer.

(use-package nyan-mode


This is what looks like:

Emacs with relative line numbers, a nice fond and color scheme.

We have sane defaults, we can open a file with :e, navigate around and we have a nice color scheme4 and modeline. Here’s the the final init.el and early-init.el

In part 2, we’ll add a project manager, our own keybindings, the best Git TUI, a handy shortcut to restart Emacs and a ton of small tweaks.

Subscribe to the RSS Feed so you don’t miss the following parts, and let me know if I missed anything foundational!

  1. If you don’t want to have to configure everything and just want an editor that, works, use VS Code check out Doom Emacs.

  2. early-init.el is loaded during startup, see The Early Init File. Unless explicitly noted, configuration should go in init.el.

  3. Derived from the incredible original

  4. Yes, I know that’s what they’re called in Vim.