Master Markdown and Multi-line Selection in Flutter: A Step-by-Step Tutorial
[

Recently, I have been developing Moli AI and it requires support for markdown rendering and multi-line copying. This article primarily covers the following topics:
How to use markdown in Flutter.
How to customize markdown styles.
How to enable multi-line selection in markdown.

flutter_markdown
flutter_markdownis a markdown renderer provided by the official Flutter community. It supports creating rich text output from simple Markdown tagged formatted plain text data, including text styles, tables, links, and more.
Overview:
- The
flutter_markdownpackage allows you to render Markdown text into rich text in Flutter. - It is built on the Dart
markdownpackage and parses Markdown into an abstract syntax tree (AST). - Markdown allows injecting HTML into the source text, but the
flutter_markdownpackage does not support inline HTML. - By default, the
flutter_markdownpackage uses the GitHub Flavored Markdown specification.
How to use:
- Add the
flutter_markdown: ^0.6.18+3dependency in yourpubspec.yamlfile.
dependencies:
flutter:
sdk: flutter
flutter_markdown: ^0.6.18+3
Then add the following code:
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
void main() {
runApp(const MyApp());}
class MyApp extends StatelessWidget {
const MyApp({super.key}); Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Markdown Page'),
); }}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
State<MyHomePage> createState() => _MyHomePageState();}
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
var markdown =
"""Here's a simple Go program that prints "Hello, world!" to the console😀:```gopackage mainimport "fmt"func main() { fmt.Println("Hello, world!")}```Save the above code in a file with a `.go` extension, for example `hello.go`. Then, you can run the program by executing the following command in your terminal:```shellgo run hello.go```The output will be:```Hello, world!```
"""
;
return Scaffold( appBar: AppBar( backgroundColor: Theme.of(context).colorScheme.inversePrimary, title: Text(widget.title), ), body: Center( child: Markdown(
selectable: true,
data: markdown, ), )); }
}
The resulting output can be seen in the image below. Markdown is rendered correctly, but the code does not have any special styles and multi-line selection is not supported.

Now, let’s see how to customize code styles.
How to customize styles:
Use the styleSheet property to customize the default styles of Markdown. You can use the TextStyle object to specify font, color, size, and other properties.
Here is the modified code where the color of the code block has been changed. It sets the font size of h1 headings to 24 pixels, the font size of code to 14 pixels, and the color to green.
body: Center( child: Markdown(
selectable: true,
data: markdown, styleSheet: MarkdownStyleSheet(
h1: const TextStyle(fontSize: 24, color: Colors.blue),
code: const TextStyle(fontSize: 14, color: Colors.green), ), ),
));

Let’s further optimize the styles:
body: Center( child: Markdown(
selectable: true,
data: markdown, styleSheet: MarkdownStyleSheet(
h1: const TextStyle(fontSize: 24, color: Colors.blue),
code: const TextStyle(
fontSize: 14, color: Colors.white, backgroundColor: Colors.grey),
codeblockPadding: const EdgeInsets.all(8),
codeblockDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: Colors.grey, ), ), ),
)
Here, I have used codeblockPadding and codeblockDecoration.
-
codeblockPaddingcontrols the padding of the code block. You can use theEdgeInsetsobject to specify horizontal, vertical, left, right, top, and bottom padding properties.
MarkdownStyleSheet(
codeblockPadding: EdgeInsets.all(8),
)
This sets the padding of the code block to 8 pixels.
-
codeblockDecorationcontrols the decoration of the code block. You can use theBoxDecorationobject to specify background color, borders, corner radius, and other properties.
MarkdownStyleSheet( codeblockDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: Colors.grey, ),
)
This sets the background color of the code block to gray and adds rounded borders.

However, multi-line copying is still not supported. This may be because flutter_markdown uses many separate widgets to render Markdown, which may not be compatible with each other.
How to enable multi-line selection:
To solve the issue of multi-line selection, we can use the flutter_markdown_selectionarea package.
To use it just replace flutter_markdown dependency in your pubspec.yaml with
flutter_markdown_selectionarea: ^0.6.17+1
and then change all imports from
import 'package:flutter_markdown/flutter_markdown.dart';
to
import 'package:flutter_markdown_selectionarea/flutter_markdown.dart';
and then you will be able to make multiple paragraphs selectable by using SelectionArea widget:
SelectionArea(
child: MarkdownBody(data: text),
Here is the modified code:
body: Center( child: SelectionArea( child: Markdown( data: markdown, styleSheet: MarkdownStyleSheet(
h1: const TextStyle(fontSize: 24, color: Colors.blue),
code: const TextStyle(
fontSize: 14, color: Colors.white, backgroundColor: Colors.grey),
codeblockPadding: const EdgeInsets.all(8),
codeblockDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: Colors.grey, ), ), ), ),
));
Now, multi-line selection is supported.

One More Thing
Although multi-line selection is supported, you may notice that the color of the selected code block does not change, which can be confusing and make it uncertain whether it is actually selected.
To address this, I used Flutter Markdown’s MarkdownElementBuilder to customize the rendering of Markdown.
To use MarkdownElementBuilder, you need to create a subclass of MarkdownElementBuilder and implement the visitElementAfter() method. In the visitElementAfter() method, you can generate custom Flutter components based on the type of Markdown element.
Here is a simple example:
class CodeElementBuilder extends MarkdownElementBuilder {
bool isCodeBlock(md.Element element) {
if (element.attributes['class'] != null) {
return true;
} else if (element.textContent.contains("\n")) {
return true; }
return false;
}
Widget? visitElementAfter(md.Element element, TextStyle? preferredStyle) {
if (!isCodeBlock(element)) {
return Container(
padding: const EdgeInsets.all(2), child: Text( element.textContent, style: TextStyle(
fontSize: 16,
fontStyle: FontStyle.italic, color: preferredStyle!.color), ), );
} else {
return Container(
margin: const EdgeInsets.all(10), child: Text( element.textContent,
style: const TextStyle(fontSize: 16, color: Colors.white),
), ); } }
}
body: Center( child: SelectionArea( child: Markdown( data: markdown, builders: {
'code': CodeElementBuilder(),
}, styleSheet: MarkdownStyleSheet(
h1: const TextStyle(fontSize: 24, color: Colors.blue),
code: const TextStyle(
fontSize: 14, color: Colors.pink, backgroundColor: Colors.grey),
codeblockPadding: const EdgeInsets.all(8),
codeblockDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: Colors.grey, ), ), ), ),
)
From the image below, you can clearly see which content is selected.

🎉🎉🎉
Remaining issues:
- How to highlight code blocks.
- How to select code blocks after highlighting.