diff --git a/__mocks__/d3.ts b/__mocks__/d3.ts index 67f09b6f4f8..f90d93557be 100644 --- a/__mocks__/d3.ts +++ b/__mocks__/d3.ts @@ -53,6 +53,18 @@ export const MockD3 = (name, parent) => { get __parent() { return parent; }, + node() { + return { + getBBox() { + return { + x: 5, + y: 10, + height: 15, + width: 20, + }; + }, + }; + }, }; elem.append = (name) => { const mockElem = MockD3(name, elem); diff --git a/cypress/integration/rendering/classDiagram-v2.spec.js b/cypress/integration/rendering/classDiagram-v2.spec.js index e36693a6522..f9ed7c64b10 100644 --- a/cypress/integration/rendering/classDiagram-v2.spec.js +++ b/cypress/integration/rendering/classDiagram-v2.spec.js @@ -496,4 +496,16 @@ describe('Class diagram V2', () => { ); cy.get('svg'); }); + + it('1433: should render a simple class with a title', () => { + imgSnapshotTest( + `--- + title: simple class diagram + --- + classDiagram-v2 + class Class10 + `, + {} + ); + }); }); diff --git a/cypress/integration/rendering/erDiagram.spec.js b/cypress/integration/rendering/erDiagram.spec.js index 057b36dc136..dea3c762044 100644 --- a/cypress/integration/rendering/erDiagram.spec.js +++ b/cypress/integration/rendering/erDiagram.spec.js @@ -273,4 +273,17 @@ describe('Entity Relationship Diagram', () => { ); cy.get('svg'); }); + + it('1433: should render a simple ER diagram with a title', () => { + imgSnapshotTest( + `--- + title: simple ER diagram + --- + erDiagram + CUSTOMER ||--o{ ORDER : places + ORDER ||--|{ LINE-ITEM : contains + `, + {} + ); + }); }); diff --git a/cypress/integration/rendering/flowchart-v2.spec.js b/cypress/integration/rendering/flowchart-v2.spec.js index 61dccfb84b5..cdf0d07cad1 100644 --- a/cypress/integration/rendering/flowchart-v2.spec.js +++ b/cypress/integration/rendering/flowchart-v2.spec.js @@ -663,4 +663,15 @@ flowchart RL { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } ); }); + it('1433: should render a titled flowchart with titleTopMargin set to 0', () => { + imgSnapshotTest( + `--- + title: Simple flowchart + --- + flowchart TD + A --> B + `, + { titleTopMargin: 0 } + ); + }); }); diff --git a/cypress/integration/rendering/gitGraph.spec.js b/cypress/integration/rendering/gitGraph.spec.js index afb39b62e6a..cb70f727259 100644 --- a/cypress/integration/rendering/gitGraph.spec.js +++ b/cypress/integration/rendering/gitGraph.spec.js @@ -322,4 +322,15 @@ describe('Git Graph diagram', () => { {} ); }); + it('1433: should render a simple gitgraph with a title', () => { + imgSnapshotTest( + `--- + title: simple gitGraph + --- + gitGraph + commit + `, + {} + ); + }); }); diff --git a/cypress/integration/rendering/stateDiagram-v2.spec.js b/cypress/integration/rendering/stateDiagram-v2.spec.js index 5b43c890cb5..7c322c1b3f4 100644 --- a/cypress/integration/rendering/stateDiagram-v2.spec.js +++ b/cypress/integration/rendering/stateDiagram-v2.spec.js @@ -559,4 +559,16 @@ stateDiagram-v2 ); }); }); + it('1433: should render a simple state diagram with a title', () => { + imgSnapshotTest( + `--- + title: simple state diagram + --- + stateDiagram-v2 + [*] --> State1 + State1 --> [*] + `, + {} + ); + }); }); diff --git a/demos/classchart.html b/demos/classchart.html index 5979152d6dc..3481bbad594 100644 --- a/demos/classchart.html +++ b/demos/classchart.html @@ -17,6 +17,9 @@

Class diagram demos

+    ---
+    title: Demo Class Diagram
+    ---
 		classDiagram
       accTitle: Demo Class Diagram
       accDescr: This class diagram show the abstract Animal class, and 3 classes that inherit from it: Duck, Fish, and Zebra.
diff --git a/demos/er.html b/demos/er.html
index 4c1a72c20c9..06fbf020e7f 100644
--- a/demos/er.html
+++ b/demos/er.html
@@ -20,6 +20,9 @@
   
     
 
+---
+title: This is a title
+---
 erDiagram
   %% title This is a title
   %% accDescription Test a description
diff --git a/demos/flowchart.html b/demos/flowchart.html
index e11bfcb262b..60e6160c38a 100644
--- a/demos/flowchart.html
+++ b/demos/flowchart.html
@@ -17,6 +17,9 @@ 

Comparison "graph vs. flowchart"

Sample 1

graph

+    ---
+    title: This is a complicated flow
+    ---
     graph LR
       accTitle: This is a complicated flow
       accDescr: This is the descriptoin for the complicated flow.
@@ -221,6 +224,9 @@ 

flowchart

Sample 2

graph

+    ---
+    title: What to buy
+    ---
     graph TD
       accTitle: What to buy
       accDescr: Options of what to buy with Christmas money
diff --git a/demos/git.html b/demos/git.html
index 15b4401dbab..5e683152aa3 100644
--- a/demos/git.html
+++ b/demos/git.html
@@ -16,6 +16,9 @@
   
     

Git diagram demo

