org-special-block-extras
A unified interface for special blocks and links: defblock

Table of Contents

1. 🧙‍♂️ Write Once, Export Everywhere: Say Hello to org-special-block-extras

Tired of repeating yourself when writing in Org-mode for LaTeX and HTML exports? org-special-block-extras is your magical toolkit for creating custom Org-mode blocks and links that work seamlessly across backends —without diving into the dark arts of raw HTML or LaTeX!

1.1. 🎁 What Does It Do?

  • 🎨 Style with ease — add colors, badges, tooltips (from a dictionary, Emacs Help, or custom file), keystroke notations, inline editorial comments, or even colourful ASCII cows with fortune cookies.
  • 📚 Glossary support — embed hoverable documentation with doc: links and auto-generated glossaries.
  • 📦 Add interactive content: from collapsible detail sections and parallel columns to spoiler tags and equational proofs.
  • Use badge-style links to embed GitHub stars, Reddit subs, or custom project metadata via shields.io.
  • 🛠 Use org-defblock and org-deflink macros — familiar defun-like syntax lets you define custom Org behaviours.
    • Create powerful custom blocks with the defblock macro: Define a block once and export to LaTeX and HTML effortlessly. With header-args support like like :color red :signoff "Thanks!" for locally customised exports.
    • Use org-deflink to create custom links using a syntax similar to defun.

🧱 Ready-to-Use Custom Blocks

Block Use Case
details Foldable sections — perfect for hiding spoilers, proofs, or long explanations.
parallel Display content in multiple columns — great for comparisons or saving space.
margin Add subtle explanatory tooltips or side remarks — delightful for scholarly writing.
box Emphasize content with nicely styled borders — great for callouts or side notes.
spoiler Hide content visually until the reader chooses to see it — useful in teaching.
tree Display proof trees and logical derivations — fantastic for formal logic.
rename Automated text substitution — great for translation or glossary effects.
stutter Repeat content multiple times — e.g., for emphasis or stylistic flair.
solution Reveal answers in stages — perfect for educational content.
org-demo Show Org markup alongside its rendered result — ideal for tutorials.
latex-definitions Hide LaTeX-only declarations from HTML — keep your source clean.
calc Step-by-step equational reasoning — beautiful for math walkthroughs.

Ready-to-Use Custom Links

Link Purpose
doc:label Glossary/dictionary tooltips —link to local or global explanations; e.g., cl-loop
kbd: Stylized keybindings with tooltips — e.g., kbd:C-x_C-e.
color: or red: Inline text colouring — supports both names and hex codes.
html-export-style: Pick a visual theme for HTML exports — one click, new style.
badge: Embed project badges — e.g., GitHub stars, Reddit subs, versioning.
fortune: Insert ASCII animals saying jokes or phrases — joy in your docs!
octoicon: GitHub-style icons — for styling and linking like a pro.
link-here: Local anchors — create navigable sections anywhere.
show: Show a variable value — dynamically insert content like your name or version.
elisp: Make clickable actions — buttons that run Emacs Lisp.

💡 Ideal For

  • 🧑‍🏫 Educators building interactive lessons and glossaries
  • 🧑‍💻 Developers documenting APIs with style
  • 📚 Writers crafting scholarly or explorable documents
  • ✨ Emacsers who want rich export without leaving Org-mode
  • ✏️ Bloggers wanting powerful interactivity
    • This is the reason I made this package.
  • 1️⃣ Anyone who wants to keep Org-mode as the single source of truth for rich exports

🪄 Just load the package, write in Org, and let defblock handle the rest. Now your Org-mode documents are not just structured —they're spectacular.


Write rich Org-mode documents — one source, many formats. The org-special-block-extras package empowers you to define and use custom blocks and links that make your Org files export beautifully to both HTML and LaTeX, without ever writing raw HTML or LaTeX again.

2. Block and link types provided by this package

2.1. Boxed Text —Calling out super duper info

﴾What You Write﴿
It can be useful to draw attention to some important text by
enclosing it in a [[doc:org-block/box][box]].

