by Gatongone
Flip LaTeX formulas in your notes horizontally, vertically, or both, while keeping them real typeset math that stays selectable and searchable.
Math Mirror
Mirror LaTeX formulas in Obsidian: $\mirrorh{...}$ flips a formula left/right,
$\mirrorv{...}$ flips it upside down.
$\mirrorh{\frac{a}{b}}$ whole formula, horizontal reflection
$\mirrorv{\vec{v}}$ whole formula, vertical reflection
$$\mirrorh{\begin{cases}a&b\\c&d\end{cases}}$$
$\mirrorhv{\int_0^1 f(x)\,dx}$ both axes (180° rotation)
$\mirrorh{\mirrorv{x}}$ nesting composes, same as \mirrorhv
$x + \mirrorv{\frac{a}{b}} = y$ only the fraction is flipped
The reflection is a CSS transform (scaleX(-1) / scaleY(-1)) applied to the
real MathJax output, not an image and not reordered characters, so mirrored
formulas stay selectable, searchable, theme-aware and vector-sharp when printed.
Requires Obsidian 1.13.7 or later (that is the version the MathJax internals it relies on were verified against). Works on desktop and mobile.
Install
Install Math Mirror from the community plugin directory: Settings → Community plugins → Browse, search for "Math Mirror", then Install and Enable.
Before the directory listing is live, either use BRAT
with the repository Gatongone/MathMirror, or build it yourself:
npm install
npm run build
npm run deploy # copies into C:\Projects\Obsidian\Notes\.obsidian\plugins\math-mirror
Use $env:OBSIDIAN_VAULT = "D:\MyVault"; npm run deploy for another vault, or copy
manifest.json, main.js and styles.css into
<vault>/.obsidian/plugins/math-mirror/ yourself, then enable Math Mirror in
Settings → Community plugins. The folder name has to match the plugin id,
math-mirror.
Math that is already on screen when the plugin is enabled is not re-rendered by Obsidian (post processors only run while rendering). Reopen the note or toggle between editing and reading mode.
Privacy
Math Mirror does not use the network, does not collect telemetry, does not show ads, and does not require an account. It only reads the notes you have open, works with the MathJax build Obsidian already ships, and writes nothing outside the rendered math in the DOM.
Principle
Two cases, decided per formula:
- A macro around the whole formula is removed from the TeX and turned into a
reflection of the rendered formula element itself (
math-mirror+ axis classes on the.mathelement). Nested wrappers compose as reflections do - the same axis twice cancels - so\mirrorh{\mirrorv{x}}is 180° and\mirrorh{\mirrorh{x}}is the identity. - A macro around part of a formula (
$x + \mirrorv{\frac{a}{b}} = y$) is rewritten to MathJax's own\class{mjx-mirror-v}{...}: MathJax then puts that class on the rendered element of exactly that part (mjx-mfrac,mjx-mrow, …, which its stylesheet already makesdisplay: inline-block) and CSS reflects it. The class name must start withmjx-, because MathJax's Safe extension filters out every other class.
Those two cases are implemented differently per view, because Obsidian renders math in two very different ways:
- Reading view, embedded notes, hover previews, canvas — the markdown renderer
emits
<span class="math math-inline">TeX</span>(delimiters already stripped) and typesets it later. The plugin's markdown post processor registers withsortOrder = -1, i.e. before Obsidian's math processor (sortOrder = 0), so it always sees the raw TeX, whatever the MathJax loading state: it rewrites the TeX and marks the element when the whole formula is wrapped. MathJax then typesets into that same element, so the CSS transform applies to the real output. - Live Preview — math is a CodeMirror widget: no post processors run, the DOM
holds only MathJax output, and Obsidian disables MathJax's assistive MathML, so
the TeX cannot be recovered from the DOM. The plugin instead maps the widget back
to the document with
view.posAtDOM, reads the$...$source from the document, re-renders the rewritten TeX withrenderMath, swaps the MathJax node and marks the widget. Obsidian's widget stays in charge of layout, click-to-edit and the block edit button. - Anything else that renders the macro itself — LaTeX Suite's math popup
preview, another plugin's preview, or reading view math that Obsidian typeset
before the post processor saw it. There the macro reaches MathJax, which does not
know it, and MathJax draws its name in red. A DOM observer notices that error
rendering, decodes the macro name from the glyph elements, deletes the red text
and puts the mirror class on the group that follows it - the argument, including
when a script is attached to it (
$\mirrorh{\mathcal{f}}_x$mirrors thefand leaves the subscript alone). Genuinely undefined macros, and a macro written without braces, keep their red error.
Everything is verified against Obsidian 1.13.7 (MathJax 3.2.2); the evidence is
collected in docs/obsidian-math-internals.md.
Settings
Mirror in Live Preview · Debug logging.
Limitations
- Only
\mirrorh,\mirrorv,\mirrorhv(and\mirrorvh) are recognised, case sensitively. - Braces must balance:
$\mirrorh{x$cannot be parsed, so the formula keeps MathJax's red error rendering (with a console message when debug logging is on). - Reading view math that was already typeset before the plugin saw it, and math rendered by another plugin, is repaired after the fact rather than rewritten first: the red macro name disappears and the reflection is applied a moment later, in the same frame.
- Partial reflections use the class names
mjx-mirror-h,mjx-mirror-vandmjx-mirror-hv; a\class{mjx-mirror-...}written by hand is treated as a mirror request as well. - The rescue pass understands MathJax's undefined-macro rendering as Obsidian 1.13.7 (MathJax 3.2.2) produces it. If a future version changes that markup, the rescue pass stops working - all other paths are unaffected.
Development
npm run dev (watch) · npm run build (type check + production bundle) ·
npm test (62 unit and integration tests, including a real CodeMirror 6 editor in
jsdom) · npm run deploy (copy the built plugin into a vault).
main.js and styles.css are build artifacts and are not committed; they are
attached to each release.
Releases
.github/workflows/release.yml builds and tests the plugin on every tag, then
opens a draft release with the three files Obsidian downloads. Push a tag that
matches manifest.json:
git tag -a 1.0.0 -m "1.0.0"
git push origin 1.0.0
Review the draft release, add the notes from CHANGELOG.md and
publish it. See docs/submission.md for the community
directory checklist.
License
Math Mirror is released under the MIT Copyright (c) 2026, Gatongone.
For plugin developers
Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.