npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@estusana/tiptap-react-native-renderer

v0.1.1

Published

Lightweight TipTap JSON renderer for React Native

Readme

TipTap React Native Renderer

A feature-rich and highly customizable React Native renderer for TipTap editor content. This library is designed to provide a flexible bridge between TipTap's JSON output and native React Native components.

⚠️ Current Status: Beta

This is a new library and should be considered beta. It has been developed with a focus on performance and stability, but it has not yet been extensively tested in a large-scale production environment.

iOS testing, in particular, is needed. Community feedback, bug reports, and contributions are highly welcome as we work to make this library ready for production use.

🚀 Features

Core Rendering

  • Broad Node Support: Renders most standard TipTap nodes, including paragraphs, headings, lists, blockquotes, code blocks, images, and tables.
  • Rich Text Formatting: Supports common text marks like bold, italic, underline, strikethrough, links, highlights, subscript, and superscript.
  • Advanced Rendering: Handles nested blockquotes, tables, and images with configurable fallbacks.

Designed for Performance & Stability

  • Performance-Focused: Uses React.memo, memoized handlers, and efficient key generation to optimize rendering performance.
  • Error Boundaries: Includes component-level error boundaries to help prevent a single faulty node from crashing the entire render view.
  • Zero TipTap Dependencies: The renderer is self-contained and does not require any TipTap packages to function.

Excellent Developer Experience

  • TypeScript First: Written entirely in TypeScript with comprehensive type definitions.
  • JSDoc Documentation: The API is documented with examples and usage guidance.
  • Development Warnings: Provides helpful console warnings for common issues and performance tips in development builds.

Customization

  • Flexible Theming: Configure colors, typography, and spacing to match your app's design system.
  • Custom Components & Handlers: Override the rendering for any node or mark type to implement custom logic or use your own components.
  • Simple Mode: A lightweight rendering mode designed for maximum performance, ideal for previews or very large documents.

📦 Installation

npm install @estusana/tiptap-react-native-renderer

🚀 Quick Start

import React from "react";
import { ScrollView } from "react-native";
import { TipTapRenderer } from "@estusana/tiptap-react-native-renderer";

const MyComponent = () => {
  const content = {
    type: "doc",
    content: [
      {
        type: "heading",
        attrs: { level: 1 },
        content: [{ type: "text", text: "Welcome to TipTap React Native" }],
      },
      {
        type: "paragraph",
        content: [
          { type: "text", text: "This is a " },
          { type: "text", text: "bold", marks: [{ type: "bold" }] },
          { type: "text", text: " and " },
          { type: "text", text: "italic", marks: [{ type: "italic" }] },
          { type: "text", text: " text with a " },
          {
            type: "text",
            text: "link",
            marks: [{ type: "link", attrs: { href: "https://tiptap.dev" } }],
          },
          { type: "text", text: "." },
        ],
      },
    ],
  };

  return (
    <ScrollView style={{ flex: 1, padding: 20 }}>
      <TipTapRenderer
        content={content}
        performanceConfig={{ enabled: true }}
        devConfig={{ warnings: true }}
      />
    </ScrollView>
  );
};

📋 Supported Content Types

Node Types

  • Document (doc) - Root container
  • Paragraph (paragraph) - Text paragraphs with formatting
  • Heading (heading) - Headings (levels 1-6) with dynamic sizing
  • Blockquote (blockquote) - Quoted content with nesting support
  • Code Block (codeBlock) - Code with syntax highlighting labels
  • Bullet List (bulletList) - Unordered lists with custom styling
  • Ordered List (orderedList) - Numbered lists with custom start numbers
  • List Item (listItem) - Individual list items with proper indentation
  • Hard Break (hardBreak) - Line breaks for text formatting
  • Horizontal Rule (horizontalRule) - Divider lines with custom styling
  • Image (image) - Images with error handling and accessibility
  • Table (table) - Tables with headers and proper styling
  • Table Row (tableRow) - Table rows with consistent formatting
  • Table Cell (tableCell) - Table cells with alignment support
  • Table Header (tableHeader) - Table headers with distinct styling

Mark Types

  • Bold (bold) - Bold text formatting
  • Italic (italic) - Italic text formatting
  • Underline (underline) - Underlined text
  • Strike (strike) - Strikethrough text
  • Code (code) - Inline code with monospace font
  • Link (link) - Clickable links with React Native integration
  • Highlight (highlight) - Highlighted text with custom colors
  • Subscript (subscript) - Subscript text positioning
  • Superscript (superscript) - Superscript text positioning

⚙️ Configuration

Complete Style Configuration

<TipTapRenderer
  content={content}
  styleConfig={{
    colors: {
      primary: "#2563eb",
      secondary: "#64748b",
      background: "#ffffff",
      text: "#1f2937",
      link: "#3b82f6",
      highlight: "#fef08a",
      codeBackground: "#f1f5f9",
      blockquoteBackground: "#f8fafc",
      horizontalRule: "#e5e7eb",
    },
    typography: {
      fontFamily: "System",
      fontSize: 16,
      lineHeight: 24,
      headingFontFamily: "System-Bold",
      codeFontFamily: "Courier",
    },
    spacing: {
      paragraph: 16,
      heading: 20,
      list: 12,
      blockquote: 16,
      codeBlock: 16,
    },
  }}
