Skip to content
glyphcss

Text diagrams in vanilla JavaScript, React, and Vue

Diagrams use the cell renderer directly. They do not need the 3D scene bindings. Install @glyphcss/diagrams in your application, then pass Mermaid source or graph data. The examples use color: "none" and assign the result as text.

<pre id="diagram"></pre>
<style>
#diagram { font-family: "Glyph Mono", ui-monospace, monospace; line-height: 1; overflow: auto; }
</style>
import { renderGlyphDiagram } from "@glyphcss/diagrams";
const host = document.querySelector<HTMLPreElement>("#diagram")!;
try {
const result = await renderGlyphDiagram(`flowchart LR
Push[Push commit] --> Build[Build app]
Build --> Test{Tests pass?}
Test -->|yes| Stage[Deploy staging]
Stage --> Smoke[Smoke tests]
Smoke --> Prod[Deploy prod]
Test -->|no| Fix[Fix build]`, {
target: "web", color: "none", width: 72, height: 19, overflow: "expand",
});
host.textContent = result.text;
} catch (error) {
host.textContent = error instanceof Error ? error.message : String(error);
}
import { useEffect, useState } from "react";
import { renderGlyphDiagram } from "@glyphcss/diagrams";
export function GlyphOrderDiagram({ source = `flowchart LR
Push[Push commit] --> Build[Build app]
Build --> Test{Tests pass?}
Test -->|yes| Stage[Deploy staging]
Stage --> Smoke[Smoke tests]
Smoke --> Prod[Deploy prod]
Test -->|no| Fix[Fix build]` }) {
const [text, setText] = useState("");
useEffect(() => {
let active = true;
renderGlyphDiagram(source, {
target: "web", color: "none", width: 72, height: 19, overflow: "expand",
}).then(
result => { if (active) setText(result.text); },
error => { if (active) setText(error instanceof Error ? error.message : String(error)); },
);
return () => { active = false; };
}, [source]);
return <pre className="order-diagram">{text}</pre>;
}
.order-diagram {
font-family: "Glyph Mono", ui-monospace, monospace;
line-height: 1;
overflow: auto;
}

The cleanup prevents a completed older request from replacing a newer diagram or updating an unmounted component. It also tolerates React StrictMode replay.

<script setup lang="ts">
import { ref, watchEffect } from "vue";
import { renderGlyphDiagram } from "@glyphcss/diagrams";
const props = withDefaults(defineProps<{ source?: string }>(), {
source: `flowchart LR
Push[Push commit] --> Build[Build app]
Build --> Test{Tests pass?}
Test -->|yes| Stage[Deploy staging]
Stage --> Smoke[Smoke tests]
Smoke --> Prod[Deploy prod]
Test -->|no| Fix[Fix build]`,
});
const text = ref("");
watchEffect(onCleanup => {
let active = true;
onCleanup(() => { active = false; });
renderGlyphDiagram(props.source, {
target: "web", color: "none", width: 72, height: 19, overflow: "expand",
}).then(
result => { if (active) text.value = result.text; },
error => { if (active) text.value = error instanceof Error ? error.message : String(error); },
);
});
</script>
<template><pre class="order-diagram">{{ text }}</pre></template>
<style scoped>
.order-diagram {
font-family: "Glyph Mono", ui-monospace, monospace;
line-height: 1;
overflow: auto;
}
</style>
        ┌─────────────┐    ┌───────────┐    ╭───────────────╮           
        │ Push commit │────▶ Build app │────▶ ◆ Tests pass? │           
        └─────────────┘    └───────────┘    ╰───────────────╯           
                                                   │ │                  
                ┌──────────────────────────────────┘ │ no               
                │             yes                    │                  
                │                             ┌──────▼────┐             
                │                             │ Fix build │             
                │                             └───────────┘             
                │                                                       
        ┌───────▼────────┐    ┌─────────────┐    ┌─────────────┐        
        │ Deploy staging │────▶ Smoke tests │────▶ Deploy prod │        
        └────────────────┘    └─────────────┘    └─────────────┘        

Use color: "css" to obtain result.html, escaped span markup for the contents of a <pre>. Insert that fragment inside the <pre>; the library does not supply the outer element. The library escapes label text; do not concatenate unescaped source into the fragment.

import { renderGlyphDiagram } from "@glyphcss/diagrams";
const result = await renderGlyphDiagram("flowchart LR; Build --> Ship", {
target: "web", color: "css", width: 64, height: 16,
});
const wrapper = document.createElement("pre");
wrapper.className = "diagram-html";
wrapper.innerHTML = result.html!;
document.body.append(wrapper);
.diagram-html {
font-family: "Glyph Mono", ui-monospace, monospace;
line-height: 1;
overflow: auto;
}

React can use <pre dangerouslySetInnerHTML={{ __html: html }} />; Vue can use <pre v-html="html" />. Populate html from result.html and retain the same stale-result cleanup as in the text examples. The pre needs the font and line-height rules above.

Measure available columns and rows at a stable font size, then call the renderer again. Use a ResizeObserver owned and disconnected by the component lifecycle. Read actual expanded dimensions from result.canvas.grid; see sizing.

For a static site, call the renderer at build time and emit text or CSS HTML without hydration. These documentation examples do that through a shared Astro adapter; no diagram runtime is required to display their output. The same renderer works in Node for server output.