Something Like MDX but with Clojure and Org-Mode for My Personal Blog: Part I


Coding
Pub.Jan 03, 2026Upd.Jan 03, 2026

MDX is a tool lets you write JavaScript and JSX in a Markdown file. It will then compile the Markdown file into a JavaScript file, so that we can use it from other JavaScript code. I always wanted something similar for my personal blog. For some background, the blogging program is written in Clojure/ClojureScript. The blogging program runs on the JVM, it works by statically generating all the HTML and other files so that the result could be hosted on platforms like Github Pages. Posts of my blog are written in org-mode. As the blog program runs on the JVM, directly using MDX is theoratically possible, but probably would create a lot of frictions.

Therefore I took some time to implement something similar. Albeit it is not fully tested, the result is quite satisfactory: I can embed Clojure code directly in an Org file, or use React components defined in Clojure code with native Org syntax. The Org file can then be converted into a Cljc file and used from other Clojure code.

In the following sections I will call it OrgX.

Info

This is part one of the series, in this post I will show the features and in the next post I will describe the process of implementing it

How OrgX Works

Similar to MDX, OrgX works by compiling the OrgX file into a Cljc file. A basic OrgX file would look like this:

#+ORGX: true

/*Hello* from @@orgx:($ :span "OrgX")@@/

#+begin_orgx
($ :div (str "Hello" " World!"))
#+end_orgx

It will be converted into something similar to the following Clojure snippet.

(ns orgx.example
  (:require [uix.core :as uix :refer
             [defui use-state use-effect use-context $]]))

(defui _comp [post-props]
  ($ :<>
     ($ :p {} ($ :i {}
                 ($ :b {} "Hello")
                 " from "
                 ($ :span "OrgX")))
     ($ :div (str "Hello" " World!"))))

(defui component [props]
  ($ _comp (merge (quote {#_"default properties"}) props)))
Note

The blogging program uses uix as its React wrapper, thus the generated Clojure code is using uix. Some uix functions like $ are also imported by default.

As you can see from this example:

  • We can @@orgx:@@ for inline Clojure code and #+begin_orgx for Clojure blocks.
  • The embedded Clojure code are put in a component called _comp, along with other contents in the Org file.
  • The outer component wraps the _comp component, its provides some metadata about this post related to the blogging program. More about this on the following sections.
  • We can use the component component from other Clojure code.

Basic Usage

As we have seen from the earily example, #+begin_orgx and @@orgx:@@ are the most basic structure for standalone and inline Clojure code, respectively.

We can have a clickable button with the following code.

#+begin_orgx
  ;; you can style it using tailwindcss
  ($ :button {:class "bg-sky-600 text-neutral-50 py-1 px-2 rounded-lg"
     :on-click (fn [& _]
                   #?(:cljs (js/alert "clicked!")))}
  "click me")
#+end_orgx

That will be rendered as:

Notice the reader conditional macro #?(:cljs) . The generated Clojure code is in a Cljc file, therefore it pairs well with the server side rendering or static generation. However, it also means we need to put the platform specified code in the corresponding reader conditionals.

We can also use the button inline this way: @@orgx:($ :button {:class "bg-sky-600 text-neutral-50 py-1 px-2 rounded-lg" :on-click (fn [& _] #?(:cljs (js/alert "clicked!")))} "Click Me")@@

The above snippet will be rendered as:

We can also use the button inline this way:

Toplevel Clojure Code

You may have noticed that, all the Clojure code being put in a component means that we can't use Clojure code that only works on the top level like defn or require. To solve this, we can use #+attr_orgx_toplevel: true to mark a OrgX block and make its contents appear in toplevel of the generated Clojure file.

Let's go back to the button example, we can define a my-button component with the following code:

#+attr_orgx_toplevel: true
#+begin_orgx
(defui my-button [{:keys [children callback]}]
    ($ :button {:class "bg-sky-600 text-neutral-50 py-1 px-2 rounded-lg"
       :on-click (fn [& _]
                   (if callback (callback)))}
    children))
#+end_orgx

The defui will be put on the top level in the generated Clojure code, along side something like _comp:

(ns ...)
(defui my-button [{:keys [children callback]}]
  ($ :button {:class "bg-sky-600 text-neutral-50 py-1 px-2 rounded-lg"
              :on-click (fn [& _]
                          #?(:cljs (callback)))}
     children))

(defui _comp ...)

We can then easily use the my-button component with either inline or standalone OrgX block.

Inline OrgX block:
@@orgx:($ my-button {:callback (fn [] #?(:cljs (js/alert "Hello From inline OrgX")))} "From inline OrgX")@@
#+begin_orgx
($ :div "We can also use it in a orgx block"
    ($ my-button {:callback (fn [] #?(:cljs (js/alert "Hello from OrgX block!")))}
         "From OrgX block"))
#+end_orgx

Inline OrgX block:

We can also use it in a orgx block

Import Modules

In MDX, we can import modules with normal import statements. In OrgX, we can do something similar with toplevel OrgX block and the require function.

#+attr_orgx_toplevel: true
#+begin_orgx
(require '[clojure.string :as string])
#+end_orgx

We can also do this with keyword metadata at the beginning of an Org file.

#+ORGX_REQUIRE: [[clojure.string :as string]]
Note

Settings in keyword metadata currently doesn't support reader macros.

Access Component Properties

Similar to MDX, we can access the component's properties with the post-props variable. Some metadata are also provided in the post-props variable.

The following metadata are provided by default:

($ :pre
  (binding [*print-namespace-maps* true]
    (with-out-str (pprint/pprint post-props))))
#:blog{:orgx true,
       :tags [],
       :unlisted false,
       :title
       "Something Like MDX but with Clojure and Org-Mode for My Personal Blog: Part I",
       :show-toc? true,
       :file-path
       "/home/void/Projects/imakira.github.io/blogs/some-like-mdx-but-with-clojure-and-org-mode.org",
       :author nil,
       :email nil,
       :description
       "MDX  is a tool lets you write JavaScript and JSX in a Markdown file. It will then compile the Markdown file into a JavaScript file, so that we can use it f...",
       :id
       "something-like-mdx-but-with-clojure-and-org-mode-for-my-personal-blog--part-i",
       :category "Coding",
       :language "en_US",
       :published-date "2026-01-03T01:47:09+08:00",
       :content
       "<p>\n<a href=\"https://mdxjs.com/\">MDX</a> is a tool lets you write JavaScript and JSX in a Markdown file. It will then compile the Markdown file into a JavaScript file, so that we can use it from other JavaScript code. I always wanted something similar for my personal blog. For some background, the blogging program is written in Clojure/ClojureScript. The blogging program runs on the JVM, it works by statically generating all the HTML and other files so that the result could be hosted on platforms like Github Pages. Posts of my blog are written in <a href=\"https://orgmode.org\">org-mode</a>. As the blog program runs on the JVM, directly using MDX is theoratically possible, but probably would create a lot of frictions.\n</p>\n\n<p>\nTherefore I took some time to implement something similar. Albeit it is not fully tested, the result is quite satisfactory: I can embed Clojure code directly in an Org file, or use React components defined in Clojure code with native Org syntax. The Org file can then be converted into a Cljc file and used from other Clojure code.\n</p>\n\n<p>\nIn the following sections I will call it <code>OrgX</code>.\n</p>\n\n<pre class=\"orgx\" data-wrapper=\"use-comp\" data-wrapper-data=\"info\"><p>\n<i>This is part <b>one</b> of the series, in this post I will show the features and in the next post I will describe the process of implementing it</i>\n</p>\n\n</pre>\n<div id=\"outline-container-how-orgx-works\" class=\"outline-2\">\n<a href=\"#how-orgx-works\"><h2 id=\"how-orgx-works\" class=\"cr-self-reference \">How OrgX Works</h2></a>\n<div class=\"outline-text-2\" id=\"text-how-orgx-works\">\n<p>\nSimilar to MDX, OrgX works by compiling the OrgX file into a <a href=\"https://clojure.org/guides/reader_conditionals\">Cljc</a> file. A basic OrgX file would look like this:\n</p>\n\n<pre class=\"orgx\" data-wrapper=\"use-defui\" data-wrapper-data=\"orgx-example\"><pre class=\"example\" id=\"org541b122\">#+ORGX: true\n\n/*Hello* from @@orgx:($ :span &quot;OrgX&quot;)@@/\n\n#+begin_orgx\n($ :div (str &quot;Hello&quot; &quot; World!&quot;))\n#+end_orgx\n</pre>\n\n</pre>\n\n\n<pre class=\"orgx\">($ tabs {:tab-list [{:name &quot;example.org&quot; :content ($ orgx-example)}]})\n\n</pre>\n\n\n<p>\nIt will be converted into something similar to the following Clojure snippet.\n</p>\n\n<pre class=\"orgx\" data-wrapper=\"use-defui\" data-wrapper-data=\"orgx-cljc\"><div class=\"org-src-container\">\n<pre class=\"cr-highlighted\"><code class=\"lang-clojure\"><html><head></head><body>(<span class=\"hljs-name\"><span class=\"hljs-built_in\">ns</span></span> orgx.example\n  (<span class=\"hljs-symbol\">:require</span> [uix.core <span class=\"hljs-symbol\">:as</span> uix <span class=\"hljs-symbol\">:refer</span>\n             [defui use-state use-effect use-context $]]))\n\n(<span class=\"hljs-name\">defui</span> _comp [post-props]\n  ($ <span class=\"hljs-symbol\">:&lt;&gt;</span>\n     ($ <span class=\"hljs-symbol\">:p</span> {} ($ <span class=\"hljs-symbol\">:i</span> {}\n                 ($ <span class=\"hljs-symbol\">:b</span> {} <span class=\"hljs-string\">&quot;Hello&quot;</span>)\n                 <span class=\"hljs-string\">&quot; from &quot;</span>\n                 ($ <span class=\"hljs-symbol\">:span</span> <span class=\"hljs-string\">&quot;OrgX&quot;</span>)))\n     ($ <span class=\"hljs-symbol\">:div</span> (<span class=\"hljs-name\"><span class=\"hljs-built_in\">str</span></span> <span class=\"hljs-string\">&quot;Hello&quot;</span> <span class=\"hljs-string\">&quot; World!&quot;</span>))))\n\n(<span class=\"hljs-name\">defui</span> component [props]\n  ($ _comp (<span class=\"hljs-name\"><span class=\"hljs-built_in\">merge</span></span> (<span class=\"hljs-name\"><span class=\"hljs-built_in\">quote</span></span> {#_<span class=\"hljs-string\">&quot;default properties&quot;</span>}) props)))\n</body></html></code></pre>\n</div>\n\n</pre>\n\n<pre class=\"orgx\">($ tabs {:tab-list [{:name &quot;orgx/example.cljc&quot; :content ($ orgx-cljc)}]})\n\n</pre>\n\n<pre class=\"orgx\" data-wrapper=\"use-comp\" data-wrapper-data=\"note\"><p>\nThe blogging program uses <a href=\"https://github.com/pitch-io/uix?tab=readme-ov-file\">uix</a> as its React wrapper, thus the generated Clojure code is using uix. Some uix functions like <code>$</code> are also imported by default. \n</p>\n\n</pre>\n\n<p>\nAs you can see from this example:\n</p>\n\n<ul class=\"org-ul\">\n<li>We can <code>@@orgx:@@</code> for inline Clojure code and <code>#+begin_orgx</code> for Clojure blocks.</li>\n<li>The embedded Clojure code are put in a component called <code>_comp</code>, along with other contents in the Org file.</li>\n<li>The outer <code>component</code> wraps the <code>_comp</code> component, its provides some metadata about this post related to the blogging program. More about this on the following sections.</li>\n<li>We can use the <code>component</code> component from other Clojure code.</li>\n</ul>\n</div>\n</div>\n<div id=\"outline-container-basic-usage\" class=\"outline-2\">\n<a href=\"#basic-usage\"><h2 id=\"basic-usage\" class=\"cr-self-reference \">Basic Usage</h2></a>\n<div class=\"outline-text-2\" id=\"text-basic-usage\">\n<p>\nAs we have seen from the earily example, <code>#+begin_orgx</code> and <code>@@orgx:@@</code> are the most basic structure for standalone and inline Clojure code, respectively.\n</p>\n\n<p>\nWe can have a clickable button with the following code.\n</p>\n\n<pre class=\"example\" id=\"org68e10f0\">#+begin_orgx\n  ;; you can style it using tailwindcss\n  ($ :button {:class &quot;bg-sky-600 text-neutral-50 py-1 px-2 rounded-lg&quot;\n     :on-click (fn [&amp; _]\n                   #?(:cljs (js/alert &quot;clicked!&quot;)))}\n  &quot;click me&quot;)\n#+end_orgx\n</pre>\n\n<p>\nThat will be rendered as:\n</p>\n\n<pre class=\"orgx\">  ($ :button {:class &quot;my-2 bg-sky-600 text-neutral-50 py-1 px-2 rounded-lg&quot; :on-click (fn [&amp; _] #?(:cljs (js/alert &quot;clicked!&quot;)))} &quot;Click Me&quot;)\n\n</pre>\n\n<p>\nNotice the <a href=\"https://clojure.org/guides/reader_conditionals\">reader conditional macro</a> <code>#?(:cljs)</code>  . The generated Clojure code is in a Cljc file, therefore it pairs well with the server side rendering or static generation. However, it also means we need to put the platform specified code in the corresponding reader conditionals.\n</p>\n\n<pre class=\"example\" id=\"org5dd6279\">We can also use the button inline this way: @@orgx:($ :button {:class &quot;bg-sky-600 text-neutral-50 py-1 px-2 rounded-lg&quot; :on-click (fn [&amp; _] #?(:cljs (js/alert &quot;clicked!&quot;)))} &quot;Click Me&quot;)@@\n</pre>\n\n<p>\nThe above snippet will be rendered as:\n</p>\n\n<p>\nWe can also use the button inline this way: <code class=\"orgx\">\n($ :button {:class &quot;bg-sky-600 text-neutral-50 py-1 px-2 rounded-lg&quot; :on-click (fn [&amp; _] #?(:cljs (js/alert &quot;clicked!&quot;)))} &quot;Click Me&quot;)\n</code>\n</p>\n</div>\n<div id=\"outline-container-toplevel-clojure-code\" class=\"outline-3\">\n<a href=\"#toplevel-clojure-code\"><h3 id=\"toplevel-clojure-code\" class=\"cr-self-reference \">Toplevel Clojure Code</h3></a>\n<div class=\"outline-text-3\" id=\"text-toplevel-clojure-code\">\n<p>\nYou may have noticed that, all the Clojure code being put in a component means that we can't use Clojure code that only works on the top level like <code>defn</code> or <code>require</code>. To solve this, we can use <code>#+attr_orgx_toplevel: true</code> to mark a OrgX block and make its contents appear in toplevel of the generated Clojure file.\n</p>\n\n<p>\nLet's go back to the button example, we can define a <code>my-button</code> component with the following code:\n</p>\n\n<pre class=\"example\" id=\"orga5c41a8\">#+attr_orgx_toplevel: true\n#+begin_orgx\n(defui my-button [{:keys [children callback]}]\n    ($ :button {:class &quot;bg-sky-600 text-neutral-50 py-1 px-2 rounded-lg&quot;\n       :on-click (fn [&amp; _]\n                   (if callback (callback)))}\n    children))\n#+end_orgx\n</pre>\n\n<pre class=\"orgx\" data-toplevel=\"\">  (defui my-button [{:keys [children callback]}]\n      ($ :button {:class &quot;bg-sky-600 text-neutral-50 py-1 px-2 rounded-lg&quot;\n         :on-click (fn [&amp; _]\n                     (if callback (callback)))}\n      children))\n\n</pre>\n\n<p>\nThe <code>defui</code> will be put on the top level in the generated Clojure code, along side something like <code>_comp</code>:\n</p>\n\n<div class=\"org-src-container\">\n<pre class=\"cr-highlighted\"><code class=\"lang-clojure\"><html><head></head><body>(<span class=\"hljs-name\"><span class=\"hljs-built_in\">ns</span></span> ...)\n(<span class=\"hljs-name\">defui</span> my-button [{<span class=\"hljs-symbol\">:keys</span> [children callback]}]\n  ($ <span class=\"hljs-symbol\">:button</span> {<span class=\"hljs-symbol\">:class</span> <span class=\"hljs-string\">&quot;bg-sky-600 text-neutral-50 py-1 px-2 rounded-lg&quot;</span>\n              <span class=\"hljs-symbol\">:on-click</span> (<span class=\"hljs-name\"><span class=\"hljs-built_in\">fn</span></span> [&amp; _]\n                          #?(<span class=\"hljs-symbol\">:cljs</span> (<span class=\"hljs-name\">callback</span>)))}\n     children))\n\n(<span class=\"hljs-name\">defui</span> _comp ...)\n</body></html></code></pre>\n</div>\n\n<p>\nWe can then easily use the <code>my-button</code> component with either inline or standalone OrgX block.\n</p>\n\n<pre class=\"orgx\" data-wrapper=\"use-defui\" data-wrapper-data=\"mybutton_example\"><pre class=\"example\" id=\"org464ca5d\">Inline OrgX block:\n@@orgx:($ my-button {:callback (fn [] #?(:cljs (js/alert &quot;Hello From inline OrgX&quot;)))} &quot;From inline OrgX&quot;)@@\n#+begin_orgx\n($ :div &quot;We can also use it in a orgx block&quot;\n    ($ my-button {:callback (fn [] #?(:cljs (js/alert &quot;Hello from OrgX block!&quot;)))}\n         &quot;From OrgX block&quot;))\n#+end_orgx\n</pre>\n\n</pre>\n\n<pre class=\"orgx\" data-wrapper=\"use-defui\" data-wrapper-data=\"mybutton_result\"><p>\nInline OrgX block:\n<code class=\"orgx\">\n($ my-button {:callback (fn [] #?(:cljs (js/alert &quot;Hello From inline OrgX&quot;)))} &quot;From inline OrgX&quot;)\n</code>\n</p>\n<pre class=\"orgx\">($ :div &quot;We can also use it in a orgx block&quot;\n    ($ my-button {:callback (fn [] #?(:cljs (js/alert &quot;Hello from OrgX block!&quot;)))}\n         &quot;From OrgX block&quot;))\n\n</pre>\n\n</pre>\n\n<pre class=\"orgx\">($ showcase {:showcase-name &quot;Rendering&quot;}\n  ($ tabs {:tab-list [{:name &quot;Example Usage&quot; :content ($ mybutton_example)}]})\n  ($ mybutton_result))\n\n</pre>\n</div>\n</div>\n<div id=\"outline-container-import-modules\" class=\"outline-3\">\n<a href=\"#import-modules\"><h3 id=\"import-modules\" class=\"cr-self-reference \">Import Modules</h3></a>\n<div class=\"outline-text-3\" id=\"text-import-modules\">\n<p>\nIn MDX, we can import modules with normal <code>import</code> statements. In OrgX, we can do something similar with toplevel OrgX block and the <code>require</code> function.\n</p>\n\n<pre class=\"example\" id=\"org0481cec\">#+attr_orgx_toplevel: true\n#+begin_orgx\n(require '[clojure.string :as string])\n#+end_orgx\n</pre>\n\n<p>\nWe can also do this with keyword metadata at the beginning of an Org file.\n</p>\n\n<pre class=\"example\" id=\"orgb39a43f\">#+ORGX_REQUIRE: [[clojure.string :as string]]\n</pre>\n\n<pre class=\"orgx\" data-wrapper=\"use-comp\" data-wrapper-data=\"note\"><p>\nSettings in keyword metadata currently doesn't support reader macros.\n</p>\n\n</pre>\n</div>\n</div>\n<div id=\"outline-container-access-component-properties\" class=\"outline-3\">\n<a href=\"#access-component-properties\"><h3 id=\"access-component-properties\" class=\"cr-self-reference \">Access Component Properties</h3></a>\n<div class=\"outline-text-3\" id=\"text-access-component-properties\">\n<p>\nSimilar to MDX, we can access the component's properties with the <code>post-props</code> variable. Some metadata are also provided in the <code>post-props</code> variable.\n</p>\n\n<pre class=\"orgx\" data-wrapper=\"use-defui\" data-wrapper-data=\"metadata-example\"><pre class=\"example\" id=\"orgfd838c2\">($ :pre\n  (binding [*print-namespace-maps* true]\n    (with-out-str (pprint/pprint post-props))))\n</pre>\n\n</pre>\n\n<p>\nThe following metadata are provided by default:\n</p>\n\n<pre class=\"orgx\">($ showcase ($ tabs {:tab-list [{:name &quot;Example&quot; :content ($ metadata-example)}]}) ($ :pre (binding [*print-namespace-maps* true] (with-out-str (pprint/pprint post-props)))))\n\n</pre>\n</div>\n</div>\n<div id=\"outline-container-use-other-orgx-files-as-components\" class=\"outline-3\">\n<a href=\"#use-other-orgx-files-as-components\"><h3 id=\"use-other-orgx-files-as-components\" class=\"cr-self-reference \">Use Other OrgX Files as Components</h3></a>\n<div class=\"outline-text-3\" id=\"text-use-other-orgx-files-as-components\">\n<p>\nBy default, OrgX files are compiled as Cljc files and will be put in the <code>orgx</code> namespace. You can import them as described in <a href=\"#import-modules\">Import Modules</a>.\n</p>\n</div>\n</div>\n</div>\n<div id=\"outline-container-use-orgx-with-native-org-syntax\" class=\"outline-2\">\n<a href=\"#use-orgx-with-native-org-syntax\"><h2 id=\"use-orgx-with-native-org-syntax\" class=\"cr-self-reference \">Use OrgX With Native Org Syntax</h2></a>\n<div class=\"outline-text-2\" id=\"text-use-orgx-with-native-org-syntax\">\n</div>\n<div id=\"outline-container-use-components\" class=\"outline-3\">\n<a href=\"#use-components\"><h3 id=\"use-components\" class=\"cr-self-reference \">Use Components</h3></a>\n<div class=\"outline-text-3\" id=\"text-use-components\">\n<p>\nWriting <code>#+begin_orgx</code> and Clojure code must be very tedious every time we want to use a simple component, like <code>note</code> or <code>warning</code> notice blocks.\n</p>\n\n<p>\nTo solve this problem, a new syntax <code>#+orgx_{comp-name}</code> has been introduced.\n</p>\n\n<p>\nFor example:\n</p>\n\n<pre class=\"orgx\" data-wrapper=\"use-defui\" data-wrapper-data=\"orgx_syntax_example\"><pre class=\"example\" id=\"orgb00da0a\">#+begin_orgx_note\nThis is a note\n\n+ /Normal/ Org markups *can* be used inside it\n\n@@orgx:($ :span &quot;You can even nest OrgX syntax in it&quot;)@@\n#+end_orgx_note\n</pre>\n\n</pre>\n\n<pre class=\"orgx\" data-wrapper=\"use-defui\" data-wrapper-data=\"orgx_syntax_result\"><pre class=\"orgx\" data-wrapper=\"use-comp\" data-wrapper-data=\"note\"><p>\nThis is a note\n</p>\n\n<ul class=\"org-ul\">\n<li><i>Normal</i> Org markups <b>can</b> be used inside it</li>\n</ul>\n\n<p>\n<code class=\"orgx\">\n($ :span &quot;You can even nest OrgX syntax in it&quot;)\n</code>\n</p>\n\n</pre>\n\n</pre>\n\n<pre class=\"orgx\">($ showcase {:showcase-name &quot;Rendering&quot;}\n  ($ tabs {:tab-list [{:name &quot;Use Component With Org Syntax&quot; :content ($  orgx_syntax_example)}]})\n  ($ orgx_syntax_result))\n\n</pre>\n\n<p>\nIt works by passing the rendered content to the component <code>note</code> as <code>children</code>. All other Org markups or structures that can be used in an Org environment can also be used here. You can also nested other OrgX structures in it, except for top level Clojure definition and component building, which are not supported.\n</p>\n\n<pre class=\"orgx\" data-wrapper=\"use-comp\" data-wrapper-data=\"note\"><p>\nCurrently we can't pass other properties to the component using this syntax, but that might change in the future.\n</p>\n\n</pre>\n</div>\n</div>\n<div id=\"outline-container-build-components\" class=\"outline-3\">\n<a href=\"#build-components\"><h3 id=\"build-components\" class=\"cr-self-reference \">Build Components</h3></a>\n<div class=\"outline-text-3\" id=\"text-build-components\">\n<p>\nSomething we may want to build a React component out of some Org blocks. Like we may want to pass two Org mode source blocks as arguments to a <code>tabs</code> components.\nTo do this, we just need to add an annotation to the existing <code>#+begin_orgx_{comp-name}</code> syntax:\n</p>\n\n<pre class=\"example\" id=\"org8bac00a\">#+attr_orgx_defui: true\n#+begin_orgx_my-note\n*@@orgx:(or (:note-text props) &quot;Tip&quot;)@@*\n#+begin_orgx\n(:children props)\n#+end_orgx\n#+end_orgx_my-note\n</pre>\n\n<pre class=\"orgx\" data-wrapper=\"use-defui\" data-wrapper-data=\"my-note\"><p>\n<b><code class=\"orgx\">\n(or (:note-text props) &quot;Tip&quot;)\n</code></b>\n</p>\n<pre class=\"orgx\">(:children props)\n\n</pre>\n\n</pre>\n\n\n<p>\nAs shown in the example, we can use normal Org syntax along with OrgX snippets. We can also access the properties with the <code>props</code> variable.\n</p>\n\n<p>\nIn the above example we have defined a <code>my-note</code> component, we can then use it with any aforementioned methods.\n</p>\n\n<pre class=\"orgx\" data-wrapper=\"use-defui\" data-wrapper-data=\"my-note-example\"><pre class=\"example\" id=\"org5e226fd\">#+begin_orgx_my-note\nThis is a note!\n#+end_orgx_my-note\n</pre>\n\n</pre>\n\n<pre class=\"orgx\" data-wrapper=\"use-defui\" data-wrapper-data=\"my-note-result\"><pre class=\"orgx\" data-wrapper=\"use-comp\" data-wrapper-data=\"my-note\"><p>\nThis is a note!\n</p>\n\n</pre>\n\n</pre>\n\n<pre class=\"orgx\">($ showcase {:showcase-name &quot;Rendering&quot;}\n  ($ tabs {:tab-list [{:name &quot;Example Usage&quot; :content ($ my-note-example)}]})\n  ($ my-note-result))\n\n</pre>\n</div>\n</div>\n<div id=\"outline-container-inline-orgx-with-macros\" class=\"outline-3\">\n<a href=\"#inline-orgx-with-macros\"><h3 id=\"inline-orgx-with-macros\" class=\"cr-self-reference \">Inline OrgX with Macros</h3></a>\n<div class=\"outline-text-3\" id=\"text-inline-orgx-with-macros\">\n<p>\nCurrently no similar syntax for inline OrgX is supported, however, it is fairly easy to make life easier with macros. Let's go back to use <code>my-button</code> as the example.\n</p>\n\n<pre class=\"orgx\" data-wrapper=\"use-defui\" data-wrapper-data=\"macro-example\"><p>\nWe can define a macro like:\n</p>\n<pre class=\"example\" id=\"org7ea414f\">#+MACRO: my-button @@orgx:($ my-button {:callback (or $2 nil)} $1)@@\n</pre>\n\n\n<p>\nAnd then use it with:\n</p>\n<pre class=\"example\" id=\"org0715d44\">{{{my-button(&quot;Using Macro&quot;, #?(:cljs (fn [] (js/alert &quot;Hello From Macro!&quot;))))}}}\n</pre>\n\n</pre>\n\n<pre class=\"orgx\" data-wrapper=\"use-defui\" data-wrapper-data=\"macro-result\"><p>\n<code class=\"orgx\">\n($ my-button {:callback (or  #?(:cljs (fn [] (js/alert &quot;Hello From Macro!&quot;))) nil)} &quot;Using Macro&quot;)\n</code>\n</p>\n\n</pre>\n\n<pre class=\"orgx\">($ showcase {:showcase-name &quot;Rendering&quot; :class &quot;lg:grid-cols-[minmax(0,1fr)_minmax(0,0.5fr)]&quot;}\n  ($ tabs {:tab-list [{:name &quot;Inline OrgX with Macro&quot; :content ($ macro-example)}]})\n  ($ macro-result))\n\n</pre>\n</div>\n</div>\n</div>\n<div id=\"outline-container-builtin-components\" class=\"outline-2\">\n<a href=\"#builtin-components\"><h2 id=\"builtin-components\" class=\"cr-self-reference \">Builtin Components</h2></a>\n<div class=\"outline-text-2\" id=\"text-builtin-components\">\n<p>\nThe blogging program provides several builtin components, I will briefly describe their features.\n</p>\n</div>\n<div id=\"outline-container-simple-remarks\" class=\"outline-3\">\n<a href=\"#simple-remarks\"><h3 id=\"simple-remarks\" class=\"cr-self-reference \">Simple Notice Blocks</h3></a>\n<div class=\"outline-text-3\" id=\"text-simple-remarks\">\n<p>\nIt provides most commons blocks like <code>info</code>, <code>note</code>, <code>warn</code> and <code>error</code>.\n</p>\n\n<pre class=\"orgx\" data-wrapper=\"use-defui\" data-wrapper-data=\"remarks_example\"><pre class=\"example\" id=\"org8943d7f\">#+begin_orgx_info\nThis is an info\n#+end_orgx_info\n\n#+begin_orgx_note\nThis is a note\n#+end_orgx_note\n\n#+begin_orgx_warn\nThis is a warn\n#+end_orgx_warn\n\n#+begin_orgx_error\nThis is an error\n#+end_orgx_error\n\n</pre>\n\n</pre>\n\n<pre class=\"orgx\" data-wrapper=\"use-defui\" data-wrapper-data=\"remarks_result\"><pre class=\"orgx\" data-wrapper=\"use-comp\" data-wrapper-data=\"info\"><p>\nThis is an info\n</p>\n\n</pre>\n<pre class=\"orgx\" data-wrapper=\"use-comp\" data-wrapper-data=\"note\"><p>\nThis is a note\n</p>\n\n</pre>\n<pre class=\"orgx\" data-wrapper=\"use-comp\" data-wrapper-data=\"warn\"><p>\nThis is a warn\n</p>\n\n</pre>\n<pre class=\"orgx\" data-wrapper=\"use-comp\" data-wrapper-data=\"error\"><p>\nThis is an error\n</p>\n\n</pre>\n\n</pre>\n\n<pre class=\"orgx\">($ showcase {:showcase-name &quot;Rendering&quot;}\n  ($ tabs {:tab-list [{:name &quot;Remarks&quot; :content ($ remarks_example)}]})\n  ($ remarks_result))\n\n</pre>\n</div>\n</div>\n<div id=\"outline-container-tabs\" class=\"outline-3\">\n<a href=\"#tabs\"><h3 id=\"tabs\" class=\"cr-self-reference \">Tabs</h3></a>\n<div class=\"outline-text-3\" id=\"text-tabs\">\n<pre class=\"orgx\" data-wrapper=\"use-defui\" data-wrapper-data=\"code_tabs\"><div class=\"org-src-container\">\n<pre class=\"cr-highlighted\"><code class=\"lang-clojure\"><html><head></head><body>(<span class=\"hljs-name\">defui</span> tabs [{<span class=\"hljs-symbol\">:keys</span> [tab-list default-table class]}]\n  (<span class=\"hljs-name\"><span class=\"hljs-built_in\">let</span></span> [[selected-tab set-selected-tab!] (<span class=\"hljs-name\">use-state</span> (<span class=\"hljs-name\"><span class=\"hljs-built_in\">or</span></span> default-table\n                                                        (<span class=\"hljs-symbol\">:name</span> (<span class=\"hljs-name\"><span class=\"hljs-built_in\">first</span></span> tab-list))))]\n    ($ <span class=\"hljs-symbol\">:div.my-4.relative.border-sky-300.border-l-2.border-l-neutral-100</span> {<span class=\"hljs-symbol\">:class</span> class}\n       ($ <span class=\"hljs-symbol\">:div.my-0.flex.relative.bg-neutral-100</span>\n          (<span class=\"hljs-name\"><span class=\"hljs-built_in\">map</span></span> (<span class=\"hljs-name\"><span class=\"hljs-built_in\">fn</span></span> [{<span class=\"hljs-symbol\">:keys</span> [name]}]\n                 ($ <span class=\"hljs-symbol\">:button.font-medium.text-neutral-700.bg-neutral-50.py-1.px-2.border-t-2.border-neutral-50.min-w-28.bg-neutral-50.border-t-neutral-100</span>\n                    {<span class=\"hljs-symbol\">:key</span> name\n                     <span class=\"hljs-symbol\">:class</span> (<span class=\"hljs-name\"><span class=\"hljs-built_in\">when</span></span> (<span class=\"hljs-name\"><span class=\"hljs-built_in\">=</span></span> name selected-tab)\n                              <span class=\"hljs-string\">&quot; text-sky-800 border-sky-400 bg-sky-100 border-t-sky-400&quot;</span>)\n                     <span class=\"hljs-symbol\">:on-click</span> (<span class=\"hljs-name\"><span class=\"hljs-built_in\">fn</span></span> []\n                                 #?(<span class=\"hljs-symbol\">:cljs</span>\n                                    (<span class=\"hljs-name\">set-selected-tab!</span> name)))}\n                    name))\n               tab-list))\n       ($ <span class=\"hljs-symbol\">:div.my-0.bg-neutral-50.overflow-hidden.px-2.h-full</span>\n          (<span class=\"hljs-symbol\">:content</span> (<span class=\"hljs-name\"><span class=\"hljs-built_in\">first</span></span> (<span class=\"hljs-name\"><span class=\"hljs-built_in\">filter</span></span> (<span class=\"hljs-name\"><span class=\"hljs-built_in\">fn</span></span> [{<span class=\"hljs-symbol\">:keys</span> [name]}]\n                                     (<span class=\"hljs-name\"><span class=\"hljs-built_in\">=</span></span> name selected-tab))\n                                   tab-list)))))))\n</body></html></code></pre>\n</div>\n\n</pre>\n\n<pre class=\"orgx\" data-wrapper=\"use-defui\" data-wrapper-data=\"tabs_example\"><pre class=\"example\" id=\"org9b21918\">#+begin_orgx\n($ tabs {:tab-list [{:name &quot;tabs.clj&quot; :content ($ code_tabs)}\n                    {:name &quot;tabs usage&quot; :content ($ tabs_example)}]})\n#+end_orgx\n</pre>\n\n</pre>\n\n\n<pre class=\"orgx\" data-wrapper=\"use-defui\" data-wrapper-data=\"tabs_result\"><pre class=\"orgx\">($ tabs {:tab-list [{:name &quot;tabs.clj&quot; :content ($ code_tabs)}\n                      {:name &quot;tabs usage&quot; :content ($ tabs_example)}]})\n\n</pre>\n\n</pre>\n\n<p>\n<code>tabs</code> is like tabs in a browser or a text editor.\n</p>\n<pre class=\"orgx\">($ showcase {:showcase-name &quot;Rendering&quot;}\n  ($ tabs {:tab-list [{:name &quot;tabs usage&quot; :content ($ tabs_example)}]})\n  ($ tabs_result))\n\n</pre>\n</div>\n</div>\n<div id=\"outline-container-split-layout\" class=\"outline-3\">\n<a href=\"#split-layout\"><h3 id=\"split-layout\" class=\"cr-self-reference \">Split Layout</h3></a>\n<div class=\"outline-text-3\" id=\"text-split-layout\">\n<p>\nSplit layout lets you show two things side by side:\n</p>\n\n<pre class=\"example\" id=\"orgda97b66\">#+attr_orgx_defui: true\n#+begin_orgx_split_1\n#+begin_src clojure\n  ($ :button {:class &quot;bg-sky-600 text-neutral-50 py-1 px-2 rounded-lg&quot;\n              :on-click (fn [&amp; _]\n                          #?(:cljs (js/alert &quot;clicked!&quot;)))}\n     &quot;Click Me&quot;)\n#+end_src\n#+end_orgx_split_1\n\n#+attr_orgx_defui: true\n#+begin_orgx_split_2\n#+begin_orgx\n  ($ :button {:class &quot;bg-sky-600 text-neutral-50 py-1 px-2 rounded-lg&quot;\n              :on-click (fn [&amp; _]\n                          #?(:cljs (js/alert &quot;clicked!&quot;)))}\n     &quot;Click Me&quot;)\n#+end_orgx\n#+end_orgx_split_2\n\n#+begin_orgx\n($ split-layout\n($ tabs {:tab-list [{:name &quot;button&quot; :content ($ split_1)}]})\n($ tabs {:tab-list [{:name &quot;showcase&quot; :content ($ split_2)}]}))\n#+end_orgx\n</pre>\n\n<p>\nWill be rendered as:\n</p>\n\n\n<pre class=\"orgx\" data-wrapper=\"use-defui\" data-wrapper-data=\"split_1\"><div class=\"org-src-container\">\n<pre class=\"cr-highlighted\"><code class=\"lang-clojure\"><html><head></head><body>($ <span class=\"hljs-symbol\">:button</span> {<span class=\"hljs-symbol\">:class</span> <span class=\"hljs-string\">&quot;bg-sky-600 text-neutral-50 py-1 px-2 rounded-lg&quot;</span>\n            <span class=\"hljs-symbol\">:on-click</span> (<span class=\"hljs-name\"><span class=\"hljs-built_in\">fn</span></span> [&amp; _]\n                        #?(<span class=\"hljs-symbol\">:cljs</span> (<span class=\"hljs-name\">js/alert</span> <span class=\"hljs-string\">&quot;clicked!&quot;</span>)))}\n   <span class=\"hljs-string\">&quot;Click Me&quot;</span>)\n</body></html></code></pre>\n</div>\n\n</pre>\n\n<pre class=\"orgx\" data-wrapper=\"use-defui\" data-wrapper-data=\"split_2\"><pre class=\"orgx\">  ($ :button {:class &quot;bg-sky-600 text-neutral-50 py-1 px-2 rounded-lg&quot;\n              :on-click (fn [&amp; _]\n                          #?(:cljs (js/alert &quot;clicked!&quot;)))}\n     &quot;Click Me&quot;)\n\n</pre>\n\n</pre>\n\n<pre class=\"orgx\">($ split-layout\n($ tabs {:tab-list [{:name &quot;button&quot; :content ($ split_1)}]})\n($ tabs {:tab-list [{:name &quot;showcase&quot; :content ($ split_2)}]}))\n\n</pre>\n</div>\n</div>\n<div id=\"outline-container-showcase\" class=\"outline-3\">\n<a href=\"#showcase\"><h3 id=\"showcase\" class=\"cr-self-reference \">Showcase</h3></a>\n<div class=\"outline-text-3\" id=\"text-showcase\">\n<p>\nShowcase is a simple wrapper over <code>split-layout</code> and <code>tabs</code>. The <code>showcase</code> component is used extensive in this post, such as the example in <a href=\"#simple-remarks\">Simple Notice Blocks</a> is coded as:\n</p>\n\n<pre class=\"example\" id=\"org7ed5775\">#+attr_orgx_defui: true\n#+begin_orgx_remarks_example\n#+begin_example\n#+begin_orgx_info\nThis is an info\n#+end_orgx_info\n\n#+begin_orgx_note\nThis is a note\n#+end_orgx_note\n\n#+begin_orgx_warn\nThis is a warn\n#+end_orgx_warn\n\n#+begin_orgx_error\nThis is an error\n#+end_orgx_error\n\n#+end_example\n#+end_orgx_remarks_example\n\n#+attr_orgx_defui: true\n#+begin_orgx_remarks_result\n#+begin_orgx_info\nThis is an info\n#+end_orgx_info\n#+begin_orgx_note\nThis is a note\n#+end_orgx_note\n#+begin_orgx_warn\nThis is a warn\n#+end_orgx_warn\n#+begin_orgx_error\nThis is an error\n#+end_orgx_error\n#+end_orgx_remarks_result\n\n#+begin_orgx\n($ showcase {:showcase-name &quot;Rendering&quot;}\n  ($ tabs {:tab-list [{:name &quot;Remarks&quot; :content ($ remarks_example)}]})\n  ($ remarks_result))\n#+end_orgx\n</pre>\n</div>\n</div>\n</div>\n<div id=\"outline-container-conclusions\" class=\"outline-2\">\n<a href=\"#conclusions\"><h2 id=\"conclusions\" class=\"cr-self-reference \">Conclusions</h2></a>\n<div class=\"outline-text-2\" id=\"text-conclusions\">\n</div>\n<div id=\"outline-container-caveats\" class=\"outline-3\">\n<a href=\"#caveats\"><h3 id=\"caveats\" class=\"cr-self-reference \">Caveats</h3></a>\n<div class=\"outline-text-3\" id=\"text-caveats\">\n<p>\nAlthough I have extensively used it in this demostration and will definitely use it in the future, there are some notably deficiency with the current implementation.\n</p>\n\n<ul class=\"org-ul\">\n<li>You need to read the logging to understand the problem if anything goes wrong. If you want to do SSR or SSG, the fact that the same code must be run on the JVM and the browser and produce the same result adds complexity.</li>\n<li>The syntax still feels cumbersome in many cases, but that might be improved in the future.</li>\n<li>The current implementation requires Emacs itself to convert the Org files into HTML, and Clojure code will then process the HTML. The most notably advantage of this method is that we can use the (basically) the full power of Org mode. However, it is also a lot of moving parts.</li>\n</ul>\n</div>\n</div>\n<div id=\"outline-container-future-plans\" class=\"outline-3\">\n<a href=\"#future-plans\"><h3 id=\"future-plans\" class=\"cr-self-reference \">Future Plans</h3></a>\n<div class=\"outline-text-3\" id=\"text-future-plans\">\n<p>\nI will describe the process of implement it in the next post of this series. Current the code is still coupled with my blogging program. I'm planning on make it a separate library in the future.\n</p>\n</div>\n</div>\n</div>\n",
       :orgx-require [[clojure.pprint :as pprint]],
       :modified-date "2026-01-03T15:10:59+08:00"}

Use Other OrgX Files as Components

By default, OrgX files are compiled as Cljc files and will be put in the orgx namespace. You can import them as described in Import Modules.

Use OrgX With Native Org Syntax

Use Components

Writing #+begin_orgx and Clojure code must be very tedious every time we want to use a simple component, like note or warning notice blocks.

To solve this problem, a new syntax #+orgx_{comp-name} has been introduced.

For example:

#+begin_orgx_note
This is a note

+ /Normal/ Org markups *can* be used inside it

@@orgx:($ :span "You can even nest OrgX syntax in it")@@
#+end_orgx_note
Note

This is a note

  • Normal Org markups can be used inside it

You can even nest OrgX syntax in it

It works by passing the rendered content to the component note as children. All other Org markups or structures that can be used in an Org environment can also be used here. You can also nested other OrgX structures in it, except for top level Clojure definition and component building, which are not supported.

Note

Currently we can't pass other properties to the component using this syntax, but that might change in the future.

Build Components

Something we may want to build a React component out of some Org blocks. Like we may want to pass two Org mode source blocks as arguments to a tabs components. To do this, we just need to add an annotation to the existing #+begin_orgx_{comp-name} syntax:

#+attr_orgx_defui: true
#+begin_orgx_my-note
*@@orgx:(or (:note-text props) "Tip")@@*
#+begin_orgx
(:children props)
#+end_orgx
#+end_orgx_my-note

As shown in the example, we can use normal Org syntax along with OrgX snippets. We can also access the properties with the props variable.

In the above example we have defined a my-note component, we can then use it with any aforementioned methods.

#+begin_orgx_my-note
This is a note!
#+end_orgx_my-note

Tip

This is a note!

Inline OrgX with Macros

Currently no similar syntax for inline OrgX is supported, however, it is fairly easy to make life easier with macros. Let's go back to use my-button as the example.

We can define a macro like:

#+MACRO: my-button @@orgx:($ my-button {:callback (or $2 nil)} $1)@@

And then use it with:

{{{my-button("Using Macro", #?(:cljs (fn [] (js/alert "Hello From Macro!"))))}}}

Builtin Components

The blogging program provides several builtin components, I will briefly describe their features.

Simple Notice Blocks

It provides most commons blocks like info, note, warn and error.

#+begin_orgx_info
This is an info
#+end_orgx_info

#+begin_orgx_note
This is a note
#+end_orgx_note

#+begin_orgx_warn
This is a warn
#+end_orgx_warn

#+begin_orgx_error
This is an error
#+end_orgx_error

Info

This is an info

Note

This is a note

Warning

This is a warn

Error

This is an error

Tabs

tabs is like tabs in a browser or a text editor.

#+begin_orgx
($ tabs {:tab-list [{:name "tabs.clj" :content ($ code_tabs)}
                    {:name "tabs usage" :content ($ tabs_example)}]})
#+end_orgx
(defui tabs [{:keys [tab-list default-table class]}]
  (let [[selected-tab set-selected-tab!] (use-state (or default-table
                                                        (:name (first tab-list))))]
    ($ :div.my-4.relative.border-sky-300.border-l-2.border-l-neutral-100 {:class class}
       ($ :div.my-0.flex.relative.bg-neutral-100
          (map (fn [{:keys [name]}]
                 ($ :button.font-medium.text-neutral-700.bg-neutral-50.py-1.px-2.border-t-2.border-neutral-50.min-w-28.bg-neutral-50.border-t-neutral-100
                    {:key name
                     :class (when (= name selected-tab)
                              " text-sky-800 border-sky-400 bg-sky-100 border-t-sky-400")
                     :on-click (fn []
                                 #?(:cljs
                                    (set-selected-tab! name)))}
                    name))
               tab-list))
       ($ :div.my-0.bg-neutral-50.overflow-hidden.px-2.h-full
          (:content (first (filter (fn [{:keys [name]}]
                                     (= name selected-tab))
                                   tab-list)))))))

Split Layout

Split layout lets you show two things side by side:

#+attr_orgx_defui: true
#+begin_orgx_split_1
#+begin_src clojure
  ($ :button {:class "bg-sky-600 text-neutral-50 py-1 px-2 rounded-lg"
              :on-click (fn [& _]
                          #?(:cljs (js/alert "clicked!")))}
     "Click Me")
#+end_src
#+end_orgx_split_1

#+attr_orgx_defui: true
#+begin_orgx_split_2
#+begin_orgx
  ($ :button {:class "bg-sky-600 text-neutral-50 py-1 px-2 rounded-lg"
              :on-click (fn [& _]
                          #?(:cljs (js/alert "clicked!")))}
     "Click Me")
#+end_orgx
#+end_orgx_split_2

#+begin_orgx
($ split-layout
($ tabs {:tab-list [{:name "button" :content ($ split_1)}]})
($ tabs {:tab-list [{:name "showcase" :content ($ split_2)}]}))
#+end_orgx

Will be rendered as:

($ :button {:class "bg-sky-600 text-neutral-50 py-1 px-2 rounded-lg"
            :on-click (fn [& _]
                        #?(:cljs (js/alert "clicked!")))}
   "Click Me")

Showcase

Showcase is a simple wrapper over split-layout and tabs. The showcase component is used extensive in this post, such as the example in Simple Notice Blocks is coded as:

#+attr_orgx_defui: true
#+begin_orgx_remarks_example
#+begin_example
#+begin_orgx_info
This is an info
#+end_orgx_info

#+begin_orgx_note
This is a note
#+end_orgx_note

#+begin_orgx_warn
This is a warn
#+end_orgx_warn

#+begin_orgx_error
This is an error
#+end_orgx_error

#+end_example
#+end_orgx_remarks_example

#+attr_orgx_defui: true
#+begin_orgx_remarks_result
#+begin_orgx_info
This is an info
#+end_orgx_info
#+begin_orgx_note
This is a note
#+end_orgx_note
#+begin_orgx_warn
This is a warn
#+end_orgx_warn
#+begin_orgx_error
This is an error
#+end_orgx_error
#+end_orgx_remarks_result

#+begin_orgx
($ showcase {:showcase-name "Rendering"}
  ($ tabs {:tab-list [{:name "Remarks" :content ($ remarks_example)}]})
  ($ remarks_result))
#+end_orgx

Conclusions

Caveats

Although I have extensively used it in this demostration and will definitely use it in the future, there are some notably deficiency with the current implementation.

  • You need to read the logging to understand the problem if anything goes wrong. If you want to do SSR or SSG, the fact that the same code must be run on the JVM and the browser and produce the same result adds complexity.
  • The syntax still feels cumbersome in many cases, but that might be improved in the future.
  • The current implementation requires Emacs itself to convert the Org files into HTML, and Clojure code will then process the HTML. The most notably advantage of this method is that we can use the (basically) the full power of Org mode. However, it is also a lot of moving parts.

Future Plans

I will describe the process of implement it in the next post of this series. Current the code is still coupled with my blogging program. I'm planning on make it a separate library in the future.