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.


3 Answers 3


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
    Commented 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. Commented 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
    Commented 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
    Commented Jun 2, 2015 at 16:19
  • Thanks for the tips. I have updated my answer. Glad I could help Commented Jun 3, 2015 at 7:14

You can use org-extra-emphasis: Extra Emphasis markers for Emacs Org mode.

The solution outlined here works with org-9.5.3.

To get this

Buffer as it is

Buffer when org-extra-emphasis-mode is OFF

Buffer as it is rendered in Emacs with org-extra-emphasis-mode

Buffer as is rendered when org-extra-emphasis-mode is ON

Buffer as it is rendered in Firefox when exporter to HTML

HTML export of org-export-emphasis.org as seen in Firefox

do this

  1. emacs -Q

  2. Download org-extra-emphasis.el to say ~/Downloads/

  3. M-x load-file ~/Downloads/org-extra-emphasis.el

  4. M-x customize-group org-extra-emphasis. Customize org-extra-emphasis-alist variable and org-extra-emphasis-1 face as below.

    Customize org-extra-emphasis

  5. Open a file org-extra-emphasis.org and fill it with following content. (Note: For some reason, the global value of org-hide-emphasis-markers was not getting inherited in the org buffer. So, I had to force its value using a file local variable.)

#+TITLE: Additional Emphasis Markers to Org mode files

Start Emacs, open a file with <kbd>C-x C-f<kbd>, do some edits, and
save it with <kbd>C-x C-s<kbd>.  Once you are happy with the edits you
can close Emacs with <kbd>C-x C-c<kbd>.

# Local Variables:
# org-hide-emphasis-markers: t
# End:

  1. Steps 4 & 5 are equivalent to

     '(org-extra-emphasis-alist '(("<kbd>" org-extra-emphasis-1)))
     '(org-hide-emphasis-markers t))
       ((t (:background "yellow"
            :box (:line-width
                  (2 . 2)
                  :color "red"
                  :style released-button))))))
  2. Export the above file to HTML with C-c C-e h o. You will get the output shown earlier.

  3. Export the above file to ODT with C-c C-e o O. You won't get the box around the emphasis market, but you will get the background color carried over.

  4. Repeat the above steps with M-x org-extra-emphasis-mode, and investigate the difference.

(Note org-extra-emphasis-mode, contrary to it's name is not a mode at all. Its effect is global)

When using org-extra-emphasis, you cannot use an arbitrary string as a emphasis marker. If some emphasis marker doesn't work with some export backend change the marker to something else.

For example, using "#' as an emphasis marker is a bad idea. There may be no # in your body text, but the HTML exporter may introduce attributes with #--think colors, or hrefs. The filter function cannot distinguish between # you introduced as an extra emphasis and the # that the HTML exporter itself introduced. This means that the export filter will happily mark any stretch of HTML text enclosed within a # as a span, and you will end up with a broken HTML.

Unlike, the solution shared here, which extends the parser, org-extra-emphasis uses an export filter, specifically org-export-filter-plain-text-functions. So, if something goes wrong, you--an intermediate-level user--can easily fix things.


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
    Commented 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) Commented Jan 26, 2019 at 5:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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