diff --git a/flutter_highlight/README.md b/flutter_highlight/README.md
index 4c3f34f..0af7a85 100644
--- a/flutter_highlight/README.md
+++ b/flutter_highlight/README.md
@@ -44,6 +44,14 @@ class MyWidget extends StatelessWidget {
 }
 ```
 
+### Background processing
+
+Processing large amounts of text can be slow. To perform text processing in the background, add a
+`HighlightBackgroundEnvironment` above any `HighlightView`s in your widget tree.
+
+A background isolate will be automatically started in `HighlightBackgroundEnvironment.initState`, and stopped in
+`HighlightBackgroundEnvironment.dispose`.
+
 ## References
 
 - [All available languages](https://github.com/pd4d10/highlight/tree/master/highlight/lib/languages)
diff --git a/flutter_highlight/example/lib/main.dart b/flutter_highlight/example/lib/main.dart
index 74ada48..86edb9e 100644
--- a/flutter_highlight/example/lib/main.dart
+++ b/flutter_highlight/example/lib/main.dart
@@ -1,7 +1,9 @@
 import 'package:flutter/material.dart';
 import 'package:flutter_highlight/flutter_highlight.dart';
+import 'package:flutter_highlight/flutter_highlight_background.dart';
 import 'package:flutter_highlight/theme_map.dart';
 import 'package:url_launcher/url_launcher.dart';
+
 import 'example_map.dart';
 
 void main() => runApp(MyApp());
@@ -95,14 +97,16 @@ class _MyHomePageState extends State<MyHomePage> {
         child: Column(
           mainAxisAlignment: MainAxisAlignment.center,
           children: <Widget>[
-            HighlightView(
-              exampleMap[language],
-              language: language,
-              theme: themeMap[theme],
-              padding: EdgeInsets.all(12),
-              textStyle: TextStyle(
-                  fontFamily:
-                      'SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace'),
+            HighlightBackgroundEnvironment(
+              child: HighlightView(
+                exampleMap[language],
+                language: language,
+                theme: themeMap[theme],
+                padding: EdgeInsets.all(12),
+                textStyle: TextStyle(
+                    fontFamily:
+                        'SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace'),
+              ),
             )
           ],
         ),
diff --git a/flutter_highlight/lib/flutter_highlight.dart b/flutter_highlight/lib/flutter_highlight.dart
index 9afbc47..7c2327d 100644
--- a/flutter_highlight/lib/flutter_highlight.dart
+++ b/flutter_highlight/lib/flutter_highlight.dart
@@ -1,9 +1,11 @@
-import 'package:flutter/material.dart';
+import 'dart:async';
+
 import 'package:flutter/widgets.dart';
+import 'package:flutter_highlight/flutter_highlight_background.dart';
 import 'package:highlight/highlight.dart' show highlight, Node;
 
 /// Highlight Flutter Widget
-class HighlightView extends StatelessWidget {
+class HighlightView extends StatefulWidget {
   /// The original code to be highlighted
   final String source;
 
@@ -27,6 +29,12 @@ class HighlightView extends StatelessWidget {
   /// Specify text styles such as font family and font size
   final TextStyle? textStyle;
 
+  /// Progress indicator
+  ///
+  /// A widget that is displayed while the [source] is being processed.
+  /// This may only be used if a [HighlightBackgroundEnvironment] is available.
+  final Widget? progressIndicator;
+
   HighlightView(
     String input, {
     this.language,
@@ -34,9 +42,27 @@ class HighlightView extends StatelessWidget {
     this.padding,
     this.textStyle,
     int tabSize = 8, // TODO: https://github.com/flutter/flutter/issues/50087
+    this.progressIndicator,
   }) : source = input.replaceAll('\t', ' ' * tabSize);
 
-  List<TextSpan> _convert(List<Node> nodes) {
+  static const _rootKey = 'root';
+  static const _defaultFontColor = Color(0xff000000);
+  static const _defaultBackgroundColor = Color(0xffffffff);
+
+  // TODO: dart:io is not available at web platform currently
+  // See: https://github.com/flutter/flutter/issues/39998
+  // So we just use monospace here for now
+  static const _defaultFontFamily = 'monospace';
+
+  @override
+  State<HighlightView> createState() => _HighlightViewState();
+
+  /// Renders a list of [nodes] into a list of [TextSpan]s using the given
+  /// [theme].
+  static List<TextSpan> render(
+    List<Node> nodes,
+    Map<String, TextStyle> theme,
+  ) {
     List<TextSpan> spans = [];
     var currentSpans = spans;
     List<List<TextSpan>> stack = [];
@@ -48,7 +74,8 @@ class HighlightView extends StatelessWidget {
             : TextSpan(text: node.value, style: theme[node.className!]));
       } else if (node.children != null) {
         List<TextSpan> tmp = [];
-        currentSpans.add(TextSpan(children: tmp, style: theme[node.className!]));
+        currentSpans
+            .add(TextSpan(children: tmp, style: theme[node.className!]));
         stack.add(currentSpans);
         currentSpans = tmp;
 
@@ -67,34 +94,96 @@ class HighlightView extends StatelessWidget {
 
     return spans;
   }
+}
 
-  static const _rootKey = 'root';
-  static const _defaultFontColor = Color(0xff000000);
-  static const _defaultBackgroundColor = Color(0xffffffff);
+class _HighlightViewState extends State<HighlightView> {
+  late Future<List<Node>> _nodesFuture;
+  late Future<List<TextSpan>> _spansFuture;
 
-  // TODO: dart:io is not available at web platform currently
-  // See: https://github.com/flutter/flutter/issues/39998
-  // So we just use monospace here for now
-  static const _defaultFontFamily = 'monospace';
+  void _parse(HighlightBackgroundProvider? backgroundProvider) => _nodesFuture =
+      backgroundProvider?.parse(widget.source, language: widget.language) ??
+          Future.value(
+            highlight.parse(widget.source, language: widget.language).nodes,
+          );
+
+  void _render(HighlightBackgroundProvider? backgroundProvider) =>
+      _spansFuture = _nodesFuture.then((nodes) =>
+          (backgroundProvider?.render(nodes, widget.theme) ??
+                  HighlightView.render(nodes, widget.theme))
+              as FutureOr<List<TextSpan>>);
+
+  void _parseAndRender(HighlightBackgroundProvider? backgroundProvider) {
+    if (backgroundProvider == null) {
+      _parse(null);
+      _render(null);
+    } else {
+      final resultFuture = backgroundProvider.parseAndRender(
+        widget.source,
+        widget.theme,
+        language: widget.language,
+      );
+      _nodesFuture = resultFuture.then((result) => result.nodes);
+      _spansFuture = resultFuture.then((result) => result.spans);
+    }
+  }
+
+  @override
+  void didChangeDependencies() {
+    super.didChangeDependencies();
+    final backgroundProvider = HighlightBackgroundProvider.maybeOf(context);
+    _parseAndRender(backgroundProvider);
+  }
+
+  @override
+  void didUpdateWidget(HighlightView oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.source != oldWidget.source ||
+        widget.language != oldWidget.language) {
+      final backgroundProvider = HighlightBackgroundProvider.maybeOf(context);
+      _parseAndRender(backgroundProvider);
+    } else if (widget.theme != oldWidget.theme) {
+      final backgroundProvider = HighlightBackgroundProvider.maybeOf(context);
+      _render(backgroundProvider);
+    }
+  }
 
   @override
   Widget build(BuildContext context) {
     var _textStyle = TextStyle(
-      fontFamily: _defaultFontFamily,
-      color: theme[_rootKey]?.color ?? _defaultFontColor,
+      fontFamily: HighlightView._defaultFontFamily,
+      color: widget.theme[HighlightView._rootKey]?.color ??
+          HighlightView._defaultFontColor,
     );
-    if (textStyle != null) {
-      _textStyle = _textStyle.merge(textStyle);
+    if (widget.textStyle != null) {
+      _textStyle = _textStyle.merge(widget.textStyle);
     }
 
     return Container(
-      color: theme[_rootKey]?.backgroundColor ?? _defaultBackgroundColor,
-      padding: padding,
-      child: RichText(
-        text: TextSpan(
-          style: _textStyle,
-          children: _convert(highlight.parse(source, language: language).nodes!),
-        ),
+      color: widget.theme[HighlightView._rootKey]?.backgroundColor ??
+          HighlightView._defaultBackgroundColor,
+      padding: widget.padding,
+      child: FutureBuilder<List<TextSpan>>(
+        future: _spansFuture,
+        builder: (context, snapshot) {
+          if (!snapshot.hasData) {
+            final progressIndicator = widget.progressIndicator;
+            if (progressIndicator == null) {
+              return const SizedBox.shrink();
+            } else {
+              assert(
+                HighlightBackgroundProvider.maybeOf(context) != null,
+                'Cannot display a progress indicator unless a HighlightBackgroundEnvironment is available!',
+              );
+              return progressIndicator;
+            }
+          }
+          return RichText(
+            text: TextSpan(
+              style: _textStyle,
+              children: snapshot.requireData,
+            ),
+          );
+        },
       ),
     );
   }
diff --git a/flutter_highlight/lib/flutter_highlight_background.dart b/flutter_highlight/lib/flutter_highlight_background.dart
new file mode 100644
index 0000000..b79572a
--- /dev/null
+++ b/flutter_highlight/lib/flutter_highlight_background.dart
@@ -0,0 +1,308 @@
+import 'dart:async';
+import 'dart:isolate';
+
+import 'package:flutter/widgets.dart';
+import 'package:flutter_highlight/flutter_highlight.dart';
+import 'package:highlight/highlight.dart' show highlight, Node;
+
+/// A result of a combined parse and render operation done in a
+/// [HighlightBackgroundEnvironment].
+class HighlightBackgroundResult {
+  final List<Node> nodes;
+  final List<TextSpan> spans;
+
+  const HighlightBackgroundResult(this.nodes, this.spans);
+}
+
+/// A widget that provides a background [Isolate] to do expensive highlighting
+/// work in.
+///
+/// The [HighlightView] will detect and use the background environment
+/// automatically.
+/// It can also be used manually through the [HighlightBackgroundProvider]
+/// [InheritedWidget].
+class HighlightBackgroundEnvironment extends StatefulWidget {
+  final Widget child;
+
+  const HighlightBackgroundEnvironment({
+    Key? key,
+    required this.child,
+  }) : super(key: key);
+
+  @override
+  State<HighlightBackgroundEnvironment> createState() =>
+      _HighlightBackgroundEnvironmentState();
+}
+
+class _HighlightBackgroundEnvironmentState
+    extends State<HighlightBackgroundEnvironment> {
+  late final Completer<SendPort> _sendPortCompleter;
+  late final StreamController<_IdentifiableIsolateMessage>
+      _resultStreamController;
+
+  @override
+  void initState() {
+    super.initState();
+    _sendPortCompleter = Completer();
+    _resultStreamController = StreamController.broadcast();
+    final receivePort = ReceivePort();
+    receivePort.listen((response) {
+      if (response is _IdentifiableIsolateResponse) {
+        _resultStreamController.add(response);
+      } else if (response is _IsolateStartedResponse) {
+        _sendPortCompleter.complete(response.sendPort);
+      } else if (response is _IsolateEndedResponse) {
+        receivePort.close();
+      }
+    });
+    Isolate.spawn(_isolateEntrypoint, receivePort.sendPort);
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+    _sendPortCompleter.future
+        .then((sendPort) => sendPort.send(_IsolateEndRequest()));
+  }
+
+  Future<T> _performTask<T extends _IdentifiableIsolateResponse>(
+      _IdentifiableIsolateRequest<T> request) {
+    _sendPortCompleter.future.then((sendPort) => sendPort.send(request));
+    return _resultStreamController.stream
+        .firstWhere(
+            (message) => identical(message.identifier, request.identifier))
+        .then((response) => response as T);
+  }
+
+  Future<List<Node>> _parse(String source, {String? language}) =>
+      _performTask(_ParseRequest(source, language: language))
+          .then((response) => response.nodes);
+
+  Future<List<TextSpan>> _render(
+    List<Node> nodes,
+    Map<String, TextStyle> theme,
+  ) =>
+      _performTask(_RenderRequest(nodes, theme))
+          .then((response) => response.spans);
+
+  Future<HighlightBackgroundResult> _parseAndRender(
+    String source,
+    Map<String, TextStyle> theme, {
+    String? language,
+  }) =>
+      _performTask(_ParseAndRenderRequest(source, theme, language: language))
+          .then((response) =>
+              HighlightBackgroundResult(response.nodes, response.spans));
+
+  @override
+  Widget build(BuildContext context) {
+    return HighlightBackgroundProvider._(
+      environmentIdentifier: this,
+      parse: _parse,
+      render: _render,
+      parseAndRender: _parseAndRender,
+      child: widget.child,
+    );
+  }
+}
+
+class HighlightBackgroundProvider extends InheritedWidget {
+  final Object environmentIdentifier;
+  final Future<List<Node>> Function(String source, {String? language}) parse;
+  final Future<List<TextSpan>> Function(
+    List<Node> nodes,
+    Map<String, TextStyle> theme,
+  ) render;
+  final Future<HighlightBackgroundResult> Function(
+    String source,
+    Map<String, TextStyle> theme, {
+    String? language,
+  }) parseAndRender;
+
+  HighlightBackgroundProvider._({
+    Key? key,
+    required this.environmentIdentifier,
+    required this.parse,
+    required this.render,
+    required this.parseAndRender,
+    required Widget child,
+  }) : super(
+          key: key,
+          child: child,
+        );
+
+  @override
+  bool updateShouldNotify(HighlightBackgroundProvider oldWidget) =>
+      !identical(environmentIdentifier, oldWidget.environmentIdentifier);
+
+  static HighlightBackgroundProvider of(BuildContext context) {
+    final backgroundProvider = maybeOf(context);
+    assert(backgroundProvider != null,
+        'No HighlightBackgroundProvider found in context');
+    return backgroundProvider!;
+  }
+
+  static HighlightBackgroundProvider? maybeOf(BuildContext context) =>
+      context.dependOnInheritedWidgetOfExactType<HighlightBackgroundProvider>();
+}
+
+void _isolateEntrypoint(SendPort sendPort) {
+  final receivePort = ReceivePort();
+  receivePort.listen((request) {
+    if (request is _ParsingRequest || request is _RenderingRequest) {
+      late final List<Node> nodes;
+      if (request is _ParsingRequest) {
+        nodes =
+            highlight.parse(request.source, language: request.language).nodes!;
+      } else if (request is _RenderRequest) {
+        nodes = request.nodes;
+      }
+      if (request is _ParseRequest) {
+        sendPort.send(_ParseResponse(request, nodes));
+      } else if (request is _RenderingRequest) {
+        final spans = HighlightView.render(nodes, request.theme);
+        if (request is _RenderRequest) {
+          sendPort.send(_RenderResponse(request, spans));
+        } else if (request is _ParseAndRenderRequest) {
+          sendPort.send(_ParseAndRenderResponse(request, nodes, spans));
+        }
+      }
+    } else if (request is _IsolateEndRequest) {
+      receivePort.close();
+      sendPort.send(const _IsolateEndedResponse());
+    }
+  });
+  sendPort.send(_IsolateStartedResponse(receivePort.sendPort));
+}
+
+abstract class _IdentifiableIsolateMessage {
+  Capability get identifier;
+}
+
+abstract class _IsolateRequest {}
+
+abstract class _IdentifiableIsolateRequest<
+        T extends _IdentifiableIsolateResponse>
+    implements _IsolateRequest, _IdentifiableIsolateMessage {}
+
+class _IsolateEndRequest implements _IsolateRequest {
+  const _IsolateEndRequest();
+}
+
+abstract class _ParsingRequest<T extends _ParsingResponse>
+    implements _IdentifiableIsolateRequest<T> {
+  String get source;
+
+  String? get language;
+}
+
+class _ParseRequest implements _ParsingRequest<_ParseResponse> {
+  @override
+  final Capability identifier = Capability();
+
+  @override
+  final String source;
+
+  @override
+  final String? language;
+
+  _ParseRequest(this.source, {this.language});
+}
+
+abstract class _RenderingRequest<T extends _RenderingResponse>
+    implements _IdentifiableIsolateRequest<T> {
+  Map<String, TextStyle> get theme;
+}
+
+class _RenderRequest implements _RenderingRequest {
+  @override
+  final Capability identifier = Capability();
+
+  final List<Node> nodes;
+
+  @override
+  final Map<String, TextStyle> theme;
+
+  _RenderRequest(this.nodes, this.theme);
+}
+
+class _ParseAndRenderRequest
+    implements
+        _ParsingRequest<_ParseAndRenderResponse>,
+        _RenderingRequest<_ParseAndRenderResponse> {
+  @override
+  final Capability identifier = Capability();
+
+  @override
+  final String source;
+
+  @override
+  final String? language;
+
+  @override
+  final Map<String, TextStyle> theme;
+
+  _ParseAndRenderRequest(this.source, this.theme, {this.language});
+}
+
+abstract class _IsolateResponse {}
+
+abstract class _IdentifiableIsolateResponse
+    implements _IsolateResponse, _IdentifiableIsolateMessage {}
+
+class _IsolateStartedResponse implements _IsolateResponse {
+  final SendPort sendPort;
+
+  const _IsolateStartedResponse(this.sendPort);
+}
+
+class _IsolateEndedResponse implements _IsolateResponse {
+  const _IsolateEndedResponse();
+}
+
+abstract class _ParsingResponse implements _IdentifiableIsolateResponse {
+  List<Node> get nodes;
+}
+
+class _ParseResponse implements _ParsingResponse {
+  @override
+  final Capability identifier;
+
+  @override
+  final List<Node> nodes;
+
+  _ParseResponse(_ParseRequest request, this.nodes)
+      : identifier = request.identifier;
+}
+
+abstract class _RenderingResponse implements _IdentifiableIsolateResponse {
+  List<TextSpan> get spans;
+}
+
+class _RenderResponse implements _RenderingResponse {
+  @override
+  final Capability identifier;
+
+  @override
+  final List<TextSpan> spans;
+
+  _RenderResponse(_RenderRequest request, this.spans)
+      : identifier = request.identifier;
+}
+
+class _ParseAndRenderResponse implements _ParsingResponse, _RenderingResponse {
+  @override
+  final Capability identifier;
+
+  @override
+  final List<Node> nodes;
+
+  @override
+  final List<TextSpan> spans;
+
+  _ParseAndRenderResponse(
+    _ParseAndRenderRequest request,
+    this.nodes,
+    this.spans,
+  ) : identifier = request.identifier;
+}