In my opinion, the power of Emacs is that you’re able to program your own working environment. Other editors may be just as, or possibly more, efficient to use, but I haven’t come across any that give you control over as many aspects of your editor as Emacs does. Customising and tweaking your configuration may be an endless endeavour, but it’s also a highly rewarding one.

All that being said, I was inspired to tweak the look of my Org Mode setup, especially after reading the blog post Beautifying Org Mode in Emacs. Below you can find screenshots and code. I hope there’s something in here that you might want to steal for your own config! In case I change these settings later, my most recently updated config is always available on my GitHub.

Theme

The easiest and quickest way to change the look of vanilla Emacs is to load a colour theme. I’m using the doom-nord theme, which is part of the doom-themes package. I find both it and several of the other themes from that pack to be excellent.

You can find an updated list of themes on emacsthemes.com where they have screenshots. From spending time on the Emacs subreddit, I also know that people are very fond of Prot’s Modus and Ef themes, as well as the built-in leuven theme. Feel free to play around!

Fonts

Next up is setting up variable-pitch and fixed-pitch fonts. I love Roboto Mono and I use a ligaturised version of it for programming, from the a-better-ligaturizer project. Here, I’ll add that a package such as ligature.el is required to display the ligatures.

For variable-pitch (regular) text, I want to use Source Sans Pro.

In my Emacs config, I have set these fonts outside the Org section, under “Interaction, Look & Feel”.

