pub package codecov style: very good analysis

⚠️ Alpha prerelease: 1.0.0-alpha.3 is part of the native rich content runtime line. APIs may change before the stable 1.0.0 release.

🌊 tagflow

Tagflow is a native rich content runtime for Flutter apps. It renders semantic TagflowDocument content with Flutter widgets and keeps HTML support through the first-party TagflowHtmlAdapter.

✨ Features

  • Render native TagflowDocument content with Flutter widgets
  • Parse HTML through the first-party TagflowHtmlAdapter
  • Render HTML <details> / <summary> as native expandable disclosure widgets
  • Apply explicit TagflowContentPolicy rules to adapter input
  • Override semantic node rendering through TagflowComponentRegistry
  • Configure runtime behavior with TagflowViewOptions
  • Keep parser and converter compatibility through package:tagflow/legacy.dart

Feature Highlights

HTML Adapter

import 'package:flutter/widgets.dart';
import 'package:tagflow/tagflow.dart';

class ArticleBody extends StatelessWidget {
  const ArticleBody({required this.html, super.key});

  final String html;

  @override
  Widget build(BuildContext context) {
    return Tagflow.html(
      html: html,
      viewOptions: TagflowViewOptions(
        selectable: const TagflowSelectableOptions(enabled: true),
        linkTapCallback: (url, attributes) {
          // Open the URL with your app's navigation layer.
        },
      ),
    );
  }
}

Use the adapter directly when you want to parse once, inspect, cache, or apply stricter HTML policy before rendering:

const adapter = TagflowHtmlAdapter(
  policy: TagflowContentPolicy(
    allowRemoteImages: false,
    allowedSchemes: {'https', 'mailto'},
  ),
);

final document = adapter.parse(htmlContent);

Tagflow.document(document);

Controlled Dynamic HTML

Use authored IDs when your HTML comes from a controlled CMS, AI pipeline, or server formatter that can re-emit the same logical blocks across updates. Default path IDs are fine for static HTML, but inserting a new block near the top renumbers later siblings and makes existing widgets look new on a full reparse.

const adapter = TagflowHtmlAdapter(
  nodeIdStrategy: TagflowHtmlNodeIdStrategy.attribute(),
);

final before = adapter.parse('''
<p data-tagflow-id="summary">Summary</p>
<p data-tagflow-id="details">Details</p>
''');

final after = adapter.parse('''
<p data-tagflow-id="callout">New callout</p>
<p data-tagflow-id="summary">Summary</p>
<p data-tagflow-id="details">Details</p>
''');

summary and details keep their authored IDs after the insertion. If every dynamic node must be annotated, set fallbackToPath: false to fail fast on missing IDs. Duplicate IDs fail during adaptation.

Native Documents

import 'package:tagflow/tagflow.dart';

final document = TagflowDocument(
  id: 'article-42',
  children: [
    TagflowDocumentNode.heading(
      id: 'article-42.title',
      level: 1,
      children: [
        TagflowDocumentNode.text(
          id: 'article-42.title.text',
          text: 'Native rich content',
        ),
      ],
    ),
    TagflowDocumentNode.paragraph(
      id: 'article-42.intro',
      children: [
        TagflowDocumentNode.text(
          id: 'article-42.intro.text',
          text: 'HTML is an adapter, not the runtime model.',
        ),
      ],
    ),
  ],
);

Tagflow.document(document);

Native JSON Transport

Use TagflowNativeBlockCodec when a trusted app backend, CMS, or AI pipeline already emits structured data and you do not want to route it through HTML. The codec accepts a deliberately small data-only JSON shape, the adapter turns that payload into a TagflowDocument, and the same document runtime renders it:

const codec = TagflowNativeBlockCodec();
const adapter = TagflowNativeBlockAdapter();

final nativePayload = codec.decodeDocument({
  'id': 'announcement-42',
  'schemaVersion': 1,
  'revision': 'cms-rev-7',
  'blocks': [
    {
      'id': 'announcement-42.title',
      'kind': 'heading',
      'attributes': {'level': 1},
      'children': [
        {
          'id': 'announcement-42.title.text',
          'kind': 'text',
          'text': 'Structured update',
        },
      ],
    },
  ],
});

final document = adapter.adapt(nativePayload);

Tagflow.document(document);

schemaVersion is intentionally strict in alpha. Documents and patch envelopes must use schemaVersion: 1; other values fail during TagflowNativeBlockCodec decode until a reviewed compatibility policy exists. Do not emit future schema versions speculatively.

