Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions packages/site_shared/lib/_sass/components/_mermaid.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright 2026 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

.mermaid-container {
display: flex;
justify-content: center;
margin: 1.75rem 0;
padding: 1.5rem 1rem;
overflow-x: auto;
background-color: var(--site-raised-bgColor-translucent);
border: 1px solid var(--site-outline-variant);
border-radius: var(--site-radius);

// Fallback shown only if server rendering the diagram failed
pre.mermaid {
margin: 0;
padding: 0;
background: transparent;
border: none;
font-family: var(--site-code-fontFamily);
}

// Both theme variants are rendered on the server; show only the one
// matching the site's current theme.
.mermaid-theme-dark {
display: none;
}

@at-root body.dark-mode & {
.mermaid-theme-light {
display: none;
}

.mermaid-theme-dark {
display: block;
}
}

// Rendered SVG styling
// You must use !important to override styles for specific elements within
// the rendered SVG. For styling individual graphs, use Mermaid's built-in
// classRef system
svg {
max-width: 100%;
height: auto;
}
}
64 changes: 64 additions & 0 deletions packages/site_shared/lib/components/common/mermaid_diagram.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright 2026 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:jaspr/dom.dart';
import 'package:jaspr/jaspr.dart';
import 'package:mermaid_core/mermaid_core.dart';

/// Renders a Mermaid diagram as server-generated SVG.
///
/// Both light and dark variants are rendered at build time and included in
/// the page, so CSS can show the variant matching the site's current theme
/// without shipping a Mermaid renderer to the client.
final class MermaidDiagram extends StatelessComponent {
const MermaidDiagram({required this.diagram, super.key});

/// The Mermaid diagram definition to render.
final String diagram;

@override
Component build(BuildContext context) {
final lightSvg = _renderDiagram(theme: MermaidTheme.defaultTheme);
final darkSvg = _renderDiagram(theme: MermaidTheme.darkTheme);

return div(
classes: 'mermaid-container',
[
if (lightSvg != null && darkSvg != null) ...[
div(classes: 'mermaid-theme mermaid-theme-light', [
RawText(lightSvg),
]),
div(classes: 'mermaid-theme mermaid-theme-dark', [
RawText(darkSvg),
]),
] else
// If rendering fails, preserve the source in a fallback element.
pre(
classes: 'mermaid',
attributes: {'data-source': diagram},
[.text(diagram)],
),
],
);
}

/// Renders [diagram] as SVG using the specified [theme].
String? _renderDiagram({required MermaidTheme theme}) {
try {
// Server rendering doesn't have browser text metrics available, so use
// the library's deterministic approximation when laying out labels.
final mermaid = Mermaid(
measurer: const ApproximateTextMeasurer(),
theme: theme,
);
final scene = mermaid.render(diagram);
return renderSceneToSvg(scene);
} catch (error) {
if (kDebugMode) {
print('Failed to render Mermaid diagram: $error');
}
return null;
}
}
}
1 change: 1 addition & 0 deletions packages/site_shared/lib/markdown.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
// found in the LICENSE file.

export 'src/markdown/markdown_parser.dart';
export 'src/markdown/mermaid_syntax.dart';
1 change: 1 addition & 0 deletions packages/site_shared/lib/page_extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export 'src/extensions/attribute_processor.dart';
export 'src/extensions/code_block_processor.dart';
export 'src/extensions/header_extractor.dart';
export 'src/extensions/header_processor.dart';
export 'src/extensions/mermaid_processor.dart';
export 'src/extensions/table_processor.dart';
38 changes: 38 additions & 0 deletions packages/site_shared/lib/src/extensions/mermaid_processor.dart
Comment thread
parlough marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright 2026 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:jaspr_content/jaspr_content.dart';

import '../../components/common/mermaid_diagram.dart';

final class MermaidProcessor implements PageExtension {
const MermaidProcessor();

@override
Future<List<Node>> apply(Page page, List<Node> nodes) async =>
_processNodes(nodes);

List<Node> _processNodes(List<Node> nodes) {
return [
for (final node in nodes)
if (node case ElementNode(
tag: 'div',
attributes: {'class': 'mermaid-container'},
children: [
ElementNode(attributes: {'data-source': final diagram}),
...,
],
))
ComponentNode(MermaidDiagram(diagram: diagram))
else if (node is ElementNode)
ElementNode(
node.tag,
node.attributes,
node.children != null ? _processNodes(node.children!) : null,
)
else
node,
];
}
}
2 changes: 2 additions & 0 deletions packages/site_shared/lib/src/markdown/markdown_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ import 'alert_syntax.dart';
import 'attribute_syntax.dart';
import 'fenced_code_block_syntax.dart';
import 'header_syntax.dart';
import 'mermaid_syntax.dart';

