Quick start

Add the dependency, parse a diagram, render it. Three calls, no build step, no browser.

Install

// build.sbt — JVM
libraryDependencies += "rocks.earlyeffect" %% "mermoid" % "<version>"

// Scala.js (or a cross-built project)
libraryDependencies += "rocks.earlyeffect" %%% "mermoid" % "<version>"

// Optional: hybrid HTML + SVG for Specular / ascent apps
libraryDependencies += "rocks.earlyeffect" %% "mermoid-ascent" % "<version>"
libraryDependencies += "rocks.earlyeffect" %%% "mermoid-ascent" % "<version>"

Pre-1.0 on early-semver: pin the exact version and read the release notes before bumping the minor.

Parse and render

MermaidParser.parse returns Either[String, Diagram] — the Left is the parse error, which you should surface rather than swallow. SvgRenderer.render turns a Diagram into the SVG document.

{
      import _root_.mermoid.*

      val source = """flowchart LR
    A[Start] --> B{Ready?}
    B -->|yes| C([Ship it])
    B -->|no| A
""".stripMargin

      MermaidParser.parse(source).map(SvgRenderer.render(_)) match
        case Right(svg) => s"${svg.length} characters of SVG, starting ${svg.take(4)}"
        case Left(err)  => s"parse error: $err"
    }
4151 characters of SVG, starting <svg

That same diagram, rendered:

MermoidAscent.svgDiagram("""flowchart LR
    A[Start] --> B{Ready?}
    B -->|yes| C([Ship it])
    B -->|no| A
""".stripMargin)
yesnoReady?StartShip it

Write it to a file

On the JVM, the whole job is one Files.writeString:

import _root_.mermoid.*
import java.nio.file.{Files, Path}

def renderToFile(mmd: Path, svg: Path): Either[String, Unit] =
  MermaidParser
    .parse(Files.readString(mmd))
    .map(d => Files.writeString(svg, SvgRenderer.render(d)))
    .map(_ => ())

Or skip the code and use the CLI.

Render in the browser

The same artifact cross-builds for Scala.js, so a Scala.js app can render a diagram client-side without pulling in mermaid.js:

import _root_.mermoid.*
import org.scalajs.dom

MermaidParser.parse(source).foreach { d =>
  dom.document.getElementById("chart").innerHTML = SvgRenderer.render(d)
}

If you build a virtual DOM rather than setting innerHTML, use SvgRenderer.renderTree and map the SvgNode tree to your framework's element type — no string round-trip.

Rendering is deterministic and platform-independent: the same source and config produce byte-identical SVG on the JVM and on Scala.js, which is what lets you render server-side and hydrate client-side without a mismatch.

Configure

RenderConfig is the one knob:

RenderConfig(
  layout            = LayoutConfig(),           // spacing, font sizes, shape geometry
  theme             = css.ThemeName.Default,    // Default | Dark | Forest | Neutral
  customStylesheet  = None,                     // merged over the theme
  resolveVariables  = true,                     // false keeps var(--mermoid-*) in the output
  responsive        = ResponsiveConfig(),       // spacing compress, direction flip, scale-to-fit
)

Pass an optional Viewport(maxWidth) (and optionally maxHeight) to SvgRenderer.render / DiagramLayout.scene when you want the layout to fit a host width. Narrow viewports prefer vertical flow; wider ones prefer horizontal. Details live on Interactive.

See Theming for themes and Custom CSS for customStylesheet and resolveVariables.

Layout without painting

DiagramLayout.scene returns geometry, edge routes, notes, and click interactions without serializing SVG. Use it when you paint yourself (or when you only need metrics):

import _root_.mermoid.*

val scene: Either[String, DiagramScene] =
  MermaidParser.parse(source).map(d => DiagramLayout.scene(d, RenderConfig(), Some(Viewport(640))))

mermoid-ascent consumes the same scene for hybrid HTML + SVG; see Interactive.

Next steps