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
- 2. Block and link types provided by this package
- 2.1. Boxed Text —Calling out super duper info
- 2.2. Nice Keystroke Renditions: kbd:C-h_h
- 2.3. Folded Details —Let the user see stuff only if they're interested
- 2.4. Remark —Inline footnotes, viz HTML tooltips and LaTeX notes in the margin
- 2.5. Parallel ---Place ideas side-by-side, possibly with a separator
- 2.6.
latex-definitions
for hiding LaTeX declarations in HTML
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
andorg-deflink
macros — familiardefun
-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 todefun
.
- Create powerful custom blocks with the
🧱 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.
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> " (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.
html:</div><div style="width: 25%; margin: 10px; border-right:4px none; float: left;">
, when the specification determines 𝓃-many columns.
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.
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))