/>

Advanced Link Configuration

<TipTapRenderer
  content={content}
  linkConfig={{
    openInBrowser: true,
    onPress: (url) => {
      console.log("Link pressed:", url);
      // Custom analytics or validation
      if (url.includes("unsafe")) {
        Alert.alert("Warning", "This link may be unsafe");
        return;
      }
    },
    customLinkComponent: ({ url, children }) => (
      <TouchableOpacity onPress={() => handleCustomLink(url)}>
        <Text style={customLinkStyle}>{children}</Text>
      </TouchableOpacity>
    ),
  }}
/>

Image Configuration

<TipTapRenderer
  content={content}
  imageConfig={{
    defaultStyle: {
      borderRadius: 8,
      marginVertical: 12,
    },
    onError: (error) => {
      console.warn("Image failed to load:", error);
      // Custom error handling or fallback
    },
    customImageComponent: ({ src, alt, style }) => (
      <FastImage
        source={{ uri: src }}
        style={style}
        alt={alt}
        resizeMode="contain"
      />
    ),
  }}
/>

Note: The default React Native <Image> component does not support SVG files. To render SVGs, you will need to install a library like react-native-svg and provide a customImageComponent.

import { SvgUri } from "react-native-svg";

<TipTapRenderer
  content={content}
  imageConfig={{
    onError: (error) => console.warn("Image failed to load:", error),
    customImageComponent: ({ src, alt, style }) => {
      if (src.endsWith(".svg")) {
        return <SvgUri uri={src} style={style} accessibilityLabel={alt} />;
      }
      return <Image source={{ uri: src }} style={style} alt={alt} />;
    },
  }}
/>;

Performance Monitoring

<TipTapRenderer
  content={content}
  performanceConfig={{
    enabled: true,
    renderTimeWarning: 100, // Warn if render takes > 100ms
    onMetrics: (metrics) => {
      console.log(
        `Rendered ${metrics.nodeCount} nodes in ${metrics.renderTime}ms`
      );
      // Send to analytics service
      analytics.track("render_performance", metrics);
    },
  }}
/>

Development Configuration

<TipTapRenderer
  content={content}
  devConfig={{
    warnings: __DEV__, // Enable warnings in development
    performance: __DEV__, // Enable performance monitoring in development
    validation: true, // Enable prop validation
  }}
/>

Simple Mode (High Performance)

For maximum performance with large documents or when you need minimal rendering overhead, enable simple mode:

<TipTapRenderer
  content={content}
  simpleMode={{
    enabled: true,
    textStyle: { fontSize: 16, color: "#333" },
    linkStyle: { fontSize: 16, color: "#007AFF" },
    spacing: 10,
  }}
/>

Simple mode features:

  • Lightweight rendering: Minimal component overhead and optimized for speed
  • Essential nodes only: Supports paragraphs, headings, blockquotes, lists, and basic text formatting
  • Reduced complexity: No extensions, image loading states, or advanced features
  • Customizable styling: Basic text and link styling options
  • Still extensible: Custom handlers can still override simple mode behaviors

Perfect for:

  • Preview modes
  • Large document rendering
  • Performance-critical applications
  • Simple content display

🎨 Custom Handlers

Custom Node Handlers

<TipTapRenderer
  content={content}
  customHandlers={{
    paragraph: ({ node, renderNode }) => (
      <View style={[defaultParagraphStyle, customParagraphStyle]}>
        {node.content?.map((child, index) => (
          <React.Fragment key={`${child.type}-${index}`}>
            {renderNode(child)}
          </React.Fragment>
        ))}
      </View>
    ),
    heading: ({ node, renderNode }) => {
      const level = node.attrs?.level || 1;
      const HeadingComponent = level === 1 ? H1 : level === 2 ? H2 : H3;

      return (
        <HeadingComponent>
          {node.content?.map((child, index) => (
            <React.Fragment key={index}>{renderNode(child)}</React.Fragment>
          ))}
        </HeadingComponent>
      );
    },
  }}
/>

Custom Mark Handlers

<TipTapRenderer
  content={content}
  customMarkHandlers={{
    bold: (mark, children) => (
      <Text style={{ fontWeight: "900", color: "#1f2937" }}>{children}</Text>
    ),
    link: (mark, children) => {
      const url = mark.attrs?.href;
      return (
        <TouchableOpacity onPress={() => handleCustomLink(url)}>
          <Text style={customLinkStyle}>{children}</Text>
        </TouchableOpacity>
      );
    },
    highlight: (mark, children) => (
      <View style={{ backgroundColor: "#fef08a", paddingHorizontal: 4 }}>
        <Text>{children}</Text>
      </View>
    ),
  }}
/>

📚 API Reference

TipTapRendererProps

