<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kyle</title>
    <description>The latest articles on DEV Community by Kyle (@latoandroid).</description>
    <link>https://dev.to/latoandroid</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3945058%2Fb464fedb-8bbf-48cd-be38-38c0e827cb8f.png</url>
      <title>DEV Community: Kyle</title>
      <link>https://dev.to/latoandroid</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/latoandroid"/>
    <language>en</language>
    <item>
      <title>I forked MathLive to make basic chemistry formulas less awkward</title>
      <dc:creator>Kyle</dc:creator>
      <pubDate>Fri, 22 May 2026 02:12:12 +0000</pubDate>
      <link>https://dev.to/latoandroid/i-forked-mathlive-to-make-basic-chemistry-formulas-less-awkward-2pbe</link>
      <guid>https://dev.to/latoandroid/i-forked-mathlive-to-make-basic-chemistry-formulas-less-awkward-2pbe</guid>
      <description>&lt;p&gt;I recently published a small MathLive fork:&lt;/p&gt;

&lt;p&gt;GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/LatoAndroid/mathlive-chemistry" rel="noopener noreferrer"&gt;https://github.com/LatoAndroid/mathlive-chemistry&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;npm:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.npmjs.com/package/mathlive-chemistry" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/mathlive-chemistry&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is not the official MathLive package. It is a focused fork of &lt;code&gt;arnog/mathlive&lt;/code&gt; that adds a bit of chemistry support:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;better editing behavior for common &lt;code&gt;\ce{...}&lt;/code&gt; formulas;&lt;/li&gt;
&lt;li&gt;a small &lt;code&gt;\chemfig{...}&lt;/code&gt; subset for simple organic structures.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most of the coding for this fork was done with AI coding assistance. Not in the "one prompt and ship it" way. I set the scope, read the existing code, pushed back on bad changes, checked the rendering details, and used AI mostly as the implementation/debugging partner. That worked well for this kind of fork because the important part was not inventing a new architecture, but keeping the changes small and compatible.&lt;/p&gt;

&lt;p&gt;The goal is not to build a professional chemistry editor. It is more boring than that: make common chemistry notation behave reasonably inside a MathLive-based formula editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I forked it
&lt;/h2&gt;

&lt;p&gt;MathLive already does the hard parts of math editing well: rendering, selection, serialization, keyboard input, virtual keyboard support, and so on.&lt;/p&gt;

&lt;p&gt;Chemistry is the rough edge.&lt;/p&gt;

&lt;p&gt;Upstream MathLive already supports part of mhchem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tex"&gt;&lt;code&gt;&lt;span class="k"&gt;\ce&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;2H2 + O2 -&amp;gt; 2H2O&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\ce&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;CaCO3 -&amp;gt;[heat] CaO + CO2 &lt;span class="p"&gt;^}&lt;/span&gt;
&lt;span class="k"&gt;\ce&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;SO4&lt;span class="p"&gt;^&lt;/span&gt;2- + Ba&lt;span class="p"&gt;^&lt;/span&gt;2+ -&amp;gt; BaSO4 v&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But &lt;code&gt;\ce{...}&lt;/code&gt; is mostly display-oriented. Internally it is represented by &lt;code&gt;ChemAtom&lt;/code&gt;. The rough flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\ce{...}
  -&amp;gt; read the argument as a balanced string
  -&amp;gt; parse it with the mhchem parser
  -&amp;gt; texify it into normal TeX
  -&amp;gt; parseLatex(tex)
  -&amp;gt; render through MathLive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a good design for display. It preserves the original &lt;code&gt;\ce{...}&lt;/code&gt; source and avoids guessing too much.&lt;/p&gt;

&lt;p&gt;The problem is editing. In some cases the formula still behaves too much like one captured object instead of something that can participate naturally in MathLive editing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I changed for ChemAtom
&lt;/h2&gt;

&lt;p&gt;I did not make every &lt;code&gt;\ce&lt;/code&gt; editable. That would be unsafe. mhchem accepts a lot of syntax, and it can contain embedded TeX. If the fork tries to normalize all of that, it will eventually damage somebody's input.&lt;/p&gt;

&lt;p&gt;So the editable path is intentionally narrow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;only &lt;code&gt;\ce&lt;/code&gt;, not &lt;code&gt;\pu&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;skip empty or very long arguments;&lt;/li&gt;
&lt;li&gt;skip arguments containing &lt;code&gt;\&lt;/code&gt;, &lt;code&gt;$&lt;/code&gt;, or &lt;code&gt;&amp;amp;&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;inspect the mhchem parser output and allow only known simple output types.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The check is roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isEditableChemFormula&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s1"&gt;ce&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[\\&lt;/span&gt;&lt;span class="sr"&gt;$&amp;amp;&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;isEditableChemOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the editable subset, &lt;code&gt;captureSelection&lt;/code&gt; is disabled so the generated MathLive body can be selected and edited recursively.&lt;/p&gt;

