I want to add a markup and formatting for such markup, i.e. <kbd>...</kbd> with a box surrounds such markup. I also want the mark up to be compatible with (setq org-hide-emphasis-markers t). That is, when the variable is set to t, the <kbd> and </kbd>tags should disappear, leaving the text between it with the specified formatting above.

The answer posted in this question: How to highlight text permanently in org-mode does not solve this problem, since it is only applicable for existing markups, not extending Org with new markups.


2 Answers 2


I have done something similar. It's in French, but the code should speak for itself. I use for the marker (I use a bepo layout), and when I do, the marked text as a pressed-button style.

I am not fluent in lisp, so there may be room for improvement.

What I have done is that, when using for marker, the marked text has a pressed-button style, and when exported it is translated to <kbd>

First, I had to define a new face:

(defface my-face-org-keystroke
  '((t (:inherit shadow 
        :box (:line-width -2 ;neg. in order to keep the same size of lines
              :color "grey75"
              :style pressed-button)))) "Face for keystrokes"
        :group 'org-faces)

Then customize org-emphasis-alist :

(("*" bold)
 ("/" italic)
 ("_" underline)
 ("=" org-code verbatim)
 ("~" org-verbatim verbatim)
  (:strike-through t))
 ("‰" my-face-org-keystroke verbatim));This line is what you want to add