/// The `package:markdown` block syntaxes to apply when parsing Markdown.
const List<md.BlockSyntax> _blockSyntaxes = [
JasprHtmlBlockSyntax(),
MermaidBlockSyntax(),
CustomFencedCodeBlockSyntax(),
HeaderWithAttributesSyntax(),
AttributeBlockSyntax(),
Expand Down
63 changes: 63 additions & 0 deletions packages/site_shared/lib/src/markdown/mermaid_syntax.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2026 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:markdown/markdown.dart' as md;

/// A custom Markdown block syntax for diagrams authored
/// between ```mermaid code fences.
///
/// Example:
///
/// ````markdown
/// ```mermaid
/// flowchart TD
/// A --> B
/// ```
/// ````
///
/// This renders as a `<div class="mermaid-container">` containing
/// a `<pre class="mermaid">` element with the diagram source, which
/// `MermaidProcessor` replaces with a server-rendered `MermaidDiagram`.
final class MermaidBlockSyntax extends md.BlockSyntax {
const MermaidBlockSyntax();

// Matches opening fence: ```mermaid (with optional trailing whitespace/config)
@override
RegExp get pattern => RegExp(r'^\s{0,3}`{3,}mermaid(?:\s.*)?$');

static final _closingFencePattern = RegExp(r'^\s{0,3}`{3,}\s*$');

@override
bool canParse(md.BlockParser parser) {
return pattern.hasMatch(parser.current.content);
}

@override
md.Node? parse(md.BlockParser parser) {
// Advance past the opening ```mermaid line
parser.advance();

final lines = <String>[];

// Collect diagram definition until the closing ```
while (!parser.isDone) {
final line = parser.current.content;
if (_closingFencePattern.hasMatch(line)) {
parser.advance(); // Consume closing fence
break;
}
lines.add(line);
parser.advance();
}

final rawContent = lines.join('\n');

// Return HTML AST node for the diagram container
final pre = md.Element.text('pre', rawContent)
..attributes['class'] = 'mermaid'
..attributes['data-source'] = rawContent;

return md.Element('div', [pre])..attributes['class'] = 'mermaid-container';
}
}
5 changes: 5 additions & 0 deletions packages/site_shared/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ dependencies:
jaspr_content: ^0.5.3
markdown: ^7.3.1
markdown_description_list: ^0.2.0
mermaid_core:
git:
url: https://github.com/orestesgaolin/mermaid.git
ref: 03759ce31539f87a1487e84d132d3ad1e8efa3cd

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider trying to update to 90c39dd66b5e798c12dd77ec8b3fba4d583f0a05. It seems it might include a fix to an issue you mentioned.

@ericwindmill ericwindmill Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That commit doesn't seem to be ready, it has broken dependencies internally, I'm going to file an issue

path: packages/mermaid_core
meta: ^1.18.2
nanoid2: ^2.0.1
opal: ^0.2.4
Expand Down
1 change: 1 addition & 0 deletions sites/docs/lib/_sass/_site.scss
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
@use 'package:site_shared/_sass/components/cookie-notice';
@use 'package:site_shared/_sass/components/dropdown';
@use 'package:site_shared/_sass/components/menu-toggle';
@use 'package:site_shared/_sass/components/mermaid';
@use 'package:site_shared/_sass/components/progress-ring';
@use 'package:site_shared/_sass/components/quiz';
@use 'package:site_shared/_sass/components/site-switcher';
Expand Down
1 change: 1 addition & 0 deletions sites/docs/lib/src/extensions/registry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const List<PageExtension> allNodeProcessingExtensions = [
HeaderExtractorExtension(),
HeaderWrapperExtension(),
TableWrapperExtension(),
MermaidProcessor(),
CodeBlockProcessor(defaultTitle: 'Runnable Flutter example'),
GlossaryLinkProcessor(),
TutorialNavigationExtension(),
Expand Down
64 changes: 64 additions & 0 deletions sites/docs/src/content/contribute/docs/markdown.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,67 @@ To learn more about customizing code blocks,
check out the dedicated documentation on [Code blocks][].

[Code blocks]: /contribute/docs/code-blocks


## Mermaid diagrams

To render flowcharts, sequence diagrams, and other charts within a Markdown file,
use a fenced code block with the `mermaid` language identifier:

````markdown
```mermaid
flowchart LR
A[Start] --> B(Process)
B --> C{Decision}
C -->|Yes| D[Done]
C -->|No| B
```
````

### Style Mermaid diagrams

Mermaid diagrams are rendered to SVG on the server, once for each theme.
Both variants ship in the page and CSS shows the one matching the site's
current light or dark theme. Diagrams inherit the default
`Google Sans Flex` typography.

#### In-diagram styling (Recommended)

When you need custom colors for specific nodes,
define them directly in the diagram using Mermaid's built-in
[`classDef` and `:::styleName` syntax][]:

```mermaid
flowchart LR
A:::highlightNode --> B
classDef highlightNode fill:#f96,stroke:#333,stroke-width:2px;
```

#### SCSS styling

To adjust the global diagram layout, border, or background across the site,
edit [`_mermaid.scss`][]:

```scss
.mermaid-container {
// Container styling (padding, margins, background, border)

// Fallback shown only if server rendering the diagram failed
pre.mermaid {
// Fallback styles
}

.mermaid-theme {}

.mermaid-theme-light {}

.mermaid-theme-dark {}

svg {
// Custom SVG element overrides (requires !important)
}
}
```
Comment thread
ericwindmill marked this conversation as resolved.

[`classDef` and `:::styleName` syntax]: https://mermaid.js.org/syntax/flowchart.html#styling-and-classes
[`_mermaid.scss`]: https://github.com/flutter/website/blob/main/packages/site_shared/lib/_sass/components/_mermaid.scss
18 changes: 17 additions & 1 deletion sites/www/lib/main.server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import 'package:jaspr/server.dart';
import 'package:jaspr_content/components/file_tree.dart';
import 'package:jaspr_content/jaspr_content.dart' hide BlogLayout;
import 'package:jaspr_content/theme.dart';
import 'package:markdown/markdown.dart' as md;
import 'package:site_shared/blog.dart';
import 'package:site_shared/components/blog/blog_index.dart';
import 'package:site_shared/components/common/youtube_embed.dart';
import 'package:site_shared/components/utils/define_component.dart';
import 'package:site_shared/markdown.dart';
import 'package:site_shared/page_extensions.dart';

import 'main.server.options.dart';
Expand Down Expand Up @@ -88,10 +90,13 @@ void main() async {
const BlogPostDataProcessor(),
assetManager.dataLoader,
],
parsers: [const MarkdownParser()],
parsers: [
const MarkdownParser(documentBuilder: _buildMarkdownDocument),
],
extensions: [
ShowcaseStoryExtension(),
const TableWrapperExtension(),
const MermaidProcessor(),
const CodeBlockProcessor(defaultTitle: 'Runnable Flutter example'),
assetManager.pageExtension,
],
Expand Down Expand Up @@ -141,3 +146,14 @@ void main() async {
),
);
}

/// Builds the `package:markdown` document used to parse this site's content,
/// adding [MermaidBlockSyntax] on top of the parser's default block syntaxes
/// so `MermaidProcessor` has diagrams to transform.
md.Document _buildMarkdownDocument(Page page) => md.Document(
blockSyntaxes: [
...MarkdownParser.defaultBlockSyntaxes,
const MermaidBlockSyntax(),
],
extensionSet: md.ExtensionSet.gitHubWeb,
);
1 change: 1 addition & 0 deletions sites/www/lib/styles/styles.scss
Comment thread
parlough marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@
@use 'package:site_shared/_sass/components/blog';
@use 'package:site_shared/_sass/components/code';
@use 'package:site_shared/_sass/components/dropdown';
@use 'package:site_shared/_sass/components/mermaid';
1 change: 1 addition & 0 deletions sites/www/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ dependencies:
intl: ^0.20.2
jaspr: ^0.23.2
jaspr_content: ^0.5.3
markdown: ^7.3.1
path: ^1.9.1
site_shared:
path: ../../packages/site_shared
Expand Down
Loading