(when (member "Roboto Mono" (font-family-list))
  (set-face-attribute 'default nil :font "Roboto Mono" :height 108)
  (set-face-attribute 'fixed-pitch nil :family "Roboto Mono"))

(when (member "Source Sans Pro" (font-family-list))
  (set-face-attribute 'variable-pitch nil :family "Source Sans Pro" :height 1.18))

Then, back in the Org-specific part of the config, I resize the Org headings and choose Source Sans Pro to be the header font.

;; Resize Org headings
(dolist (face '((org-level-1 . 1.35)
                (org-level-2 . 1.3)
                (org-level-3 . 1.2)
                (org-level-4 . 1.1)
                (org-level-5 . 1.1)
                (org-level-6 . 1.1)
                (org-level-7 . 1.1)
                (org-level-8 . 1.1)))
  (set-face-attribute (car face) nil :font "Source Sans Pro" :weight 'bold :height (cdr face)))

;; Make the document title a bit bigger
(set-face-attribute 'org-document-title nil :font "Source Sans Pro" :weight
'bold :height 1.8)

In order to avoid line spacing issues when a line of text contains both variable- and fixed-pitch text, we need to make sure that the org-indent face inherits from fixed-pitch.

(require 'org-indent)
(set-face-attribute 'org-indent nil :inherit '(org-hide fixed-pitch))

And then, we want to make sure that some parts of the Org document always use fixed-pitch even when variable-pitch-mode is on.

(set-face-attribute 'org-block nil            :foreground nil :inherit
'fixed-pitch :height 0.85)
(set-face-attribute 'org-code nil             :inherit '(shadow fixed-pitch) :height 0.85)
(set-face-attribute 'org-indent nil           :inherit '(org-hide fixed-pitch) :height 0.85)
(set-face-attribute 'org-verbatim nil         :inherit '(shadow fixed-pitch) :height 0.85)
(set-face-attribute 'org-special-keyword nil  :inherit '(font-lock-comment-face
fixed-pitch))
(set-face-attribute 'org-meta-line nil        :inherit '(font-lock-comment-face fixed-pitch))
(set-face-attribute 'org-checkbox nil         :inherit 'fixed-pitch)

For this all to come together, we need to make sure that variable-pitch-mode is always active in Org buffers.

(add-hook 'org-mode-hook 'variable-pitch-mode)

Also, if you’re having troubles with the size of LaTeX-previews like I did, you can increase the size like so.

(plist-put org-format-latex-options :scale 2)

Decluttering

We’ll use “pretty entities”, which allow us to insert special characters LaTeX-style by using a leading backslash (e.g., \alpha to write the greek letter alpha). org-ellipsis is the symbol displayed after an Org-heading that is collapsed - I prefer a simple dot.

(setq org-adapt-indentation t
      org-hide-leading-stars t
      org-pretty-entities t
	  org-ellipsis "  ·")

For source code blocks specifically, I want Org to display the contents using the major mode of the relevant language. I also want TAB to behave inside the source code block like it normally would when writing code in that language.

(setq org-src-fontify-natively t
	  org-src-tab-acts-natively t
      org-edit-src-content-indentation 0)

It’s common to hide emphasis markers (e.g., /.../ for italics, *...* for bold, etc.) to have a cleaner visual look, but this makes it harder to edit the text. org-appear is the solution to all my troubles. It displays the markers when the cursor is within them and hides them otherwise, making edits easy while looking pretty.

(use-package org-appear
  :commands (org-appear-mode)
  :hook     (org-mode . org-appear-mode)
  :config
  (setq org-hide-emphasis-markers t)  ; Must be activated for org-appear to work
  (setq org-appear-autoemphasis   t   ; Show bold, italics, verbatim, etc.
        org-appear-autolinks      t   ; Show links
		org-appear-autosubmarkers t)) ; Show sub- and superscripts

And finally, I have some Org options to deal with headers and TODO’s nicely.

(setq org-log-done                       t
	  org-auto-align-tags                t
	  org-tags-column                    -80
	  org-fold-catch-invisible-edits     'show-and-error
	  org-special-ctrl-a/e               t
	  org-insert-heading-respect-content t)

LaTeX Previews

The LaTeX previews in Org mode are pretty small by default, so I’ll increase their size a little.

(plist-put org-format-latex-options :scale 1.35)

org-fragtog works like org-appear, but for LaTeX fragments: It toggles LaTeX previews on and off automatically, depending on the cursor position. If you move the cursor to a preview, it’s toggled off so you can edit the LaTeX snippet. When you move the cursor away, the preview is turned on again.

(use-package org-fragtog
  :hook (org-mode-hook . org-fragtog-mode))

Centring & Line Breaks

I want the text to fill the screen adaptively, so that long lines of text adapt to the size of the window. It also breaks lines instead of truncating them.

(add-hook 'org-mode-hook 'visual-line-mode)

I prefer having my Org buffer centred. I think it looks prettier when I only have one buffer open, and it’s barely noticeable when several are open because the width of the margins adapt. For this, I use Olivetti, which I think is a great package for this purpose.

(add-hook 'org-mode-hook 'olivetti-mode)

As you can see in the below screenshot, the Org document fills up the left side of the screen comfortably even when olivetti-mode is on.

Task & Time Tracking

Org mode is also a really powerful tool for tracking tasks and time usage. However, the default colours don’t go too well with our new look.

Of course, you should change the keywords and the number of priorities to suit your tastes. I have lifted my colours straight from the official Nord theme pallette so that they go well with my preferred theme.

Let’s set the number of task priorities and specify the colour for each priority.

(setq org-lowest-priority ?F)  ;; Gives us priorities A through F
(setq org-default-priority ?E) ;; If an item has no priority, it is considered [#E].

(setq org-priority-faces
      '((65 . "#BF616A")
        (66 . "#EBCB8B")
        (67 . "#B48EAD")
        (68 . "#81A1C1")
        (69 . "#5E81AC")
        (70 . "#4C566A")))

And then the custom keywords.

(setq org-todo-keywords
      '((sequence
		 "TODO(t)" "WAIT(w)" "READ(r)" "PROG(p)" ; Needs further action
		 "|"
		 "DONE(d)")))                            ; Needs no action currently

I don’t set the colours of each TODO state individually anymore, but if you wanted to, you could set the org-todo-keyword-faces variable like this:

(setq org-todo-keyword-faces
      '(("TODO(t)"      :inherit (org-todo region) :foreground "#A3BE8C" :weight bold)
		...))

Bullets

org-superstar styles some of my UI elements, such as bullets and special checkboxes for TODOs. It can style a lot more, so I recommend checking the package out!

(use-package org-superstar
  :config
  (setq org-superstar-leading-bullet " ")
  (setq org-superstar-headline-bullets-list '("◉" "○" "⚬" "◈" "◇"))
  (setq org-superstar-special-todo-items t) ;; Makes TODO header bullets into boxes
  (setq org-superstar-todo-bullet-alist '(("TODO"  . 9744)
                                          ("WAIT"  . 9744)
                                          ("READ"  . 9744)
                                          ("PROG"  . 9744)
										  ("DONE"  . 9745)))
  :hook (org-mode . org-superstar-mode))

SVG Elements

I use svg-tag-mode to replace progress bars, task priorities, dates, and citations with nice SVG graphics. This package can also style many more elements and I’d encourage you to read the documentation to find other things you might want to style with this.

(use-package svg-tag-mode
  :config
  (defconst date-re "[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\}")
  (defconst time-re "[0-9]\\{2\\}:[0-9]\\{2\\}")
  (defconst day-re "[A-Za-z]\\{3\\}")
  (defconst day-time-re (format "\\(%s\\)? ?\\(%s\\)?" day-re time-re))

  (defun svg-progress-percent (value)
	(svg-image (svg-lib-concat
				(svg-lib-progress-bar (/ (string-to-number value) 100.0)
			      nil :margin 0 :stroke 2 :radius 3 :padding 2 :width 11)
				(svg-lib-tag (concat value "%")
				  nil :stroke 0 :margin 0)) :ascent 'center))

  (defun svg-progress-count (value)
	(let* ((seq (mapcar #'string-to-number (split-string value "/")))
           (count (float (car seq)))
           (total (float (cadr seq))))
	  (svg-image (svg-lib-concat
				  (svg-lib-progress-bar (/ count total) nil
					:margin 0 :stroke 2 :radius 3 :padding 2 :width 11)
				  (svg-lib-tag value nil
					:stroke 0 :margin 0)) :ascent 'center)))
  (setq svg-tag-tags
      `(
        ;; Task priority
        ("\\[#[A-Z]\\]" . ( (lambda (tag)
                              (svg-tag-make tag :face 'org-priority
                                            :beg 2 :end -1 :margin 0))))

        ;; Progress
        ("\\(\\[[0-9]\\{1,3\\}%\\]\\)" . ((lambda (tag)
          (svg-progress-percent (substring tag 1 -2)))))
        ("\\(\\[[0-9]+/[0-9]+\\]\\)" . ((lambda (tag)
          (svg-progress-count (substring tag 1 -1)))))

        ;; Citation of the form [cite:@Knuth:1984]
        ("\\(\\[cite:@[A-Za-z]+:\\)" . ((lambda (tag)
                                          (svg-tag-make tag
                                                        :inverse t
                                                        :beg 7 :end -1
                                                        :crop-right t))))
        ("\\[cite:@[A-Za-z]+:\\([0-9]+\\]\\)" . ((lambda (tag)
                                                (svg-tag-make tag
                                                              :end -1
                                                              :crop-left t))))


        ;; Active date (with or without day name, with or without time)
        (,(format "\\(<%s>\\)" date-re) .
         ((lambda (tag)
            (svg-tag-make tag :beg 1 :end -1 :margin 0))))
        (,(format "\\(<%s \\)%s>" date-re day-time-re) .
         ((lambda (tag)
            (svg-tag-make tag :beg 1 :inverse nil :crop-right t :margin 0))))
        (,(format "<%s \\(%s>\\)" date-re day-time-re) .
         ((lambda (tag)
            (svg-tag-make tag :end -1 :inverse t :crop-left t :margin 0))))

        ;; Inactive date  (with or without day name, with or without time)
         (,(format "\\(\\[%s\\]\\)" date-re) .
          ((lambda (tag)
             (svg-tag-make tag :beg 1 :end -1 :margin 0 :face 'org-date))))
         (,(format "\\(\\[%s \\)%s\\]" date-re day-time-re) .
          ((lambda (tag)
             (svg-tag-make tag :beg 1 :inverse nil
						       :crop-right t :margin 0 :face 'org-date))))
         (,(format "\\[%s \\(%s\\]\\)" date-re day-time-re) .
          ((lambda (tag)
             (svg-tag-make tag :end -1 :inverse t
						       :crop-left t :margin 0 :face 'org-date)))))))

(add-hook 'org-mode-hook 'svg-tag-mode)

Prettify Tags & Keywords

I have a custom function to prettify tags and other elements, lifted from Jake B’s Emacs setup.

(defun my/prettify-symbols-setup ()
  ;; Checkboxes
  (push '("[ ]" . "") prettify-symbols-alist)
  (push '("[X]" . "") prettify-symbols-alist)
  (push '("[-]" . "" ) prettify-symbols-alist)

  ;; org-abel
  (push '("#+BEGIN_SRC" . ?≫) prettify-symbols-alist)
  (push '("#+END_SRC" . ?≫) prettify-symbols-alist)
  (push '("#+begin_src" . ?≫) prettify-symbols-alist)
  (push '("#+end_src" . ?≫) prettify-symbols-alist)

  (push '("#+BEGIN_QUOTE" . ?❝) prettify-symbols-alist)
  (push '("#+END_QUOTE" . ?❞) prettify-symbols-alist)

  ;; Drawers
  (push '(":PROPERTIES:" . "") prettify-symbols-alist)

  ;; Tags
  (push '(":projects:" . "") prettify-symbols-alist)
  (push '(":work:"     . "") prettify-symbols-alist)
  (push '(":inbox:"    . "") prettify-symbols-alist)
  (push '(":task:"     . "") prettify-symbols-alist)
  (push '(":thesis:"   . "") prettify-symbols-alist)
  (push '(":uio:"      . "") prettify-symbols-alist)
  (push '(":emacs:"    . "") prettify-symbols-alist)
  (push '(":learn:"    . "") prettify-symbols-alist)
  (push '(":code:"     . "") prettify-symbols-alist)

  (prettify-symbols-mode))

(add-hook 'org-mode-hook        #'my/prettify-symbols-setup)
(add-hook 'org-agenda-mode-hook #'my/prettify-symbols-setup)

After all this prettification, TODOs, code blocks, and lists look like screenshot below.

Conclusion

There it is, that’s pretty much all of the visual Org-specific code in my config. If you’re interested in other aspects of my config, you’re of course welcome to check it out. I’m just starting out, so I’d also really appreciate constructive criticism or tips!