For the export, you may need to load ox.el with (require 'ox).

Then each time bold or code appears in a function (in ox-org.el), I have created a similar function (or modified existing ones):

(defun org-html-keystroke (keystroke contents info)
  "Transcode KEYSTROKE from Org to HTML.
CONTENTS is nil.  INFO is a plist holding contextual
  (format (or (cdr (assq 'my-object-keystroke org-html-text-markup-alist)) "%s")
          (org-html-encode-plain-text (org-element-property :value keystroke))))

(defun org-element-my-object-keystroke-parser ()
  "Parse code object at point.

Return a list whose CAR is `my-object-keystroke' and CDR is a plist with
`:value', `:begin', `:end' and `:post-blank' keywords.

Assume point is at the first tilde marker."
    (unless (bolp) (backward-char 1))
    (looking-at org-emph-re)
    (let ((begin (match-beginning 2))
          (value (org-match-string-no-properties 4))
          (post-blank (progn (goto-char (match-end 2))
                             (skip-chars-forward " \t")))
          (end (point)))
      (list 'my-object-keystroke
            (list :value value
                  :begin begin
                  :end end
                  :post-blank post-blank)))))

(defun org-element-my-object-keystroke-interpreter (keystroke contents)
  "Interpret KEYSTROKE object as Org syntax.
CONTENTS is nil."
  (format "‰%s‰" (org-element-property :value keystroke)))

(defconst org-element-object-successor-alist
  '((subscript . sub/superscript) (superscript . sub/superscript)
    (bold . text-markup) (code . text-markup) (italic . text-markup)
    (strike-through . text-markup) (underline . text-markup)
    (verbatim . text-markup) (entity . latex-or-entity)
    (latex-fragment . latex-or-entity) (my-object-keystroke . text-markup))
  "Alist of translations between object type and successor name.
Sharing the same successor comes handy when, for example, the
regexp matching one object can also match the other object.")

(defconst org-element-all-objects
  '(bold code entity export-snippet footnote-reference inline-babel-call
         inline-src-block italic line-break latex-fragment link macro
         radio-target statistics-cookie strike-through subscript superscript
         table-cell target timestamp underline verbatim my-object-keystroke)
  "Complete list of object types.")

(defun org-element-text-markup-successor ()
  "Search for the next text-markup object.

Return value is a cons cell whose CAR is a symbol among `bold',
`italic', `underline', `strike-through', `code' and `verbatim'
and CDR is beginning position."
    (unless (bolp) (backward-char))
    (when (re-search-forward org-emph-re nil t)
      (let ((marker (match-string 3)))
        (cons (cond
               ((equal marker "*") 'bold)
               ((equal marker "/") 'italic)
               ((equal marker "_") 'underline)
               ((equal marker "+") 'strike-through)
               ((equal marker "~") 'code)
               ((equal marker "=") 'verbatim)
               ((equal marker "‰") 'my-object-keystroke) 
               (t (error "Unknown marker at %d" (match-beginning 3))))
              (match-beginning 2))))))

Next, I have defined a my-html backend for the export:

(org-export-define-derived-backend 'my-html 'html
  :translate-alist '((my-object-keystroke . org-html-keystroke))
  :menu-entry ' (?h 1
                    ((?r "my-html"  org-html-export-to-my-html))))

(defun org-html-export-to-my-html
  (&optional async subtreep visible-only body-only ext-plist)
  "Export current buffer to a HTML file.

Return output file's name."
  (let* ((extension (concat "." org-html-extension))
         (file (org-export-output-file-name extension subtreep))
         (org-export-coding-system org-html-coding-system))
    (org-export-to-file 'my-html file
      async subtreep visible-only body-only ext-plist)))

(defun org-html-publish-to-my-html (plist filename pub-dir)
  "Publish an org file to my-html.
Return output file name."
  (org-publish-org-to 'my-html filename
                      (concat "." (or (plist-get plist :html-extension)
                                      org-html-extension "html"))
                      plist pub-dir))

(defun org-html-convert-region-to-my-html ()
  "Assume the current region has org-mode syntax, and convert it to HTML.
This can be used in any buffer.  For example, you can write an
itemized list in org-mode syntax in an HTML buffer and use this
command to convert it."
  (org-export-replace-region-by 'my-html))

So when I use C-c C-e h r it's exported correctly:

enter image description here

enter image description here

enter image description here

As suggested by OP in comments, you may need to use org-mode-restart (or org-reload) or kill/reload your buffer.

Edit: This works for org-mode with versions prior 8.3 (that is till 8.2.10)

With versions ≥8.3.1, I have to modify

  • org-element-all-objects
  • possibly org-element-object-restrictions
  • org-element--set-regexps
  • org-element--object-lex

and of course still add the functions

  • org-element-my-object-keystroke-parser
  • org-element-my-object-keystroke-interpreter


  • org-element-object-successor-alist
  • org-element-text-markup-successor

are now deleted.

Thanks to Charles C. Berry for his help.

  • Is % marker a built-in one? I cannot get it to work with latest Org. As for other markers, it works fine if I change their faces. But is there a way to really add our own markers? Nevertheless, your answer is useful.
    – Tu Do
    Jun 1, 2015 at 14:32
  • % is not currently used as a marker. You can use it the same way I used . I don't understand your second question though, is a new marker. Jun 2, 2015 at 7:59
  • Ok, I was able to make the % marker work, but I had to run org-reload. You should update the answer with that command.
    – Tu Do
    Jun 2, 2015 at 15:52
  • Actually, we don't need org-reload but org-mode-restart. The thing is, we must kill previous Org buffer and create a new one for the change to take effect.
    – Tu Do
    Jun 2, 2015 at 16:19
  • Thanks for the tips. I have updated my answer. Glad I could help Jun 3, 2015 at 7:14

I don't think it's possible to add markers for new org-mode markup options.

According to this 2012 post it looks like org-mode's "emphasis markers will be hard-coded". Doing a quick search for org-emph-re in org.el doesn't reveal any code that would actually generate org-emph-re from org-emphasis-alist. Based on that it looks like org-emph-re won't search for anything that you add to org-emphasis-alist.

This is consistent with my experience (I can redefine the existing emphasis markers, but couldn't get org-mode to recognize | or & or H).

I'm not an expert here, though, and would love to find out that I'm wrong :)

  • 1
    Simply editing org-emphasis-alist won't add a new marker. You have to additionally work with org-font-lock-extra-keywords too. This answer gives a working solution.
    – Dean Seo
    Jan 24, 2019 at 18:03
  • Hey, that works! At least, that achieves the same effect! :) When one uses the org-font-lock-extra-keywords then one doesn't need to change org-emphasis-alist at all, evidently (I added the org-font-lock... code but didn't change my org-emphasis-alist and now things are being formatted) Jan 26, 2019 at 5:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.