Rich inline text styling for Flutter — your tags, your syntax, zero friction.
Define [tags] once, nest them freely, and render styled text natively via
Flutter's RichText / TextSpan. No Markdown lock-in, no fixed syntax.
❗ In order to start using Text Mark you must have the Flutter SDK installed on your machine.
Install via flutter pub add:
dart pub add text_mark- Your syntax —
[tag]by default, or any custom format viaTagFormat - Nesting —
[bold][red]...[/red][/bold]just works; child styles win on conflict - Dynamic values —
[c=FF5733],[size=24]— value embedded in the tag - Three API levels — local styles / dynamic tags / global
TextMarkTheme - LRU cache — parse results cached by (text + rules fingerprint), 200 entries default
- Extensions —
.textmark()onStringandText - Zero dependencies — pure Flutter
TextMark(
text: "[bold]Hello[/bold] [red]world[/red]!",
tagFormat: TagFormat.bracket,
styles: {
'bold': TextStyle(fontWeight: FontWeight.bold),
'red': TextStyle(color: Color(0xFFE53935)),
},
)Define your styles once in a dedicated file:
// app_text_styles.dart
class AppTextStyles {
static const styles = <String, TextStyle>{
'red': TextStyle(color: Color(0xFFE53935)),
'bold': TextStyle(fontWeight: FontWeight.bold),
'h1': TextStyle(fontSize: 28, fontWeight: FontWeight.w800),
'warn': TextStyle(color: Color(0xFFF57F17), backgroundColor: Color(0xFFFFF9C4)),
};
static final dynamicStyles = <String, TextStyle Function(String?)>{
'c': (v) => TextStyle(color: _hexColor(v ?? 'FF0000')),
'size': (v) => TextStyle(fontSize: double.tryParse(v ?? '16') ?? 16),
};
}Place the theme inside MaterialApp.builder:
MaterialApp(
builder: (context, child) => TextMarkTheme(
tagFormat: TagFormat.bracket,
styles: AppTextStyles.styles,
dynamicStyles: AppTextStyles.dynamicStyles,
child: child!,
),
)Then anywhere in the tree — no repeated config:
// Widget
TextMark(text: "[h1]Title[/h1] with [red]alert[/red]")
// String extension
"[bold]Easy[/bold] as [red]that[/red].".textmark()
// Text extension — preserves style, textAlign, maxLines…
Text("[warn]Warning:[/warn] read carefully.", style: TextStyle(fontSize: 16))
.textmark()TextMark(
text: "[bold]This is bold and [red]this part is also red[/red], "
"back to bold only[/bold], then plain.",
)- Child styles win on conflict (e.g. a red child inside a blue parent → red)
- Parent styles propagate where the child is silent (e.g. bold parent + red child → bold + red)
- Misordered closing tags degrade gracefully — no crash
TextMark(
text: "Price: [c=E53935]€49[/c] Size: [size=24]big[/size]",
dynamicStyles: {
'c': (v) => TextStyle(color: _hexColor(v ?? 'FF0000')),
'size': (v) => TextStyle(fontSize: double.tryParse(v ?? '16') ?? 16),
'bg': (v) => TextStyle(backgroundColor: _hexColor(v ?? 'FFFF00')),
'font': (v) => TextStyle(fontFamily: v),
},
)Local styles are merged on top of the theme — local keys always win:
TextMark(
text: "[bold]Global bold[/bold] and [vip]local only[/vip].",
styles: {
'vip': TextStyle(color: Color(0xFFFFB300), fontWeight: FontWeight.w600),
},
)TagFormat.bracket // [tag]...[/tag] ← default, recommended
TagFormat.doubleBrace // {{tag}}...{{/tag}}
TagFormat.wrap('@', '::') // @tag::...@/tag::
TagFormat.custom( // fully custom RegExp
open: RegExp(r'<(\w+)>'),
close: RegExp(r'</(\w+)>'),
)[tag] is the recommended default — safe in JSON, YAML, and API payloads with no escaping required.
The parse cache lives on TextMarkTheme and is shared across the entire subtree.
Cache size is configurable:
TextMarkTheme(cacheSize: 500, ...) // default: 200The cache key is hash(rawText) + hash(registeredTagNames).
Style value changes without tag name changes do NOT invalidate the cache —
style maps are expected to be constants.
-
WidgetSpansupport — inline icons / custom widgets - Parse result caching with style-value fingerprint
- Accessibility / semantics label support