Unknown native JSON kind values and unknown patch op values also fail during codec decode. TagflowUnsupportedBehavior applies after decode to known blocks rejected by adapter policy, such as a known image block whose URL policy rejects the source.

Patch envelopes use TagflowNativeBlockPatchEnvelope: decode the producer envelope, adapt the ordered native operations, then apply runtime document patches.

final envelope = codec.decodePatchEnvelope({
  'id': 'announcement-42',
  'schemaVersion': 1,
  'baseRevision': 'cms-rev-7',
  'revision': 'cms-rev-8',
  'operations': [
    {
      'op': 'insert-before',
      'siblingNodeId': 'announcement-42.title',
      'blocks': [
        {
          'id': 'announcement-42.notice',
          'kind': 'paragraph',
          'children': [
            {
              'id': 'announcement-42.notice.text',
              'kind': 'text',
              'text': 'This update came from trusted app JSON.',
            },
          ],
        },
      ],
    },
  ],
});

final updatedDocument = document.applyPatches(
  adapter.adaptPatches(envelope.operations),
);

This transport is for trusted, app-controlled structured content. It is not a webpage renderer, JavaScript environment, arbitrary CMS sync layer, or generic serializer for Flutter widgets and callbacks.

Semantic Renderer Overrides

final registry = TagflowComponentRegistry(
  overrides: {
    TagflowNodeKind.paragraph: (context, node) {
      return Padding(
        padding: const EdgeInsets.only(bottom: 12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: context.renderChildren(node),
        ),
      );
    },
  },
);

Tagflow.document(document, registry: registry);

Tagflow.html(
  html: htmlContent,
  registry: registry,
);

Use Tagflow.html(..., registry: registry) when HTML-origin content only needs semantic render overrides. Use TagflowHtmlAdapter plus Tagflow.document(...) when the app also needs authored IDs, strict HTML policy, inspection, or caching before rendering.

Compatibility Imports

Parser, converter, selector, and legacy node APIs are still available during the alpha transition from the compatibility barrel:

import 'package:tagflow/legacy.dart';

Use it for existing TagflowParser, ElementConverter, TagflowNode, or selector-based custom converter integrations. New runtime code should prefer package:tagflow/tagflow.dart, Tagflow.document(...), Tagflow.html(...), TagflowHtmlAdapter, and TagflowComponentRegistry.

Theming

Tagflow.html(
  html: articleContent,
  theme: TagflowTheme.fromTheme(
    Theme.of(context),
    headingConfig: const TagflowHeadingConfig(
      baseSize: 16,
      scales: [2.5, 2, 1.75, 1.5, 1.25, 1],
    ),
  ),
);

Installation

Add tagflow to your pubspec.yaml:

dependencies:
  tagflow: ^1.0.0-alpha.3

Supported Features

  • Native semantic document rendering
  • Trusted native JSON document decode, adapt, and render flow
  • Trusted native JSON patch envelope decode, adapt, and apply flow
  • HTML adapter support for headings, paragraphs, emphasis, links, code, blockquotes, lists, images, and tables
  • Content policy filtering for unsafe tags, URL schemes, and unsupported input
  • Runtime view options for links, selection, image behavior, caching, and render errors
  • HTML comment render boundaries for adapter input
  • Legacy parser and converter compatibility for alpha migration

Theme System

Tagflow's theme system integrates with Flutter's Material Design while providing customization hooks for supported rich content:

  • Material integration with app colors and typography
  • Styles for supported semantic nodes, HTML tags, and classes
  • Responsive units such as rem, em, percentages, viewport width, and viewport height where supported
  • Color parsing and named color support

Theme Configuration

TagflowTheme.fromTheme(
  Theme.of(context),
  spacingConfig: const TagflowSpacingConfig(baseSize: 16, scale: 1.2),
);

Documentation

Visit our documentation for detailed guides and examples.

For the v1 alpha migration direction, see docs/migration/2026-06-11-tagflow-v1-alpha-migration.md. The example app also includes a Native JSON Transport screen that decodes native block JSON, renders it with Tagflow.document(...), and applies a patch envelope through TagflowNativeBlockAdapter.adaptPatches(...). The native transport benchmark lane is available through dart run melos run benchmark:native-transport; current results are report-only local evidence, not public performance claims.

Contributing

Contributions are welcome! Please read our contributing guidelines before submitting pull requests.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Libraries

legacy
tagflow