+    ---
+    title: Simple Git diagram
+    ---
     gitGraph:
     options
     {
diff --git a/demos/journey.html b/demos/journey.html
index c5c6c25e8d3..71eecb58428 100644
--- a/demos/journey.html
+++ b/demos/journey.html
@@ -16,8 +16,10 @@
   
     

Journey diagram demo

-         journey
-    title My working day
+     ---
+     title: My working day 
+     ---
+     journey
       accTitle: Very simple journey demo
       accDescr: 2 main sections: work and home, each with just a few tasks
 
diff --git a/demos/state.html b/demos/state.html
index dbe2286a304..9f126cbc254 100644
--- a/demos/state.html
+++ b/demos/state.html
@@ -17,6 +17,9 @@
     

State diagram demos

Very simple showing change from State1 to State2

+    ---
+    title: Very simple diagram
+    ---
 		stateDiagram
 		  accTitle: This is the accessible title
       accDescr:This is an accessible description
@@ -43,6 +46,9 @@ 

And these are how they are applied:

+    ---
+    title: Very simple diagram
+    ---
 		stateDiagram-v2
 		  accTitle: This is the accessible title
       accDescr: This is an accessible description
diff --git a/docs/syntax/classDiagram.md b/docs/syntax/classDiagram.md
index d57125c84d4..e29c7295e8a 100644
--- a/docs/syntax/classDiagram.md
+++ b/docs/syntax/classDiagram.md
@@ -14,6 +14,9 @@ The class diagram is the main building block of object-oriented modeling. It is
 Mermaid can render class diagrams.
 
 ```mermaid-example
+---
+title: Animal example
+---
 classDiagram
     note "From Duck till Zebra"
     Animal <|-- Duck
@@ -40,6 +43,9 @@ classDiagram
 ```
 
 ```mermaid
+---
+title: Animal example
+---
 classDiagram
     note "From Duck till Zebra"
     Animal <|-- Duck
@@ -77,6 +83,9 @@ A single instance of a class in the diagram contains three compartments:
 - The bottom compartment contains the operations the class can execute. They are also left-aligned and the first letter is lowercase.
 
 ```mermaid-example
+---
+title: Bank example
+---
 classDiagram
     class BankAccount
     BankAccount : +String owner
@@ -87,6 +96,9 @@ classDiagram
 ```
 
 ```mermaid
+---
+title: Bank example
+---
 classDiagram
     class BankAccount
     BankAccount : +String owner
diff --git a/docs/syntax/entityRelationshipDiagram.md b/docs/syntax/entityRelationshipDiagram.md
index fef7b6fee71..9b938bc3683 100644
--- a/docs/syntax/entityRelationshipDiagram.md
+++ b/docs/syntax/entityRelationshipDiagram.md
@@ -13,6 +13,9 @@ Note that practitioners of ER modelling almost always refer to _entity types_ si
 Mermaid can render ER diagrams
 
 ```mermaid-example
+---
+title: Order example
+---
 erDiagram
     CUSTOMER ||--o{ ORDER : places
     ORDER ||--|{ LINE-ITEM : contains
@@ -20,6 +23,9 @@ erDiagram
 ```
 
 ```mermaid
+---
+title: Order example
+---
 erDiagram
     CUSTOMER ||--o{ ORDER : places
     ORDER ||--|{ LINE-ITEM : contains
diff --git a/docs/syntax/flowchart.md b/docs/syntax/flowchart.md
index 234f46236d3..a6094499a6a 100644
--- a/docs/syntax/flowchart.md
+++ b/docs/syntax/flowchart.md
@@ -15,11 +15,17 @@ It can also accommodate different arrow types, multi directional arrows, and lin
 ### A node (default)
 
 ```mermaid-example
+---
+title: Node
+---
 flowchart LR
     id
 ```
 
 ```mermaid
+---
+title: Node
+---
 flowchart LR
     id
 ```
@@ -33,11 +39,17 @@ found for the node that will be used. Also if you define edges for the node late
 one previously defined will be used when rendering the box.
 
 ```mermaid-example
+---
+title: Node with text
+---
 flowchart LR
     id1[This is the text in the box]
 ```
 
 ```mermaid
+---
+title: Node with text
+---
 flowchart LR
     id1[This is the text in the box]
 ```
diff --git a/docs/syntax/gitgraph.md b/docs/syntax/gitgraph.md
index cd1a3f12a3e..051e7ce39e0 100644
--- a/docs/syntax/gitgraph.md
+++ b/docs/syntax/gitgraph.md
@@ -13,6 +13,9 @@ These kind of diagram are particularly helpful to developers and devops teams to
 Mermaid can render Git diagrams
 
 ```mermaid-example
+    ---
+    title: Example Git diagram
+    ---
     gitGraph
        commit
        commit
@@ -27,6 +30,9 @@ Mermaid can render Git diagrams
 ```
 
 ```mermaid
+    ---
+    title: Example Git diagram
+    ---
     gitGraph
        commit
        commit
diff --git a/docs/syntax/stateDiagram.md b/docs/syntax/stateDiagram.md
index ec91411f612..1cec5afca16 100644
--- a/docs/syntax/stateDiagram.md
+++ b/docs/syntax/stateDiagram.md
@@ -11,6 +11,9 @@
 Mermaid can render state diagrams. The syntax tries to be compliant with the syntax used in plantUml as this will make it easier for users to share diagrams between mermaid and plantUml.
 
 ```mermaid-example
+---
+title: Simple sample
+---
 stateDiagram-v2
     [*] --> Still
     Still --> [*]
@@ -22,6 +25,9 @@ stateDiagram-v2
 ```
 
 ```mermaid
+---
+title: Simple sample
+---
 stateDiagram-v2
     [*] --> Still
     Still --> [*]
diff --git a/package.json b/package.json
index 7bd64887773..282123d4b09 100644
--- a/package.json
+++ b/package.json
@@ -90,6 +90,7 @@
     "jest": "^29.3.1",
     "jison": "^0.4.18",
     "jsdom": "^20.0.2",
+    "js-yaml": "^4.1.0",
     "lint-staged": "^13.0.3",
     "path-browserify": "^1.0.1",
     "pnpm": "^7.15.0",
diff --git a/packages/mermaid/src/Diagram.ts b/packages/mermaid/src/Diagram.ts
index 798adf50127..1afc90f298a 100644
--- a/packages/mermaid/src/Diagram.ts
+++ b/packages/mermaid/src/Diagram.ts
@@ -2,7 +2,9 @@ import * as configApi from './config';
 import { log } from './logger';
 import { getDiagram, registerDiagram } from './diagram-api/diagramAPI';
 import { detectType, getDiagramLoader } from './diagram-api/detectType';
+import { extractFrontMatter } from './diagram-api/frontmatter';
 import { isDetailedError, type DetailedError } from './utils';
+import { start } from 'repl';
 
 export type ParseErrorFunction = (err: string | DetailedError, hash?: any) => void;
 
@@ -29,6 +31,8 @@ export class Diagram {
     this.db.clear?.();
     this.renderer = diagram.renderer;
     this.parser = diagram.parser;
+    const originalParse = this.parser.parse.bind(this.parser);
+    this.parser.parse = (text: string) => originalParse(extractFrontMatter(text, this.db));
     this.parser.parser.yy = this.db;
     if (diagram.init) {
       diagram.init(cnf);
diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts
index cbcd2f661a1..ff199ca8b11 100644
--- a/packages/mermaid/src/config.type.ts
+++ b/packages/mermaid/src/config.type.ts
@@ -189,6 +189,7 @@ export interface C4DiagramConfig extends BaseDiagramConfig {
 }
 
 export interface GitGraphDiagramConfig extends BaseDiagramConfig {
+  titleTopMargin?: number;
   diagramPadding?: number;
   nodeLabel?: NodeLabel;
   mainBranchName?: string;
@@ -227,6 +228,7 @@ export interface MindmapDiagramConfig extends BaseDiagramConfig {
 export type PieDiagramConfig = BaseDiagramConfig;
 
 export interface ErDiagramConfig extends BaseDiagramConfig {
+  titleTopMargin?: number;
   diagramPadding?: number;
   layoutDirection?: string;
   minEntityWidth?: number;
@@ -238,6 +240,7 @@ export interface ErDiagramConfig extends BaseDiagramConfig {
 }
 
 export interface StateDiagramConfig extends BaseDiagramConfig {
+  titleTopMargin?: number;
   arrowMarkerAbsolute?: boolean;
   dividerMargin?: number;
   sizeUnit?: number;
@@ -258,6 +261,7 @@ export interface StateDiagramConfig extends BaseDiagramConfig {
 }
 
 export interface ClassDiagramConfig extends BaseDiagramConfig {
+  titleTopMargin?: number;
   arrowMarkerAbsolute?: boolean;
   dividerMargin?: number;
   padding?: number;
@@ -343,6 +347,7 @@ export interface SequenceDiagramConfig extends BaseDiagramConfig {
 }
 
 export interface FlowchartDiagramConfig extends BaseDiagramConfig {
+  titleTopMargin?: number;
   arrowMarkerAbsolute?: boolean;
   diagramPadding?: number;
   htmlLabels?: boolean;
diff --git a/packages/mermaid/src/defaultConfig.ts b/packages/mermaid/src/defaultConfig.ts
index 2ddae580ca5..37d4f71fffc 100644
--- a/packages/mermaid/src/defaultConfig.ts
+++ b/packages/mermaid/src/defaultConfig.ts
@@ -154,6 +154,17 @@ const config: Partial = {
 
   /** The object containing configurations specific for flowcharts */
   flowchart: {
+    /**
+     * ### titleTopMargin
+     *
+     * | Parameter      | Description                                    | Type    | Required | Values             |
+     * | -------------- | ---------------------------------------------- | ------- | -------- | ------------------ |
+     * | titleTopMargin | Margin top for the text over the flowchart     | Integer | Required | Any Positive Value |
+     *
+     * **Notes:** Default value: 25
+     */
+    titleTopMargin: 25,
+
     /**
      * | Parameter      | Description                                     | Type    | Required | Values             |
      * | -------------- | ----------------------------------------------- | ------- | -------- | ------------------ |
@@ -851,6 +862,16 @@ const config: Partial = {
     sectionColours: ['#fff'],
   },
   class: {
+    /**
+     * ### titleTopMargin
+     *
+     * | Parameter      | Description                                    | Type    | Required | Values             |
+     * | -------------- | ---------------------------------------------- | ------- | -------- | ------------------ |
+     * | titleTopMargin | Margin top for the text over the class diagram | Integer | Required | Any Positive Value |
+     *
+     * **Notes:** Default value: 25
+     */
+    titleTopMargin: 25,
     arrowMarkerAbsolute: false,
     dividerMargin: 10,
     padding: 5,
@@ -884,6 +905,16 @@ const config: Partial = {
     defaultRenderer: 'dagre-wrapper',
   },
   state: {
+    /**
+     * ### titleTopMargin
+     *
+     * | Parameter      | Description                                    | Type    | Required | Values             |
+     * | -------------- | ---------------------------------------------- | ------- | -------- | ------------------ |
+     * | titleTopMargin | Margin top for the text over the state diagram | Integer | Required | Any Positive Value |
+     *
+     * **Notes:** Default value: 25
+     */
+    titleTopMargin: 25,
     dividerMargin: 10,
     sizeUnit: 5,
     padding: 8,
@@ -932,6 +963,17 @@ const config: Partial = {
 
   /** The object containing configurations specific for entity relationship diagrams */
   er: {
+    /**
+     * ### titleTopMargin
+     *
+     * | Parameter      | Description                                    | Type    | Required | Values             |
+     * | -------------- | ---------------------------------------------- | ------- | -------- | ------------------ |
+     * | titleTopMargin | Margin top for the text over the diagram       | Integer | Required | Any Positive Value |
+     *
+     * **Notes:** Default value: 25
+     */
+    titleTopMargin: 25,
+
     /**
      * | Parameter      | Description                                     | Type    | Required | Values             |
      * | -------------- | ----------------------------------------------- | ------- | -------- | ------------------ |
@@ -1085,6 +1127,16 @@ const config: Partial = {
     line_height: 20,
   },
   gitGraph: {
+    /**
+     * ### titleTopMargin
+     *
+     * | Parameter      | Description                                    | Type    | Required | Values             |
+     * | -------------- | ---------------------------------------------- | ------- | -------- | ------------------ |
+     * | titleTopMargin | Margin top for the text over the Git diagram   | Integer | Required | Any Positive Value |
+     *
+     * **Notes:** Default value: 25
+     */
+    titleTopMargin: 25,
     diagramPadding: 8,
     nodeLabel: {
       width: 75,
diff --git a/packages/mermaid/src/diagram-api/detectType.ts b/packages/mermaid/src/diagram-api/detectType.ts
index 1c1abc51c9f..6f985722123 100644
--- a/packages/mermaid/src/diagram-api/detectType.ts
+++ b/packages/mermaid/src/diagram-api/detectType.ts
@@ -1,6 +1,7 @@
 import { MermaidConfig } from '../config.type';
 import { log } from '../logger';
 import { DetectorRecord, DiagramDetector, DiagramLoader } from './types';
+import { frontMatterRegex } from './frontmatter';
 
 const directive =
   /[%]{2}[{]\s*(?:(?:(\w+)\s*:|(\w+))\s*(?:(?:(\w+))|((?:(?![}][%]{2}).|\r?\n)*))?\s*)(?:[}][%]{2})?/gi;
@@ -31,7 +32,7 @@ const detectors: Record = {};
  * @returns A graph definition key
  */
 export const detectType = function (text: string, config?: MermaidConfig): string {
-  text = text.replace(directive, '').replace(anyComment, '\n');
+  text = text.replace(frontMatterRegex, '').replace(directive, '').replace(anyComment, '\n');
   for (const [key, { detector }] of Object.entries(detectors)) {
     const diagram = detector(text, config);
     if (diagram) {
diff --git a/packages/mermaid/src/diagram-api/frontmatter.spec.ts b/packages/mermaid/src/diagram-api/frontmatter.spec.ts
new file mode 100644
index 00000000000..92aa70573d5
--- /dev/null
+++ b/packages/mermaid/src/diagram-api/frontmatter.spec.ts
@@ -0,0 +1,71 @@
+import { vi } from 'vitest';
+import { extractFrontMatter } from './frontmatter';
+
+const dbMock = () => ({ setDiagramTitle: vi.fn() });
+
+describe('extractFrontmatter', () => {
+  it('returns text unchanged if no frontmatter', () => {
+    expect(extractFrontMatter('diagram', null)).toEqual('diagram');
+  });
+
+  it('returns text unchanged if frontmatter lacks closing delimiter', () => {
+    const text = `---\ntitle: foo\ndiagram`;
+    expect(extractFrontMatter(text, null)).toEqual(text);
+  });
+
+  it('handles empty frontmatter', () => {
+    const db = dbMock();
+    const text = `---\n\n---\ndiagram`;
+    expect(extractFrontMatter(text, db)).toEqual('diagram');
+    expect(db.setDiagramTitle).not.toHaveBeenCalled();
+  });
+
+  it('handles frontmatter without mappings', () => {
+    const db = dbMock();
+    const text = `---\n1\n---\ndiagram`;
+    expect(extractFrontMatter(text, db)).toEqual('diagram');
+    expect(db.setDiagramTitle).not.toHaveBeenCalled();
+  });
+
+  it('does not try to parse frontmatter at the end', () => {
+    const db = dbMock();
+    const text = `diagram\n---\ntitle: foo\n---\n`;
+    expect(extractFrontMatter(text, db)).toEqual(text);
+    expect(db.setDiagramTitle).not.toHaveBeenCalled();
+  });
+
+  it('handles frontmatter with multiple delimiters', () => {
+    const db = dbMock();
+    const text = `---\ntitle: foo---bar\n---\ndiagram\n---\ntest`;
+    expect(extractFrontMatter(text, db)).toEqual('diagram\n---\ntest');
+    expect(db.setDiagramTitle).toHaveBeenCalledWith('foo---bar');
+  });
+
+  it('handles frontmatter with title', () => {
+    const db = dbMock();
+    const text = `---\ntitle: foo\n---\ndiagram`;
+    expect(extractFrontMatter(text, db)).toEqual('diagram');
+    expect(db.setDiagramTitle).toHaveBeenCalledWith('foo');
+  });
+
+  it('handles booleans in frontmatter properly', () => {
+    const db = dbMock();
+    const text = `---\ntitle: true\n---\ndiagram`;
+    expect(extractFrontMatter(text, db)).toEqual('diagram');
+    expect(db.setDiagramTitle).toHaveBeenCalledWith('true');
+  });
+
+  it('ignores unspecified frontmatter keys', () => {
+    const db = dbMock();
+    const text = `---\ninvalid: true\ntitle: foo\ntest: bar\n---\ndiagram`;
+    expect(extractFrontMatter(text, db)).toEqual('diagram');
+    expect(db.setDiagramTitle).toHaveBeenCalledWith('foo');
+  });
+
+  it('throws exception for invalid YAML syntax', () => {
+    const text = `---\n!!!\n---\ndiagram`;
+    expect(() => extractFrontMatter(text, null)).toThrow(
+      'tag suffix cannot contain exclamation marks'
+    );
+  });
+});
diff --git a/packages/mermaid/src/diagram-api/frontmatter.ts b/packages/mermaid/src/diagram-api/frontmatter.ts
new file mode 100644
index 00000000000..4cb7ee3a71a
--- /dev/null
+++ b/packages/mermaid/src/diagram-api/frontmatter.ts
@@ -0,0 +1,40 @@
+// The "* as yaml" part is necessary for tree-shaking
+import * as yaml from 'js-yaml';
+
+// Match Jekyll-style front matter blocks (https://jekyllrb.com/docs/front-matter/).
+// Based on regex used by Jekyll: https://github.com/jekyll/jekyll/blob/6dd3cc21c40b98054851846425af06c64f9fb466/lib/jekyll/document.rb#L10
+// Note that JS doesn't support the "\A" anchor, which means we can't use
+// multiline mode.
+// Relevant YAML spec: https://yaml.org/spec/1.2.2/#914-explicit-documents
+export const frontMatterRegex = /^(?:\s*---\s*[\r\n])(.*?)(?:[\r\n]\s*---\s*[\r\n]+)/s;
+
+type FrontMatterMetadata = {
+  title?: string;
+};
+
+/**
+ * Extract and parse frontmatter from text, if present, and sets appropriate
+ * properties in the provided db.
+ * @param text -
+ * @param db -
+ * @returns text with frontmatter stripped out
+ */
+export function extractFrontMatter(text: string, db: any): string {
+  // https://yaml.org/spec/1.2.2/#914-explicit-documents
+  const matches = text.match(frontMatterRegex);
+  if (matches) {
+    const parsed: FrontMatterMetadata = yaml.load(matches[1], {
+      // To keep things simple, only allow strings, arrays, and plain objects.
+      // https://www.yaml.org/spec/1.2/spec.html#id2802346
+      schema: yaml.FAILSAFE_SCHEMA,
+    }) as FrontMatterMetadata;
+
+    if (parsed && parsed.title) {
+      db?.setDiagramTitle(parsed.title);
+    }
+
+    return text.slice(matches[0].length);
+  } else {
+    return text;
+  }
+}
diff --git a/packages/mermaid/src/diagrams/class/classDb.js b/packages/mermaid/src/diagrams/class/classDb.js
index 83ef6072b49..9830c059e79 100644
--- a/packages/mermaid/src/diagrams/class/classDb.js
+++ b/packages/mermaid/src/diagrams/class/classDb.js
@@ -10,6 +10,8 @@ import {
   getAccDescription,
   setAccDescription,
   clear as commonClear,
+  setDiagramTitle,
+  getDiagramTitle,
 } from '../../commonDb';
 
 const MERMAID_DOM_ID_PREFIX = 'classid-';
@@ -408,4 +410,6 @@ export default {
   getTooltip,
   setTooltip,
   lookUpDomId,
+  setDiagramTitle,
+  getDiagramTitle,
 };
diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.js b/packages/mermaid/src/diagrams/class/classRenderer-v2.js
index fbc2e4833a8..bca3c01c837 100644
--- a/packages/mermaid/src/diagrams/class/classRenderer-v2.js
+++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.js
@@ -3,6 +3,7 @@ import graphlib from 'graphlib';
 import { log } from '../../logger';
 import { getConfig } from '../../config';
 import { render } from '../../dagre-wrapper/index.js';
+import utils from '../../utils';
 import { curveLinear } from 'd3';
 import { interpolateToCurve, getStylesFromArray } from '../../utils';
 import { setupGraphViewbox } from '../../setupGraphViewbox';
@@ -429,6 +430,8 @@ export const draw = function (text, id, _version, diagObj) {
     id
   );
 
+  utils.insertTitle(svg, 'classTitleText', conf.titleTopMargin, diagObj.db.getDiagramTitle());
+
   setupGraphViewbox(g, svg, conf.diagramPadding, conf.useMaxWidth);
 
   // Add label rects for non html labels
diff --git a/packages/mermaid/src/diagrams/class/styles.js b/packages/mermaid/src/diagrams/class/styles.js
index bc391114ed4..981cd7b7305 100644
--- a/packages/mermaid/src/diagrams/class/styles.js
+++ b/packages/mermaid/src/diagrams/class/styles.js
@@ -148,6 +148,11 @@ g.classGroup line {
   font-size: 11px;
 }
 
+.classTitleText {
+  text-anchor: middle;
+  font-size: 18px;
+  fill: ${options.textColor};
+}
 `;
 
 export default getStyles;
diff --git a/packages/mermaid/src/diagrams/er/erDb.js b/packages/mermaid/src/diagrams/er/erDb.js
index ad3454f846d..96b60836bc1 100644
--- a/packages/mermaid/src/diagrams/er/erDb.js
+++ b/packages/mermaid/src/diagrams/er/erDb.js
@@ -8,6 +8,8 @@ import {
   getAccDescription,
   setAccDescription,
   clear as commonClear,
+  setDiagramTitle,
+  getDiagramTitle,
 } from '../../commonDb';
 
 let entities = {};
@@ -94,4 +96,6 @@ export default {
   getAccTitle,
   setAccDescription,
   getAccDescription,
+  setDiagramTitle,
+  getDiagramTitle,
 };
diff --git a/packages/mermaid/src/diagrams/er/erRenderer.js b/packages/mermaid/src/diagrams/er/erRenderer.js
index 323bb4607c0..c6d00d4a707 100644
--- a/packages/mermaid/src/diagrams/er/erRenderer.js
+++ b/packages/mermaid/src/diagrams/er/erRenderer.js
@@ -3,6 +3,7 @@ import { line, curveBasis, select } from 'd3';
 import dagre from 'dagre';
 import { getConfig } from '../../config';
 import { log } from '../../logger';
+import utils from '../../utils';
 import erMarkers from './erMarkers';
 import { configureSvgSize } from '../../setupGraphViewbox';
 import addSVGAccessibilityFields from '../../accessibility';
@@ -649,6 +650,8 @@ export const draw = function (text, id, _version, diagObj) {
 
   const padding = conf.diagramPadding;
 
+  utils.insertTitle(svg, 'entityTitleText', conf.titleTopMargin, diagObj.db.getDiagramTitle());
+
   const svgBounds = svg.node().getBBox();
   const width = svgBounds.width + padding * 2;
   const height = svgBounds.height + padding * 2;
diff --git a/packages/mermaid/src/diagrams/er/styles.js b/packages/mermaid/src/diagrams/er/styles.js
index 907d813b674..42dbcebdee0 100644
--- a/packages/mermaid/src/diagrams/er/styles.js
+++ b/packages/mermaid/src/diagrams/er/styles.js
@@ -27,6 +27,12 @@ const getStyles = (options) =>
     .relationshipLine {
       stroke: ${options.lineColor};
     }
+
+  .entityTitleText {
+    text-anchor: middle;
+    font-size: 18px;
+    fill: ${options.textColor};
+  }    
 `;
 
 export default getStyles;
diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.js b/packages/mermaid/src/diagrams/flowchart/flowDb.js
index 6abc22659a2..38754c667a2 100644
--- a/packages/mermaid/src/diagrams/flowchart/flowDb.js
+++ b/packages/mermaid/src/diagrams/flowchart/flowDb.js
@@ -10,6 +10,8 @@ import {
   getAccDescription,
   setAccDescription,
   clear as commonClear,
+  setDiagramTitle,
+  getDiagramTitle,
 } from '../../commonDb';
 
 const MERMAID_DOM_ID_PREFIX = 'flowchart-';
@@ -785,4 +787,6 @@ export default {
   },
   exists,
   makeUniq,
+  setDiagramTitle,
+  getDiagramTitle,
 };
diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js
index 6b7c4c1bfe9..5f0288b0354 100644
--- a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js
+++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js
@@ -3,6 +3,7 @@ import { select, curveLinear, selectAll } from 'd3';
 
 import flowDb from './flowDb';
 import { getConfig } from '../../config';
+import utils from '../../utils';
 
 import { render } from '../../dagre-wrapper/index.js';
 import addHtmlLabel from 'dagre-d3/lib/label/add-html-label.js';
@@ -437,6 +438,8 @@ export const draw = function (text, id, _version, diagObj) {
   const element = root.select('#' + id + ' g');
   render(element, g, ['point', 'circle', 'cross'], 'flowchart', id);
 
+  utils.insertTitle(svg, 'flowchartTitleText', conf.titleTopMargin, diagObj.db.getDiagramTitle());
+
   setupGraphViewbox(g, svg, conf.diagramPadding, conf.useMaxWidth);
 
   // Index nodes
diff --git a/packages/mermaid/src/diagrams/flowchart/styles.ts b/packages/mermaid/src/diagrams/flowchart/styles.ts
index 82fb1f87594..a89d33d3dc8 100644
--- a/packages/mermaid/src/diagrams/flowchart/styles.ts
+++ b/packages/mermaid/src/diagrams/flowchart/styles.ts
@@ -103,6 +103,12 @@ const getStyles = (options: FlowChartStyleOptions) =>
     pointer-events: none;
     z-index: 100;
   }
+
+  .flowchartTitleText {
+    text-anchor: middle;
+    font-size: 18px;
+    fill: ${options.textColor};
+  }
 `;
 
 export default getStyles;
diff --git a/packages/mermaid/src/diagrams/git/gitGraphAst.js b/packages/mermaid/src/diagrams/git/gitGraphAst.js
index 496e578b717..65980933d40 100644
--- a/packages/mermaid/src/diagrams/git/gitGraphAst.js
+++ b/packages/mermaid/src/diagrams/git/gitGraphAst.js
@@ -10,6 +10,8 @@ import {
   getAccDescription,
   setAccDescription,
   clear as commonClear,
+  setDiagramTitle,
+  getDiagramTitle,
 } from '../../commonDb';
 
 let mainBranchName = getConfig().gitGraph.mainBranchName;
@@ -529,5 +531,7 @@ export default {
   getAccTitle,
   getAccDescription,
   setAccDescription,
+  setDiagramTitle,
+  getDiagramTitle,
   commitType,
 };
diff --git a/packages/mermaid/src/diagrams/git/gitGraphRenderer.js b/packages/mermaid/src/diagrams/git/gitGraphRenderer.js
index 71698a50071..75e8d445dc2 100644
--- a/packages/mermaid/src/diagrams/git/gitGraphRenderer.js
+++ b/packages/mermaid/src/diagrams/git/gitGraphRenderer.js
@@ -1,6 +1,7 @@
 import { select } from 'd3';
 import { getConfig, setupGraphViewbox } from '../../diagram-api/diagramAPI';
 import { log } from '../../logger';
+import utils from '../../utils';
 import addSVGAccessibilityFields from '../../accessibility';
 
 let allCommitsDict = {};
@@ -521,6 +522,12 @@ export const draw = function (txt, id, ver, diagObj) {
   }
   drawArrows(diagram, allCommitsDict);
   drawCommits(diagram, allCommitsDict, true);
+  utils.insertTitle(
+    diagram,
+    'gitTitleText',
+    gitGraphConfig.titleTopMargin,
+    diagObj.db.getDiagramTitle()
+  );
 
   // Setup the view box and size of the svg element
   setupGraphViewbox(
diff --git a/packages/mermaid/src/diagrams/git/styles.js b/packages/mermaid/src/diagrams/git/styles.js
index 7e09ff7e0eb..7417602356a 100644
--- a/packages/mermaid/src/diagrams/git/styles.js
+++ b/packages/mermaid/src/diagrams/git/styles.js
@@ -51,6 +51,11 @@ const getStyles = (options) =>
   }
 
   .arrow { stroke-width: 8; stroke-linecap: round; fill: none}
+  .gitTitleText {
+    text-anchor: middle;
+    font-size: 18px;
+    fill: ${options.textColor};
+  }
   }
 `;
 
diff --git a/packages/mermaid/src/diagrams/state/stateDb.js b/packages/mermaid/src/diagrams/state/stateDb.js
index 5e82eaf78be..19ecbe65f58 100644
--- a/packages/mermaid/src/diagrams/state/stateDb.js
+++ b/packages/mermaid/src/diagrams/state/stateDb.js
@@ -9,6 +9,8 @@ import {
   getAccDescription,
   setAccDescription,
   clear as commonClear,
+  setDiagramTitle,
+  getDiagramTitle,
 } from '../../commonDb';
 
 import {
@@ -571,4 +573,6 @@ export default {
   addStyleClass,
   setCssClass,
   addDescription,
+  setDiagramTitle,
+  getDiagramTitle,
 };
diff --git a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js
index 752b70e4451..03c67878924 100644
--- a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js
+++ b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js
@@ -5,6 +5,7 @@ import { render } from '../../dagre-wrapper/index.js';
 import { log } from '../../logger';
 import { configureSvgSize } from '../../setupGraphViewbox';
 import common from '../common/common';
+import utils from '../../utils';
 import addSVGAccessibilityFields from '../../accessibility';
 import {
   DEFAULT_DIAGRAM_DIRECTION,
@@ -437,8 +438,9 @@ export const draw = function (text, id, _version, diag) {
 
   const padding = 8;
 
-  const bounds = svg.node().getBBox();
+  utils.insertTitle(svg, 'statediagramTitleText', conf.titleTopMargin, diag.db.getDiagramTitle());
 
+  const bounds = svg.node().getBBox();
   const width = bounds.width + padding * 2;
   const height = bounds.height + padding * 2;
 
diff --git a/packages/mermaid/src/diagrams/state/styles.js b/packages/mermaid/src/diagrams/state/styles.js
index 4a1c4651220..f4783b477b3 100644
--- a/packages/mermaid/src/diagrams/state/styles.js
+++ b/packages/mermaid/src/diagrams/state/styles.js
@@ -194,6 +194,12 @@ g.stateGroup line {
   stroke: ${options.lineColor};
   stroke-width: 1;
 }
+
+.statediagramTitleText {
+  text-anchor: middle;
+  font-size: 18px;
+  fill: ${options.textColor};
+}
 `;
 
 export default getStyles;
diff --git a/packages/mermaid/src/docs/syntax/classDiagram.md b/packages/mermaid/src/docs/syntax/classDiagram.md
index 20bdd657f81..6ef0b82c997 100644
--- a/packages/mermaid/src/docs/syntax/classDiagram.md
+++ b/packages/mermaid/src/docs/syntax/classDiagram.md
@@ -8,6 +8,9 @@ The class diagram is the main building block of object-oriented modeling. It is
 Mermaid can render class diagrams.
 
 ```mermaid-example
+---
+title: Animal example
+---
 classDiagram
     note "From Duck till Zebra"
     Animal <|-- Duck
@@ -45,6 +48,9 @@ A single instance of a class in the diagram contains three compartments:
 - The bottom compartment contains the operations the class can execute. They are also left-aligned and the first letter is lowercase.
 
 ```mermaid-example
+---
+title: Bank example
+---
 classDiagram
     class BankAccount
     BankAccount : +String owner
diff --git a/packages/mermaid/src/docs/syntax/entityRelationshipDiagram.md b/packages/mermaid/src/docs/syntax/entityRelationshipDiagram.md
index e52b0df4c18..c666877c5f5 100644
--- a/packages/mermaid/src/docs/syntax/entityRelationshipDiagram.md
+++ b/packages/mermaid/src/docs/syntax/entityRelationshipDiagram.md
@@ -7,6 +7,9 @@ Note that practitioners of ER modelling almost always refer to _entity types_ si
 Mermaid can render ER diagrams
 
 ```mermaid-example
+---
+title: Order example
+---
 erDiagram
     CUSTOMER ||--o{ ORDER : places
     ORDER ||--|{ LINE-ITEM : contains
diff --git a/packages/mermaid/src/docs/syntax/flowchart.md b/packages/mermaid/src/docs/syntax/flowchart.md
index 25252e54d7b..5888db105c4 100644
--- a/packages/mermaid/src/docs/syntax/flowchart.md
+++ b/packages/mermaid/src/docs/syntax/flowchart.md
@@ -9,6 +9,9 @@ It can also accommodate different arrow types, multi directional arrows, and lin
 ### A node (default)
 
 ```mermaid-example
+---
+title: Node
+---
 flowchart LR
     id
 ```
@@ -22,6 +25,9 @@ found for the node that will be used. Also if you define edges for the node late
 one previously defined will be used when rendering the box.
 
 ```mermaid-example
+---
+title: Node with text
+---
 flowchart LR
     id1[This is the text in the box]
 ```
diff --git a/packages/mermaid/src/docs/syntax/gitgraph.md b/packages/mermaid/src/docs/syntax/gitgraph.md
index b19c1e2cda0..c3210af3135 100644
--- a/packages/mermaid/src/docs/syntax/gitgraph.md
+++ b/packages/mermaid/src/docs/syntax/gitgraph.md
@@ -7,6 +7,9 @@ These kind of diagram are particularly helpful to developers and devops teams to
 Mermaid can render Git diagrams
 
 ```mermaid-example
+    ---
+    title: Example Git diagram
+    ---
     gitGraph
        commit
        commit
diff --git a/packages/mermaid/src/docs/syntax/stateDiagram.md b/packages/mermaid/src/docs/syntax/stateDiagram.md
index e28819e7a2e..9293e10839b 100644
--- a/packages/mermaid/src/docs/syntax/stateDiagram.md
+++ b/packages/mermaid/src/docs/syntax/stateDiagram.md
@@ -5,6 +5,9 @@
 Mermaid can render state diagrams. The syntax tries to be compliant with the syntax used in plantUml as this will make it easier for users to share diagrams between mermaid and plantUml.
 
 ```mermaid-example
+---
+title: Simple sample
+---
 stateDiagram-v2
     [*] --> Still
     Still --> [*]
diff --git a/packages/mermaid/src/utils.spec.js b/packages/mermaid/src/utils.spec.js
index 4a511b3c044..04cf9b7695e 100644
--- a/packages/mermaid/src/utils.spec.js
+++ b/packages/mermaid/src/utils.spec.js
@@ -4,6 +4,7 @@ import assignWithDepth from './assignWithDepth';
 import { detectType } from './diagram-api/detectType';
 import { addDiagrams } from './diagram-api/diagram-orchestration';
 import memoize from 'lodash/memoize';
+import { MockD3 } from 'd3';
 addDiagrams();
 
 describe('when assignWithDepth: should merge objects within objects', function () {
@@ -232,6 +233,16 @@ Alice->Bob: hi`;
     const type = detectType(str);
     expect(type).toBe('gitGraph');
   });