#+begin_box Uses of callout boxes
Such boxes often callout tips, warnings, cautionary info or
emphasises core information.
#+end_box
﴾What You Get﴿

It can be useful to draw attention to some important text by enclosing it in a box.

Uses of callout boxes

Such boxes often callout tips, warnings, cautionary info or emphasises core information.


﴾How It’s Implemented﴿
  (org-defblock box (title "" background-color nil shadow nil frame-color nil title-background-color nil)
  "Enclose text in a box, possibly with a title.

By default, the box's COLOR is green for HTML and red for LaTeX,
and it has no TITLE.

SHADOW is an alternate style of boxing: It shows the contents in a centred
box with a shadow colour being the given non-nil value of SHADOW.
If SHADOW is a hexidecimal colour, it should be a string enclosed in double quotes.
More accurately, the following are all valid uses:

    #+begin_box title :shadow t
    #+begin_box title :shadow inset

    #+begin_box title :shadow \"pink\"
    #+begin_box title :shadow pink

    #+begin_box title :shadow (cyan pink orange yellow)
    #+begin_box title :shadow (cyan \"inset pink\" orange)
    #+begin_box title :shadow (:left cyan :right pink :deep-right orange :deep-left yellow)

Notice that prefixing colours with ‘inset’ causes the colour to be within the box,
rather than spread, with blur, around the box. The use of ‘inset’ swaps left and right.

The HTML export uses a padded div, whereas the LaTeX export
requires the tcolorbox package.

In the future, I will likely expose more arguments."

  (pcase backend
    (`latex
     (apply #'concat
            `("\\begin{tcolorbox}[title={" ,title "}"
              ",colback=" ,(pp-to-string (or background-color 'red!5!white))
              ",colframe=" ,(pp-to-string (or frame-color 'red!75!black))
              ",colbacktitle=" ,(pp-to-string (or title-background-color 'yellow!50!red))
              ",coltitle=red!25!black, fonttitle=\\bfseries,"
              "subtitle style={boxrule=0.4pt, colback=yellow!50!red!25!white}]"
              ,contents
              "\\end{tcolorbox}")))
    ;; CSS syntax: “box-shadow: specification, specification, ...”
    ;; where a specification is of the shape “[inset] x_offset y_offset [blur [spread]] color”.
    (_ (-let [haze (lambda (left right deep-right deep-left)
                     (format "width: 50%%; margin: auto; box-shadow: %s"
                             (thread-last (list (cons right      "8px 6px 13px 8px %s")
                                                (cons left       "-16px 12px 20px 16px %s")
                                                (cons deep-right "48px 36px 71px 28px %s")
                                                (cons deep-left  "-48px -20px 71px 28px %s"))
                               (--filter (car it))
                               (--map (format (cdr it) (car it)))
                               (s-join ","))))]
         (format "<div style=\"%s\"> <h3>%s</h3> %s </div>"
                 (s-join ";" `("padding: 1em"
                               ,(format "background-color: %s" (org-subtle-colors (format "%s" (or background-color "green"))))
                               "border-radius: 15px"
                               "font-size: 0.9em"
                               ,(when shadow
                                  (cond
                                   ((equal shadow t)
                                    (funcall haze "hsl(60, 100%, 50%)" "hsl(1, 100%, 50%)" "hsl(180, 100%, 50%)" nil))
                                   ((equal shadow 'inset)
                                    (funcall haze "inset hsl(60, 100%, 50%)" "inset hsl(1, 100%, 50%)" "inset hsl(180, 100%, 50%)" nil))
                                   ((or (stringp shadow) (symbolp shadow))
                                    (format "box-shadow: 10px 10px 20px 0px %s; width: 50%%; margin: auto" (pp-to-string shadow)))
                                   ((json-plist-p shadow)
                                    (-let [(&plist :left X :right Y :deep-right Z :deep-left W) shadow]
                                      (funcall haze X Y Z W)))
                                   (:otherwise (-let [(X Y Z W) shadow]
                                        (funcall haze X Y Z W)))))))
                 title contents)))))
 
 

2.2. Nice Keystroke Renditions: C-h h

Anyone who writes about Emacs will likely want to mention keystrokes in an aesthetically pleasing way, such as C-u 80 - to insert 80 dashes, or C-c C-e h o to export an Org-mode file to HTML, or the useful M-s h ..

  • kbd:𝒳 will show a tooltip defining 𝒳, as an Emacs Lisp function, if possible. For example, kbd:C-h_h is C-h h; and likewise <kbd:M-s h .> is M-s h. (we need to use <...> since punctuation is not picked up as part of link labels). In contrast, kbd:nope renders as nope without a tooltip (nor a red border).

    You can also supply explicit tooltip description: [[kbd:key sequence][description]] will show as key sequence; i.e., the key sequence in nice key font along with a tooltip explaining it.

kbd.png

2.3. Folded Details —Let the user see stuff only if they're interested

﴾What You Write﴿
Sometimes there is a remark or a code snippet that is useful to
have, but not relevant to the discussion at hand and so we want to
/fold away such [[doc:org-block/details][details]]/.

#+begin_details Example use cases
+ ‘Conversation-style’ articles, where the author asks the reader questions
  whose answers are “folded away” so the reader can think about the exercise
  before seeing the answer.

+ Hiding boring but important code snippets, such as a list of import
  declarations or a tedious implementation.
#+end_details

Full syntax ~#+begin_details "title" :background-color "cyan" :title-color "green"~.
﴾What You Get﴿

Sometimes there is a remark or a code snippet that is useful to have, but not relevant to the discussion at hand and so we want to fold away such details.

Example use cases
  • ‘Conversation-style’ articles, where the author asks the reader questions whose answers are “folded away” so the reader can think about the exercise before seeing the answer.
  • Hiding boring but important code snippets, such as a list of import declarations or a tedious implementation.

Full syntax #+begin_details "title" :background-color "cyan" :title-color "green".


﴾How It’s Implemented﴿
  (org-defblock details (title "Details"
              background-color "#e5f5e5" title-color "green")
  "Enclose contents in a folded up box, for HTML.

For LaTeX, this is just a boring, but centered, box.

By default, the TITLE of such blocks is “Details”,
its TITLE-COLOR is green, and BACKGROUND-COLOR is “#e5f5e5”.

In HTML, we show folded, details, regions with a nice greenish colour.

In the future ---i.e., when I have time---
it may be prudent to expose more aspects as arguments.
"
   (pcase backend
     (`latex (concat (pcase (substring background-color 0 1)
                       ("#" (format "\\definecolor{osbe-bg}{HTML}{%s}" (substring background-color 1)))
                       (_ (format "\\colorlet{osbe-bg}{%s}" background-color)))
                     (pcase (substring title-color 0 1)
                       ("#" (format "\\definecolor{osbe-fg}{HTML}{%s}" (substring title-color 1)))
                       (_ (format "\\colorlet{osbe-fg}{%s}" title-color)))
                     (format "\\begin{quote}
                              \\begin{tcolorbox}[colback=osbe-bg,colframe=osbe-fg,title={%s},sharp corners,boxrule=0.4pt]
                                   %s
                               \\end{tcolorbox}
                \\end{quote}" title contents)))
     (_ (format "<details class=\"code-details\"
                 style =\"padding: 1em;
                          background-color: %s;
                          border-radius: 15px;
                          color: hsl(157 75% 20%);
                          font-size: 0.9em;
                          box-shadow: 0.05em 0.1em 5px 0.01em  #00000057;\">
                  <summary>
                    <strong>
                      <font face=\"Courier\" size=\"3\" color=\"%s\">
                         %s
                      </font>
                    </strong>
                  </summary>
                  %s
               </details>" background-color title-color title contents))))
 
 

2.4. Remark —Inline footnotes, viz HTML tooltips and LaTeX notes in the margin

Sometimes you want to remark on [BROKEN LINK: org-block/details] without drawing too much attention, and so a tooltip via [BROKEN LINK: org-block/remark] suffices.

(Scroll to make tooltips disappear!)

﴾What You Write﴿
It can be annoying to make readers jump to the bottom of the page to
read a footnote.  Instead, I prefer to write an /inline [[doc:org-block/tooltip][remark]]/
containing, say, references or additional explanation.

   /[[tooltip:Allah][The God of Abraham; known as Elohim in the Bible]] does not burden a
       soul beyond what it can bear./ --- Quran 2:286

Or, without specifying an explicit label:

   /Allah[[tooltip:][The God of Abraham; known as Elohim in the Bible]] does not
       burden a soul beyond what it can bear./ --- Quran 2:286
﴾What You Get﴿

It can be annoying to make readers jump to the bottom of the page to read a footnote. Instead, I prefer to write an inline remark containing, say, references or additional explanation.

Allah  does not burden a soul beyond what it can bear. — Quran 2:286

Or, without specifying an explicit label:

Allah°  does not burden a soul beyond what it can bear. — Quran 2:286


﴾How It’s Implemented﴿
  (org-defblock tooltip (marker "°")
 "Produce an HTML tooltip."
 (format "<abbr class=\"tooltip\" title=\"%s\">%s</abbr>&emsp13;"
         (org-ospe-html-export-preserving-whitespace contents)
         marker))
 
 

2.5. Parallel ---Place ideas side-by-side, possibly with a separator

﴾What You Write﴿
Reduce the amount of whitespace noise in your articles by using the
[[doc:org-block/parallel][parallel]] block to place ideas
side-by-side ---with up to 𝓃-many columns.

----------------------------------------------------------------------

#+begin_parallel 2
*“Soft Columns”:* Writing ~#+begin_parallel 𝓃 :bar BAR~ will produce
𝒏-many parallel columns, possibly separated by solid rules, or a
“bar”. /This style allows text to freely move between columns,
depending on the size of the browser, which may dynamically shrink
and grow./ The value ~BAR~ can either be ~t~, ~nil~, or any
(backend)-valid colour specification; such as ~red~ or ~green~.
#+end_parallel

----------------------------------------------------------------------

#+begin_parallel 25% 50% 25% :bar green

*“Hard Columns”:* Alternatively, /for non-uniform column widths, 𝓃 may
instead be a specification of the widths of the columns./

#+columnbreak:  
However, this extra flexibility comes at an additional cost: The
contents of the block must now contain /𝓃-1/ lines consisting of
~#+columnbreak:~, when the specification determines 𝓃-many columns.

#+columnbreak:
Goodbye (“God-be-with-ye”) to the right!

#+end_parallel

# In the Soft Columns style above, any ‘#+columnbreak:’ are merely ignored.

In summary, articles can get lengthy when vertical whitespace is
wasted on thin lines; instead, one could save space by using
/[[doc:org-block/parallel][parallel]] columns of text/.
﴾What You Get﴿

Reduce the amount of whitespace noise in your articles by using the parallel block to place ideas side-by-side —with up to 𝓃-many columns.


“Soft Columns”: Writing #+begin_parallel 𝓃 :bar BAR will produce 𝒏-many parallel columns, possibly separated by solid rules, or a “bar”. This style allows text to freely move between columns, depending on the size of the browser, which may dynamically shrink and grow. The value BAR can either be t, nil, or any (backend)-valid colour specification; such as red or green.


“Hard Columns”: Alternatively, for non-uniform column widths, 𝓃 may instead be a specification of the widths of the columns.

However, this extra flexibility comes at an additional cost: The contents of the block must now contain 𝓃-1 lines consisting of html:</div><div style="width: 25%; margin: 10px; border-right:4px none; float: left;">, when the specification determines 𝓃-many columns.

Goodbye (“God-be-with-ye”) to the right!

In summary, articles can get lengthy when vertical whitespace is wasted on thin lines; instead, one could save space by using parallel columns of text.


﴾How It’s Implemented﴿
  (org-defblock parallel (cols "2" bar nil)
  "Place ideas side-by-side, possibly with a separator.

There are COLS many columns, and they may be seperated by solid
vertical rules if BAR is a non-nil (colour) value.

+ COLS is either a number or a sequence of the shape: 10% 20% 30%.
+ BAR is either `t', `nil', or a colour such as `red' or `blue'.

----------------------------------------------------------------------

“Soft Columns”: Writing “#+begin_parallel 𝓃 :bar t” will produce
𝒏-many parallel columns, possibly separated by solid rules, or a
“bar”. This style allows text to freely move between columns,
depending on the size of the browser, which may dynamically
shrink and grow.  BAR can either be `t', `nil', or
any (backend)-valid colour specification; such as `red' or
`green'.

“Hard Columns”: Alternatively, for non-uniform column widths,
COLS may instead be a specification of the widths of the
columns. However, this extra flexibility comes at an additional
cost: The contents of the block must now contain 𝒏-1 lines
consisting of ‘#+columnbreak:’, when the specification determines
𝒏 columns, as shown in the following example.

   #+begin_parallel 20% 60% 20% :bar green
   Hello, to the left!

   #+columnbreak:
   A super duper wide middle margin!

   #+columnbreak:
   Goodbye (“God-be-with-ye”) to the right!
   #+end_parallel

The specification is 𝒏 measurements denoting widths; which may be
in any HTML recognisable units; e.g., “5em 20px 30%” is valid.
I personally advise only the use of percentage measurements.

In the Soft Columns style above, any ‘#+columnbreak:’ are merely ignored.
With LaTeX export, the use of ‘#+columnbreak:’ is used to request a column break."
  (let ((rule (pcase backend
               (`latex (if bar 2 0))
               (_  (format "%s %s" (if bar "solid" "none") (if (string= bar "t") "black" bar)))))
        (contents′ (s-replace "#+columnbreak:" "\\columnbreak" contents)))
    (pcase backend
      (`latex   (format  "\\par \\setlength{\\columnseprule}{%s pt}
          \\begin{minipage}[t]{\\linewidth}
          \\begin{multicols}{%s}
          %s
          \\end{multicols}\\end{minipage}"    rule cols contents′))
      (_ (if (not (s-contains-p "%" cols))
             (format "<div style=\"column-rule-style: %s;column-count: %s;\">%s</div>"
                     rule cols contents)
           ;; Otherwise: cols ≈ "10% 40% 50%", for example.
           (let ((spec (s-split " " (s-collapse-whitespace (s-trim cols))))
                 (columnBreak (lambda (width omit-rule?)
                                (format "<div style=\"width: %s; margin: 10px; border-right:4px %s; float:  left;\">" width
                                        (if omit-rule? "none" rule)))) )
             (format "<div style=\"display: flex; justify-content: space-between; align-items: flex-start;\">%s%s%s</div>"
                     (funcall columnBreak (pop spec) nil)
                     (s-replace-regexp (regexp-quote "#+columnbreak:")
                                       ;; λ’ since we need the “pop” evaluated for each find-replace instance.
                                       ;; We use “not spec” to omit the rule separator when there is NOT anymore elements in SPEC.
                                       (lambda (_) (format "html:</div>%s" (funcall columnBreak (pop spec) (not spec))))
                                       contents)
             (if (s-contains-p " " cols) "</div>" ""))))))))
 
 

2.6.   latex-definitions for hiding LaTeX declarations in HTML

To present mathematical formulae in HTML export, we may use LaTeX-style commands such as {\color{red} x} by enclosing them in $-symbols to obtain \({\color{red}x}\). This is known as the MathJax tool —Emacs' default HTML export includes it. (Unfortunately, MathJax does not directly support arbitrary HTML elements to occur within the $-delimiters.)

It is common to declare LaTeX definitions for convenience, but such declarations occur within $-delimiters and thereby produce undesirable extra whitespace. See the superflous vertical whitespace in the Result side below.

Source

 Hello, in this blog post I'd like to talk about quantifiers.
 # First, I'll define some helpers that I use multiple times...
   $$
  \def\Plus{\color{teal}\bigoplus}
  \def\plus{\;\color{teal}\oplus\;}
  \def\f#1{{\color{brown}f}{\color{violet}(#1)}}
  \def\xstart{\color{red} a}
  \def\xend{\color{cyan} b}
  $$
  
  Anyhow, let's talk about quantifiers...
  $$
  \Plus_{{\color{violet} x} = \xstart}^{\xend} \f{x}
  \; = \;
  \f{{\xstart}} \plus \f{a+1} \plus \f{a+2} \plus \cdots \plus \f{{\xend}}
  $$

  The above “quantification” means [[teal:Loop sequentially with]]
  [[brown:loop-bodies f(x)]] [[teal:fused using ⊕]], using [[violet:x as the
  name of the current element]], [[red:starting at a]] and [[cyan:ending at b]].

Result

Hello, in this blog post I'd like to talk about quantifiers.

\[ \def\Plus{\color{teal}\bigoplus} \def\plus{\;\color{teal}\oplus\;} \def\f#1{{\color{brown}f}{\color{violet}(#1)}} \def\xstart{\color{red} a} \def\xend{\color{cyan} b} \]

Anyhow, let's talk about quantifiers… \[ \Plus_{{\color{violet} x} = \xstart}^{\xend} \f{x} \; = \; \f{{\xstart}} \plus \f{a+1} \plus \f{a+2} \plus \cdots \plus \f{{\xend}} \]

The above “quantification” means Loop sequentially with loop-bodies f(x) fused using ⊕, using x as the name of the current element, starting at a and ending at b.

As such, we declare the latex-definitions block type which avoids displaying such extra whitespace in the resulting HTML.

﴾What You Write﴿
Hello, in this blog post I'd like to talk about quantifiers.
 
# First, I'll define some helpers that I use multiple times...   
#+begin_latex-definitions
\def\Plus{\color{teal}\bigoplus}
\def\plus{\;\color{teal}\oplus\;}
\def\f#1{{\color{brown}f}{\color{violet}(#1)}}
\def\xstart{\color{red} a}
\def\xend{\color{cyan} b}
#+end_latex-definitions

Anyhow, let's talk about quantifiers...
$$
\Plus_{{\color{violet} x} = \xstart}^{\xend} \f{x}
\; = \;
\f{{\xstart}} \plus \f{a+1} \plus \f{a+2} \plus \cdots \plus \f{{\xend}}
$$

The above “quantification” means [[teal:Loop sequentially with]]
[[brown:loop-bodies f(x)]] [[teal:fused using ⊕]], using [[violet:x as the
name of the current element]], [[red:starting at a]] and [[cyan:ending at b]].
﴾What You Get﴿

Hello, in this blog post I'd like to talk about quantifiers.

\[\def\Plus{\color{teal}\bigoplus} \def\plus{\;\color{teal}\oplus\;} \def\f#1{{\color{brown}f}{\color{violet}(#1)}} \def\xstart{\color{red} a} \def\xend{\color{cyan} b} \]

Anyhow, let's talk about quantifiers… \[ \Plus_{{\color{violet} x} = \xstart}^{\xend} \f{x} \; = \; \f{{\xstart}} \plus \f{a+1} \plus \f{a+2} \plus \cdots \plus \f{{\xend}} \]

The above “quantification” means Loop sequentially with loop-bodies f(x) fused using ⊕, using x as the name of the current element, starting at a and ending at b.


﴾How It’s Implemented﴿
  (org-defblock latex-definitions nil
  "Declare but do not display the CONTENTS according to the BACKEND."
  (format (pcase backend
            ('html "<p style=\"display:none\">\\[%s\\]</p>")
            (_ "%s"))
          raw-contents))
 
 

Author: Musa Al-hassy

Created: 2025-06-06 Fri 20:38

Validate