diff --git a/packages/wyatt_ui_components/lib/src/domain/entities/entities.dart b/packages/wyatt_ui_components/lib/src/domain/entities/entities.dart
index f04375b8..85ce40c3 100644
--- a/packages/wyatt_ui_components/lib/src/domain/entities/entities.dart
+++ b/packages/wyatt_ui_components/lib/src/domain/entities/entities.dart
@@ -23,4 +23,5 @@ export './error_widget_component.dart';
export './loader_component.dart';
export './loader_style.dart';
export './loading_widget_component.dart';
+export './rich_text_builder/rich_text_builder.dart';
export './theme_style.dart';
diff --git a/packages/wyatt_ui_components/lib/src/domain/entities/rich_text_builder/parser.dart b/packages/wyatt_ui_components/lib/src/domain/entities/rich_text_builder/parser.dart
new file mode 100644
index 00000000..bd92d252
--- /dev/null
+++ b/packages/wyatt_ui_components/lib/src/domain/entities/rich_text_builder/parser.dart
@@ -0,0 +1,120 @@
+// Copyright (C) 2023 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+import 'package:flutter/widgets.dart';
+
+class RichTextStyleParameter {
+ const RichTextStyleParameter(
+ this.defaultStyle,
+ this.definedStyle,
+ this.styleName,
+ );
+
+ final TextStyle defaultStyle;
+ final Map definedStyle;
+ final String? styleName;
+
+ TextStyle get style {
+ if (definedStyle.containsKey(styleName)) {
+ return definedStyle[styleName]!;
+ }
+ return defaultStyle;
+ }
+
+ RichTextStyleParameter copyWith({
+ TextStyle? defaultStyle,
+ Map? definedStyle,
+ String? styleName,
+ }) =>
+ RichTextStyleParameter(
+ defaultStyle ?? this.defaultStyle,
+ definedStyle ?? this.definedStyle,
+ styleName ?? this.styleName,
+ );
+}
+
+class RichTextNode {
+ RichTextNode(this.nodes);
+
+ final List nodes;
+
+ static RichTextNode from(
+ String content,
+ RegExp regex,
+ RichTextStyleParameter styleParameter,
+ ) {
+ final matches = regex.allMatches(content);
+ if (matches.isNotEmpty) {
+ // match found -> construct node with leaf/nodes
+ final List nodes = [];
+ for (var i = 0; i < matches.length; i++) {
+ final previousMatch = i > 0 ? matches.elementAt(i - 1) : null;
+ final currentMatch = matches.elementAt(i);
+ // non match before
+ final nonMatchBefore = (previousMatch != null)
+ ? content.substring(previousMatch.end, currentMatch.start)
+ : content.substring(0, currentMatch.start);
+ nodes
+ ..add(RichTextNode.from(nonMatchBefore, regex, styleParameter))
+ // match
+ ..add(
+ RichTextNode.from(
+ currentMatch.group(2)!,
+ regex,
+ styleParameter.copyWith(styleName: currentMatch.group(1)),
+ ),
+ );
+ }
+ // non match after
+ final nonMatchAfter = content.substring(matches.last.end);
+ nodes.add(RichTextNode.from(nonMatchAfter, regex, styleParameter));
+ return RichTextNode(nodes);
+ } else {
+ // match not found -> construct leaf
+ return RichTextLeaf(styleParameter.style, content);
+ }
+ }
+
+ InlineSpan toInlineSpan(RichTextParser parser) {
+ final children = [];
+ for (final node in nodes) {
+ children.add(node.toInlineSpan(parser));
+ }
+ return TextSpan(children: children);
+ }
+}
+
+class RichTextLeaf extends RichTextNode {
+ RichTextLeaf(this.style, this.content) : super([]);
+
+ final TextStyle style;
+ final String content;
+
+ @override
+ InlineSpan toInlineSpan(RichTextParser parser) =>
+ parser.nodeBuilder.call(content, style);
+}
+
+class RichTextParser {
+ const RichTextParser({required this.nodeBuilder});
+ factory RichTextParser.defaultBuilder() => RichTextParser(
+ nodeBuilder: (content, style) => TextSpan(
+ text: content,
+ style: style,
+ ),
+ );
+ final InlineSpan Function(String content, TextStyle style) nodeBuilder;
+}
diff --git a/packages/wyatt_ui_components/lib/src/domain/entities/rich_text_builder/rich_text_builder.dart b/packages/wyatt_ui_components/lib/src/domain/entities/rich_text_builder/rich_text_builder.dart
new file mode 100644
index 00000000..1eae2365
--- /dev/null
+++ b/packages/wyatt_ui_components/lib/src/domain/entities/rich_text_builder/rich_text_builder.dart
@@ -0,0 +1,19 @@
+// Copyright (C) 2023 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+export 'parser.dart';
+export 'rich_text_builder_component.dart';
+export 'rich_text_builder_style.dart';
diff --git a/packages/wyatt_ui_components/lib/src/domain/entities/rich_text_builder/rich_text_builder_component.dart b/packages/wyatt_ui_components/lib/src/domain/entities/rich_text_builder/rich_text_builder_component.dart
new file mode 100644
index 00000000..603aaa32
--- /dev/null
+++ b/packages/wyatt_ui_components/lib/src/domain/entities/rich_text_builder/rich_text_builder_component.dart
@@ -0,0 +1,56 @@
+// Copyright (C) 2023 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+import 'package:flutter/widgets.dart';
+import 'package:wyatt_component_copy_with_extension/component_copy_with_extension.dart';
+import 'package:wyatt_ui_components/wyatt_wyatt_ui_components.dart';
+
+part 'rich_text_builder_component.g.dart';
+
+@ComponentProxyExtension()
+abstract class RichTextBuilderComponent extends Component
+ with CopyWithMixin<$RichTextBuilderComponentCWProxy> {
+ const RichTextBuilderComponent({
+ this.text,
+ this.parser,
+ this.defaultStyle,
+ this.styles,
+ super.themeResolver,
+ super.key,
+ });
+
+ /// Full text
+ final String? text;
+
+ /// How to build InlineSpans
+ final RichTextParser? parser;
+
+ /// Default TextStyle used in this rich text component.
+ final TextStyle? defaultStyle;
+
+ /// Used styles in this rich text component.
+ ///
+ /// e.g.
+ /// ```dart
+ /// styles = {'red': TextStyle(color: Colors.red)};
+ /// ```
+ /// will transform:
+ /// ```text
+ /// This text is red? styles;
+}
diff --git a/packages/wyatt_ui_components/lib/src/domain/entities/rich_text_builder/rich_text_builder_component.g.dart b/packages/wyatt_ui_components/lib/src/domain/entities/rich_text_builder/rich_text_builder_component.g.dart
new file mode 100644
index 00000000..9c5194e6
--- /dev/null
+++ b/packages/wyatt_ui_components/lib/src/domain/entities/rich_text_builder/rich_text_builder_component.g.dart
@@ -0,0 +1,25 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'rich_text_builder_component.dart';
+
+// **************************************************************************
+// ComponentProxyGenerator
+// **************************************************************************
+
+abstract class $RichTextBuilderComponentCWProxy {
+ RichTextBuilderComponent text(String? text);
+ RichTextBuilderComponent parser(RichTextParser? parser);
+ RichTextBuilderComponent defaultStyle(TextStyle? defaultStyle);
+ RichTextBuilderComponent styles(Map? styles);
+ RichTextBuilderComponent themeResolver(
+ ThemeResolver? themeResolver);
+ RichTextBuilderComponent key(Key? key);
+ RichTextBuilderComponent call({
+ String? text,
+ RichTextParser? parser,
+ TextStyle? defaultStyle,
+ Map? styles,
+ ThemeResolver? themeResolver,
+ Key? key,
+ });
+}
diff --git a/packages/wyatt_ui_components/lib/src/domain/entities/rich_text_builder/rich_text_builder_style.dart b/packages/wyatt_ui_components/lib/src/domain/entities/rich_text_builder/rich_text_builder_style.dart
new file mode 100644
index 00000000..43a03ff5
--- /dev/null
+++ b/packages/wyatt_ui_components/lib/src/domain/entities/rich_text_builder/rich_text_builder_style.dart
@@ -0,0 +1,83 @@
+// Copyright (C) 2023 WYATT GROUP
+// Please see the AUTHORS file for details.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+import 'package:flutter/widgets.dart';
+import 'package:wyatt_ui_components/wyatt_wyatt_ui_components.dart';
+
+class RichTextBuilderStyle extends ThemeStyle {
+ const RichTextBuilderStyle({
+ this.defaultStyle,
+ this.styles,
+ });
+
+ /// Merges non-null `b` attributes in `a`
+ static RichTextBuilderStyle? merge(
+ RichTextBuilderStyle? a,
+ RichTextBuilderStyle? b,
+ ) {
+ if (b == null) {
+ return a?.copyWith();
+ }
+ if (a == null) {
+ return b.copyWith();
+ }
+
+ return a.copyWith(
+ defaultStyle: b.defaultStyle,
+ styles: b.styles,
+ );
+ }
+
+ /// Used for interpolation.
+ static RichTextBuilderStyle? lerp(
+ RichTextBuilderStyle? a,
+ RichTextBuilderStyle? b,
+ double t,
+ ) {
+ if (a == null || b == null) {
+ return null;
+ }
+ // b.copyWith to return b attributes even if they are not lerped
+ return b.copyWith(
+ defaultStyle: TextStyle.lerp(a.defaultStyle, b.defaultStyle, t),
+ styles: b.styles, // TODO(wyatt): compute lerp value of each styles
+ );
+ }
+
+ /// Default TextStyle used in this rich text component.
+ final TextStyle? defaultStyle;
+
+ /// Used styles in this rich text component.
+ final Map? styles;
+
+ @override
+ RichTextBuilderStyle mergeWith(RichTextBuilderStyle? other) =>
+ RichTextBuilderStyle.merge(this, other)!;
+
+ @override
+ RichTextBuilderStyle? lerpWith(RichTextBuilderStyle? other, double t) =>
+ RichTextBuilderStyle.lerp(this, other, t);
+
+ @override
+ RichTextBuilderStyle copyWith({
+ TextStyle? defaultStyle,
+ Map? styles,
+ }) =>
+ RichTextBuilderStyle(
+ defaultStyle: defaultStyle ?? this.defaultStyle,
+ styles: styles ?? this.styles,
+ );
+}