| Prop | Type | Required | Description | | -------------------- | ------------------------------------- | -------- | ----------------------------------------- | | content | TipTapDocument \| TipTapNode | ✅ | The TipTap content to render | | customHandlers | Record<string, NodeHandler> | ❌ | Custom node handlers to override defaults | | customMarkHandlers | Record<string, MarkHandler> | ❌ | Custom mark handlers to override defaults | | styleConfig | StyleConfig | ❌ | Style configuration for theming | | linkConfig | LinkConfig | ❌ | Link handling configuration | | imageConfig | ImageConfig | ❌ | Image handling configuration | | performanceConfig | PerformanceConfig | ❌ | Performance monitoring configuration | | devConfig | DevConfig | ❌ | Development mode configuration | | customComponents | Record<string, React.ComponentType> | ❌ | Custom components for specific node types |

StyleConfig

interface StyleConfig {
  colors?: {
    primary?: string;
    secondary?: string;
    background?: string;
    text?: string;
    link?: string;
    highlight?: string;
    codeBackground?: string;
    blockquoteBackground?: string;
    horizontalRule?: string;
  };
  typography?: {
    fontFamily?: string;
    fontSize?: number;
    lineHeight?: number;
    headingFontFamily?: string;
    codeFontFamily?: string;
  };
  spacing?: {
    paragraph?: number;
    heading?: number;
    list?: number;
    blockquote?: number;
    codeBlock?: number;
  };
}

Performance Metrics

interface PerformanceMetrics {
  renderTime: number; // Total render time in milliseconds
  nodeCount: number; // Number of nodes rendered
  timestamp: number; // Timestamp of measurement
}

🎯 Advanced Examples

Complete Blog Post Renderer

const BlogPostRenderer = ({ post }) => {
  const handleLinkPress = (url: string) => {
    if (url.startsWith("/")) {
      // Internal navigation
      navigation.navigate("Article", { slug: url.slice(1) });
    } else {
      // External link
      Linking.openURL(url);
    }
  };

  const trackPerformance = (metrics: PerformanceMetrics) => {
    if (metrics.renderTime > 200) {
      analytics.track("slow_render", {
        renderTime: metrics.renderTime,
        nodeCount: metrics.nodeCount,
        postId: post.id,
      });
    }
  };

  return (
    <ScrollView style={styles.container}>
      <TipTapRenderer
        content={post.content}
        styleConfig={{
          colors: {
            primary: "#1a202c",
            link: "#3182ce",
            highlight: "#faf089",
            codeBackground: "#f7fafc",
          },
          typography: {
            fontSize: 18,
            lineHeight: 28,
            fontFamily: "Georgia",
          },
          spacing: {
            paragraph: 20,
            heading: 24,
          },
        }}
        linkConfig={{
          onPress: handleLinkPress,
          openInBrowser: false,
        }}
        imageConfig={{
          defaultStyle: {
            borderRadius: 12,
            marginVertical: 16,
          },
          onError: () => {
            // Track image loading errors
            analytics.track("image_load_error", { postId: post.id });
          },
        }}
        performanceConfig={{
          enabled: true,
          onMetrics: trackPerformance,
          renderTimeWarning: 200,
        }}
      />
    </ScrollView>
  );
};

Custom Table Renderer

const CustomTableRenderer = ({ content }) => {
  return (
    <TipTapRenderer
      content={content}
      customHandlers={{
        table: ({ node, renderNode }) => (
          <ScrollView horizontal showsHorizontalScrollIndicator={false}>
            <View style={styles.table}>
              {node.content?.map((child, index) => (
                <React.Fragment key={index}>{renderNode(child)}</React.Fragment>
              ))}
            </View>
          </ScrollView>
        ),
        tableCell: ({ node, renderNode }) => (
          <View style={[styles.tableCell, { minWidth: 120 }]}>
            {node.content?.map((child, index) => (
              <React.Fragment key={index}>{renderNode(child)}</React.Fragment>
            ))}
          </View>
        ),
      }}
      styleConfig={{
        colors: {
          primary: "#374151",
        },
      }}
    />
  );
};

🔧 Performance Guidelines

Optimization Tips

  1. Use Performance Monitoring: Enable performanceConfig to identify potential bottlenecks.
  2. Memoize Custom Handlers: If passing custom handlers as props, wrap them in useCallback or useMemo to prevent unnecessary re-renders.
  3. Optimize Images: Use appropriately sized raster images and consider lazy loading for documents with many images.

Performance Observations

While more extensive benchmarking is needed, the design of this library aims for high performance:

  • Simple Mode: This mode is designed to offer a significant performance improvement for large documents by using a minimal component tree.
  • Document Slicing: The slice prop can be used to improve initial render times for very long content by rendering only a subset of nodes.

🤝 Contributing

This project is new and contributions are incredibly valuable. The best way you can help is by using the library and providing feedback.

How You Can Help

  • Testing on iOS: The library has primarily been tested on Android. Testing on various iOS devices and versions is a top priority.
  • Reporting Bugs: If you find an issue, please open a detailed bug report on GitHub.
  • Performance Testing: Share your results using the built-in performance monitor on large or complex documents.
  • Pull Requests: Feel free to fix bugs or add features. Please open an issue to discuss significant changes first.

📄 License

MIT License