&lt;p&gt;Serialization is the other important part. The atom does not just keep returning the original string. It serializes the current body, runs a small normalization step, then wraps it back into:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tex"&gt;&lt;code&gt;&lt;span class="k"&gt;\ce&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;...&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, a test changes the internal body from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tex"&gt;&lt;code&gt;&lt;span class="k"&gt;\ce&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;2H2 + O2 -&amp;gt; 2H2O&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tex"&gt;&lt;code&gt;3&lt;span class="k"&gt;\mathrm&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;H&lt;span class="p"&gt;}_{&lt;/span&gt;2&lt;span class="p"&gt;}&lt;/span&gt;+&lt;span class="k"&gt;\mathrm&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;O&lt;span class="p"&gt;}_{&lt;/span&gt;2&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;\longrightarrow&lt;/span&gt;2&lt;span class="k"&gt;\mathrm&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;H&lt;span class="p"&gt;}_{&lt;/span&gt;2&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;\mathrm&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;O&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and gets this back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tex"&gt;&lt;code&gt;&lt;span class="k"&gt;\ce&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;3H2+O2 -&amp;gt; 2H2O&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is not trying to preserve every space. It is trying to preserve a valid editable chemistry formula.&lt;/p&gt;

&lt;h2&gt;
  
  
  The chemfig part
&lt;/h2&gt;

&lt;p&gt;MathLive does not support &lt;code&gt;\chemfig&lt;/code&gt; upstream. A command like this is unknown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tex"&gt;&lt;code&gt;&lt;span class="k"&gt;\chemfig&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;*6(-=-=-=)&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full chemfig support would be a much bigger project. I only needed a few common structures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tex"&gt;&lt;code&gt;&lt;span class="k"&gt;\chemfig&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;CH&lt;span class="p"&gt;_&lt;/span&gt;3-CH&lt;span class="p"&gt;_&lt;/span&gt;2-OH&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\chemfig&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;CH&lt;span class="p"&gt;_&lt;/span&gt;2=CH&lt;span class="p"&gt;_&lt;/span&gt;2&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\chemfig&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;HC#CH&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\chemfig&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;CH&lt;span class="p"&gt;_&lt;/span&gt;3-C(=O)-OH&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\chemfig&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;*6(-=-=-=)&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\chemfig&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;*6(-=-(-CH&lt;span class="p"&gt;_&lt;/span&gt;3)-=-)&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\chemfig&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;*6(-=-(-NO&lt;span class="p"&gt;_&lt;/span&gt;2)-=-)&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;\chemfig&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;*6(-=-(-COOH)-=-)&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So this fork uses a small parser instead of a full chemfig implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;parseLinear()&lt;/code&gt; for simple chains and single/double/triple bonds;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;parseRing()&lt;/code&gt; for a six-membered ring with simple substituents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result is rendered as SVG and then wrapped in a MathLive &lt;code&gt;Box&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;\chemfig{...}
  -&amp;gt; parseChemfig()
  -&amp;gt; ChemfigStructure
  -&amp;gt; SVG
  -&amp;gt; Box(width / height / depth)
  -&amp;gt; inline MathLive rendering
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not the most elegant thing in the world, but it keeps the change local. I did not want to rework MathLive's layout model just for a small chemistry subset.&lt;/p&gt;

&lt;p&gt;Right now &lt;code&gt;\chemfig{...}&lt;/code&gt; is still an object-level atom for editing. It renders, selects, deletes, and serializes. It is not a visual structure editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;mathlive-chemistry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Usage stays close to MathLive:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mathlive-chemistry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MathfieldElement&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mathlive-chemistry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What this is not
&lt;/h2&gt;

&lt;p&gt;This fork is not:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;official MathLive;&lt;/li&gt;
&lt;li&gt;a full mhchem implementation;&lt;/li&gt;
&lt;li&gt;a full chemfig implementation;&lt;/li&gt;
&lt;li&gt;a professional chemistry drawing tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It does not handle advanced stereochemistry, wedge/dashed bonds, mechanism arrows, electron pushing, or complex research-grade structures.&lt;/p&gt;

&lt;p&gt;Complex &lt;code&gt;\ce{...}&lt;/code&gt; and &lt;code&gt;\pu{...}&lt;/code&gt; still stay conservative and behave as whole objects. That is intentional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Relationship to MathLive
&lt;/h2&gt;

&lt;p&gt;Original project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/arnog/mathlive" rel="noopener noreferrer"&gt;https://github.com/arnog/mathlive&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This fork:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/LatoAndroid/mathlive-chemistry" rel="noopener noreferrer"&gt;https://github.com/LatoAndroid/mathlive-chemistry&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The original project is by Arno Gourdol and MathLive contributors, under the MIT license. This fork keeps the same license.&lt;/p&gt;

&lt;p&gt;There is still plenty to polish, especially around spacing, baselines, and the exact chemfig subset. Those tiny layout details are annoying: a one-pixel difference can look fine in isolation and wrong inside a formula.&lt;/p&gt;

&lt;p&gt;If you are using MathLive and have real chemistry examples that fail, feel free to open an issue:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/LatoAndroid/mathlive-chemistry/issues" rel="noopener noreferrer"&gt;https://github.com/LatoAndroid/mathlive-chemistry/issues&lt;/a&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>opensource</category>
      <category>science</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
