ff_tiptap_viewer
Render TipTap JSON as native, selectable and copyable Flutter widgets. Zero runtime dependencies; composable extensions; a single theming surface.
Features
- Accepts the API's JSON string or an already-decoded
Map. - Renders
paragraph,blockquote,bulletList,orderedList(withstart),listItem, andtext; marksbold,italic,underline,strike. - Ships a custom
Mentionextension (opt-in — see below). - TipTap-style extension list — include only the nodes/marks you want; the rest degrade gracefully (wrap → unwrap, leaf → strip).
- Text is selectable/copyable out of the box (
SelectionArea). - One
TiptapViewerThemecontrols all visual tokens. - A flattened
TiptapTextwidget (plus a puretoPlainText()) for compact, truncatable previews — see Compact previews.
Usage
TiptapViewer(
document: courseDescriptionJsonString, // String or Map<String, dynamic>
extensions: <TiptapExtension>[
Paragraph(), TextNode(), Bold(), Italic(), Underline(), Strike(),
Blockquote(), BulletList(), OrderedList(), ListItem(),
Mention(onTap: (id, label) {/* navigate; id is opaque */}),
],
theme: TiptapViewerTheme.fromContext(context),
selectable: true,
)
Omit extensions to use the default set (everything except Mention — add
that explicitly so mentions only render when you wire up onTap).
Compact previews (TiptapText)
TiptapViewer builds a column of block widgets — the full document. For a
list/card preview you usually want the opposite: the whole document flattened
onto one truncatable line. TiptapText does that.
TiptapText(
document: courseDescriptionJsonString, // String or Map
maxLines: 2,
overflow: TextOverflow.ellipsis, // line cap
maxChars: 120, // hard character cap (adds an ellipsis)
includeStyle: false, // strip bold/italic/underline/strike
)
Inline runs concatenate; block siblings (paragraphs, list items, blockquote
lines, …) are joined with separator (a space by default). Block structure —
indents, bullets, numbers, blockquote bars — is intentionally dropped; this is
the "plain" path. Mentions always render as @label (in the theme's mention
color/weight when includeStyle is true), so a preview never silently drops one.
maxLines + overflow give a visual line cap; maxChars is a hard character
cap that cuts the text and appends ellipsis (default …, set '' to cut with
no marker). TiptapText is not wrapped in a SelectionArea by default
(selectable: false) — previews are usually tap-through.
Note: with
overflow: TextOverflow.ellipsis, the trailing…is drawn by Flutter's text layout and adopts the style of the run it truncates inside — so when the cut lands in marked text (andincludeStyleis true), the…can pick up that run's weight/color/decoration (e.g. a strikethrough running through the ellipsis). This is a Flutter limitation. ThemaxCharscap is not affected — it appends its ownellipsisin the base style — so reach formaxChars(orincludeStyle: false) when a clean ellipsis matters.
When you only need a String (search, accessibility labels), skip the widget and
call TiptapDocument.toPlainText() (or TiptapNode.toPlainText()) directly.
Mentions (custom extension)
Mention is a custom extension, kept out of the default set so it only renders
when you add it explicitly and wire up onTap.
Mention.onTap receives the raw (id, label) strings exactly as stored;
the package never parses any id convention (e.g. course@123) — your app
decides what an id means.
Mention(display: …) chooses the render strategy:
MentionDisplay.highlight(default) — a styledTextSpan. Selects and copies as real text; lighter, with a flat highlight.MentionDisplay.chip— a roundedWidgetSpanpill. Selection and copy are faithful too (the@labellands in the clipboard) — verified on web and iOS. Re-check on Android/desktop before relying on chip copy there.
Theming
TiptapViewerTheme is the single styling surface; extensions only choose which
widgets get produced. Derive defaults with TiptapViewerTheme.fromContext(context),
then copyWith(...).
FlutterFlow
A package can't import the host app's FlutterFlowTheme, but mapping it is
trivial since FF exposes plain Color/TextStyle. In the FlutterFlow app:
TiptapViewerTheme tiptapThemeFromFlutterFlow(FlutterFlowTheme ff) {
return TiptapViewerTheme(
baseTextStyle: ff.bodyMedium.copyWith(color: ff.primaryText),
blockquoteBorderColor: ff.alternate,
mentionColor: ff.primary,
orderedNumberStyle: ff.bodyMedium.copyWith(color: ff.primaryText),
);
}
See example/lib/fake_flutter_flow_theme.dart for a runnable version.
Example
cd example
flutter create . # generate platform folders (web/android/…)
flutter run -d chrome # or an emulator
License
MIT — see LICENSE.
Libraries
- ff_tiptap_viewer
- Renders TipTap JSON as native, selectable Flutter widgets, with a composable extension list and a single theming surface.