+  it('should handle frontmatter', function () {
+    const str = '---\ntitle: foo\n---\n  gitGraph TB:\nbfs1:queue';
+    const type = detectType(str);
+    expect(type).toBe('gitGraph');
+  });
+  it('should handle frontmatter with leading spaces', function () {
+    const str = '    ---\ntitle: foo\n---\n  gitGraph TB:\nbfs1:queue';
+    const type = detectType(str);
+    expect(type).toBe('gitGraph');
+  });
 });
 describe('when finding substring in array ', function () {
   it('should return the array index that contains the substring', function () {
@@ -340,3 +351,23 @@ describe('when initializing the id generator', function () {
     expect(idGenerator.next()).toEqual(lastId + 1);
   });
 });
+
+describe('when inserting titles', function () {
+  it('should do nothing when title is empty', function () {
+    const svg = MockD3('svg');
+    utils.insertTitle(svg, 'testClass', 0, '');
+    expect(svg.__children.length).toBe(0);
+  });
+
+  it('should insert title centered', function () {
+    const svg = MockD3('svg');
+    utils.insertTitle(svg, 'testClass', 5, 'test title');
+    expect(svg.__children.length).toBe(1);
+    const text = svg.__children[0];
+    expect(text.__name).toBe('text');
+    expect(text.text).toHaveBeenCalledWith('test title');
+    expect(text.attr).toHaveBeenCalledWith('x', 15);
+    expect(text.attr).toHaveBeenCalledWith('y', -5);
+    expect(text.attr).toHaveBeenCalledWith('class', 'testClass');
+  });
+});
diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts
index 3eecd5f4fbc..82f89d61abf 100644
--- a/packages/mermaid/src/utils.ts
+++ b/packages/mermaid/src/utils.ts
@@ -885,6 +885,32 @@ export function getErrorMessage(error: unknown): string {
   return String(error);
 }
 
+/**
+ * Appends  element with the given title, centered.
+ *
+ * @param parent - d3 svg object to append title to
+ * @param cssClass - CSS class for the  element containing the title
+ * @param titleTopMargin - Margin in pixels between title and rest of the graph
+ * @param title - The title. If empty, returns immediately.
+ */
+export const insertTitle = (
+  parent,
+  cssClass: string,
+  titleTopMargin: number,
+  title?: string
+): void => {
+  if (!title) {
+    return;
+  }
+  const bounds = parent.node().getBBox();
+  parent
+    .append('text')
+    .text(title)
+    .attr('x', bounds.x + bounds.width / 2)
+    .attr('y', -titleTopMargin)
+    .attr('class', cssClass);
+};
+
 export default {
   assignWithDepth,
   wrapLabel,
@@ -907,4 +933,5 @@ export default {
   initIdGenerator: initIdGenerator,
   directiveSanitizer,
   sanitizeCss,
+  insertTitle,
 };
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 85afcb31dc0..c9549c05b2f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -115,6 +115,9 @@ importers:
       jison:
         specifier: ^0.4.18
         version: 0.4.18
+      js-yaml:
+        specifier: ^4.1.0
+        version: 4.1.0
       jsdom:
         specifier: ^20.0.2
         version: 20.0.2