Get started.
Start with the smallest working setup, then use the examples and playground when you need reactions, highlighting, or rendering tweaks for denser structures.
Install from NPM
Available as smiles-drawer and works with npm, pnpm, and yarn.
Drop in a script tag
For browser-only rendering, the CDN build is enough.
Use the site itself
Compare the docs, examples, and playground side by side.
Choose between apply(), draw(), and parse() depending on how much control you need.
apply() is the fastest start, draw() is the default for dynamic UIs, and parse() is only needed when you want the parse tree.
Apply static molecules
Use this when all SMILES strings are known at page load and every drawing can share the same dimensions.
<script src="https://unpkg.com/smiles-drawer@2/dist/smiles-drawer.min.js"></script>
<canvas data-smiles="CN1C=NC2=C1C(=O)N(C(=O)N2C)C"></canvas>
<canvas data-smiles="c1(C=O)cc(OC)c(O)cc1"></canvas>
<script>
SmilesDrawer.apply({ width: 550, height: 450 });
</script> Use apply() when you want a minimal setup and don’t need per-target sizing.
Draw dynamically
Create a SmiDrawer when user input changes or when molecules and reactions need different targets.
<input id="input" value="O=C(O)CNCP(=O)(O)O" />
<svg id="output"></svg>
<script src="https://unpkg.com/smiles-drawer@2/dist/smiles-drawer.min.js"></script>
<script>
var drawer = new SmilesDrawer.SmiDrawer({ width: 550, height: 450 });
var input = document.getElementById('input');
function render(smiles) {
drawer.draw(smiles, '#output');
}
input.addEventListener('input', function() {
render(input.value);
});
render(input.value);
</script> The default for application UIs, playgrounds, and framework components.
Parse then draw
Use SmilesDrawer.parse() when you need the parse result before rendering, or when you want to choose between Drawer and SvgDrawer yourself.
var drawer = new SmilesDrawer.SvgDrawer({ width: 550, height: 450 });
SmilesDrawer.parse(smiles, function(result) {
drawer.draw(result, document.getElementById('output'), 'light');
}, function(error) {
console.error(error);
}); Reaction SMILES use reactants>reagents>products.
Use SmilesDrawer.SmiDrawer.apply() or drawer.draw() when you want the library to parse and render the reaction in one step.
<svg data-smiles="[Pb]>>[Au]"></svg>
<script src="https://unpkg.com/smiles-drawer@2/dist/smiles-drawer.min.js"></script>
<script>
SmilesDrawer.SmiDrawer.apply();
</script>
To label the reaction arrow, append a JSON-in-SMILES tail to the reaction string wrapped in __…__. The library strips and parses it before rendering. Example:
drawer.draw(
"[Pb]>>[Au] __{'textAboveArrow': 'MAGIC', 'textBelowArrow': '42°C'}__",
'#output'
); Weighted overlays take one numeric value per heavy atom.
Positive values render green, negative values render red, and the intensity scales with the magnitude of the weight.
var drawer = new SmilesDrawer.SmiDrawer({ width: 550, height: 450 });
drawer.draw(
'CCCCCCN',
'#weights-output',
'light',
null,
null,
[-3, -2, -1, 0, 1, 2, 3]
); For SVG output, weights look best on a light background.
Weight reactants and products separately in a single reaction.
Instead of a flat array, reaction weights take an object keyed by reactants, reagents, and products. Each value is an array of arrays: one inner array per molecule in that category, with one weight per heavy atom.
var weights = {
reactants: [[1, 0], [0, -1, 0]],
products: [[1, -1, 0], [0, 0]]
};
var drawer = new SmilesDrawer.SmiDrawer({ width: 550, height: 450 });
drawer.draw('CF.FC#N>>CC#N.FF', '#output', 'light', null, null, weights);
The apply() path uses the attributes data-smiles-reactant-weights and data-smiles-product-weights, separating per-molecule weight arrays with semicolons and per-atom values with commas. Reagents accept the same shape but aren't drawn as structures, so weights on them have no visible effect. Draw to SVG on a light background.
Reactants and products get independent weight arrays; red is negative, green is positive.
Class-based color circles under individual atoms.
Unlike weights (which visualize per-atom magnitudes), highlights are categorical. Attach a class number to an atom in the SMILES with the :N syntax, then pass an array of [class, color] pairs when you draw.
var smiles = 'CCCC[CH2:1][CH2:2]CC[NH2:2]';
var highlights = [[1, '#ffd34d'], [2, '#6fdc8c']];
SmilesDrawer.parse(smiles, function(tree) {
var drawer = new SmilesDrawer.SvgDrawer({ width: 550, height: 450 });
drawer.draw(tree, '#output', 'light', null, false, highlights);
});
For canvas, pass the highlight array as the fifth argument to Drawer.draw() instead. Highlights require the parse-then-draw path and don't apply to reactions.
Classes from the SMILES: [CH2:1] is yellow, [CH2:2] and [NH2:2] share green.
Most of the site’s rendering fixes come down to a small set of options.
For this site, the biggest levers are bondLength, fontSizeLarge, padding, and whether compactDrawing or explicitHydrogens are enabled.
Site default render preset
These are the starting values used on the website and in the playground because they hold up better for most molecules than the larger legacy baseline.
var options = {
scale: 0,
width: 550,
height: 450,
bondLength: 19,
bondThickness: 1.1,
shortBondLength: 0.6,
bondSpacing: 3.2,
fontSizeLarge: 6.3
}; Library defaults are shown below. The site preset above overrides the numeric ones so denser structures stay legible.
| Option | Default | Description |
|---|---|---|
| width / height | 500 / 500 | Drawing dimensions in pixels. |
| padding | 10 | Whitespace inside the drawing bounds. |
| bondLength | 30.0 | Main scale control for how large the structure feels. |
| bondThickness | 1.0 | Stroke width for bonds. |
| shortBondLength | 0.8 | Relative length for shortened bonds (e.g. inner double bonds). |
| bondSpacing | 5.1 | Spacing between double and triple bonds. |
| fontSizeLarge | 11 | Large font size in pt, used for element labels. |
| fontSizeSmall | 3 | Small font size in pt, reserved for subscripts and numbers. |
| atomVisualization | 'default' | 'default', 'balls', or 'allballs'. |
| compactDrawing | true | Abbreviates terminals and pseudo-elements when possible. |
| terminalCarbons | false | Show terminal carbon labels such as CH3. |
| explicitHydrogens | true | Render explicit hydrogens from the SMILES input. |
| isomeric | true | Draw stereochemistry when the input describes it. |
| overlapSensitivity | 0.42 | Threshold that controls how aggressively atom overlaps get resolved. |
| overlapResolutionIterations | 1 | Number of layout passes dedicated to resolving overlapping atoms. |
| experimentalSSSR | false | Alternative ring detection for stretched or awkward large rings. |
| themes | built-ins | Custom color schemes keyed by name. See the custom themes card below. |
| debug | false | Draw debug labels on atoms and bonds. |
Too much abbreviation
Set compactDrawing to false.
Unwanted hydrogens
Set explicitHydrogens to false.
Large ring issues
Try experimentalSSSR when rings stretch or overlap badly.
Built-in themes
Pass a theme name as the final argument to apply() or draw().
SmilesDrawer.apply(options, 'canvas[data-smiles]', 'matrix');
var drawer = new SmilesDrawer.SmiDrawer(options);
drawer.draw(smiles, '#output', 'solarized'); Custom themes
Provide a themes object in your options, then draw with the custom name like any built-in preset.
var options = {
width: 550,
height: 450,
themes: {
'my-theme': {
C: '#1f2937',
O: '#dc2626',
N: '#2563eb',
H: '#6b7280',
BACKGROUND: '#ffffff'
}
}
}; The integration pattern is the same everywhere.
Create a render target, draw when the component mounts, and redraw when the SMILES string or relevant options change.
useEffect around a persistent SVG refWrapper pattern
import { useEffect, useRef } from 'react';
import SmilesDrawer from 'smiles-drawer';
export default function MoleculeFigure({ smiles, theme = 'light' }) {
const svgRef = useRef(null);
useEffect(() => {
if (!svgRef.current || !smiles) return;
const drawer = new SmilesDrawer.SmiDrawer({ width: 320, height: 220 });
drawer.draw(smiles, svgRef.current, theme);
}, [smiles, theme]);
return <svg ref={svgRef} />;
} Custom elements come with a few gotchas.
attributeChangedCallbackfires on construction too, so guard drawing behind ablockDrawingflag and draw inconnectedCallback.- SmilesDrawer sets the canvas
width/heightduring draw, which would re-trigger the callback. Skipping draws when values are unchanged prevents the infinite loop. - Expose a
delayDrawing(fn)helper so callers can batch attribute updates into one redraw. - Autonomous elements are
display: inlineby default and pick up a few stray pixels of height from line-height. Setdisplay: inline-blockandline-height: 0.
The customized built-in variant (extends: 'canvas') uses the same lifecycle but skips the shadow DOM, so you can write <canvas is="smiles-drawer">. Safari doesn't support it natively, so polyfill if you need it.
Autonomous custom element
class SmilesDrawerElement extends HTMLElement {
static get observedAttributes() {
return ['smiles', 'width', 'height', 'theme'];
}
constructor() {
super();
this.blockDrawing = true;
const shadow = this.attachShadow({ mode: 'open' });
shadow.appendChild(document.createElement('canvas'));
}
connectedCallback() {
if (this.blockDrawing) {
this.blockDrawing = false;
this.draw();
}
}
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue !== newValue) {
this.modified = true;
this.draw();
}
}
delayDrawing(callback) {
this.blockDrawing = true;
callback();
this.blockDrawing = false;
if (this.modified) this.draw();
}
draw() {
if (this.blockDrawing) return;
SmilesDrawer.parse(this.getAttribute('smiles'), (tree) => {
const canvas = this.shadowRoot.firstElementChild;
const drawer = new SmilesDrawer.Drawer({
width: parseInt(this.getAttribute('width')) || 500,
height: parseInt(this.getAttribute('height')) || 500
});
drawer.draw(tree, canvas, this.getAttribute('theme') || 'light');
});
this.modified = false;
}
}
customElements.define('smiles-drawer', SmilesDrawerElement); <style>
smiles-drawer { display: inline-block; line-height: 0; }
</style>
<smiles-drawer smiles="CCCC" width="300" height="200"></smiles-drawer> Pick the right entrypoint when you need more control than the quick start.
SmilesDrawer exposes several drawer classes. These are the ones worth calling directly; the rest are internal helpers or deprecated. For the full catalog, see the source.
Apply
SmilesDrawer.apply(options, selector = "canvas[data-smiles]", themeName = "light", onError = null) The smallest entrypoint. Canvas-only, single molecules, no reactions. Reads data-smiles from each matching element.
new SmilesDrawer.SmiDrawer(moleculeOptions, reactionOptions).apply(attribute = "data-smiles", theme = "light", onSuccess, onError) The more capable apply. Handles molecules and reactions, and picks up the attributes data-smiles-theme, data-smiles-options, data-smiles-weights, and the reaction-weight attributes.
A current bug prevents this from drawing to canvas elements. Target svgs instead.
Draw
new SmilesDrawer.SmiDrawer(moleculeOptions, reactionOptions).draw(smiles, target, theme = "light", onSuccess, onError, weights = null) The recommended draw. Accepts both molecule and reaction SMILES. target can be an element, "svg" / "canvas" / "img" to create a new element, a CSS selector, or null for a new SVG.
Passing an HTMLCanvasElement directly doesn't currently work. Pass a selector instead.
new SmilesDrawer.SvgDrawer(options).draw(data, target, themeName = "light", weights = null, infoOnly = false, highlight_atoms = [], weightsNormalized = false) Low-level SVG renderer. Takes a parsed molecule (from SmilesDrawer.parse()) and draws to an existing svg. This is where molecule weights and atom highlighting run.
new SmilesDrawer.Drawer(options).draw(data, target, themeName = "light", infoOnly = false, highlight_atoms = []) Canvas equivalent of SvgDrawer. Internally delegates to SvgDrawer and copies the result.
new SmilesDrawer.ReactionDrawer(reactionOptions, moleculeOptions).draw(reaction, target, theme = "light", weights = null, textAbove, textBelow, infoOnly) Renders a pre-parsed reaction. Note the constructor argument order: reaction options come before molecule options here. There's currently no way to control the overall drawing size from here.
Parse
SmilesDrawer.parse(smiles, onSuccess, onError) Parse a single molecule. The result arrives through onSuccess. Hand it to SvgDrawer.draw() or Drawer.draw(). This is the path needed for atom highlighting.
SmilesDrawer.parseReaction(smiles, onSuccess, onError) Parse a reaction SMILES into a reaction object. Hand it to ReactionDrawer.draw().
Next steps
Use the playground to tune your own structures, compare against the examples page when something looks off, and keep the API reference nearby if you need framework integration.
If you use SmilesDrawer in research, cite 10.1021/acs.jcim.7b00425.