quill_web_editor 1.1.0 copy "quill_web_editor: ^1.1.0" to clipboard
quill_web_editor: ^1.1.0 copied to clipboard

Platformweb

A rich text editor package for Flutter Web powered by Quill.js. Provides full-featured editing with tables, media embedding, custom fonts, and HTML export.

Quill Web Editor #

A powerful, full-featured rich text editor package for Flutter Web powered by Quill.js.

Flutter Quill.js License: MIT

Quill Logo

๐ŸŽฎ Try It Out #

Experience the editor live in your browser:

๐Ÿš€ Live Playground

Try all the features including rich text formatting, tables, media embedding, zoom controls, and more!


โœจ Features #

Feature Description
๐Ÿ“ Rich Text Editing Full formatting toolbar with bold, italic, underline, strikethrough
๐ŸŽฎ Controller API QuillEditorController for programmatic control (like TextEditingController)
โšก Custom Actions Register and execute user-defined actions with callbacks
๐Ÿ“Š Table Support Create and edit tables with quill-table-better
๐Ÿ–ผ๏ธ Media Embedding Images, videos, and iframes with resize controls
๐ŸŽจ Custom Fonts Roboto, Open Sans, Lato, Montserrat, Crimson Pro, DM Sans, Source Code Pro
๐Ÿ“ Font Sizes Small, Normal, Large, Huge
๐Ÿ“ Line Heights 1.0, 1.5, 2.0, 2.5, 3.0
๐Ÿ”— Links & Code Hyperlinks, blockquotes, inline code, code blocks
๐Ÿ“‹ Smart Paste Preserves fonts and formatting when pasting
๐Ÿ’พ HTML Export Clean HTML export with all styles preserved
๐Ÿ” Preview Full-screen preview with print support
๐Ÿ”Ž Zoom Controls 50% to 300% zoom range
๐Ÿ˜€ Emoji Support Built-in emoji picker
โœ… Checklists Task lists with checkboxes
๐ŸŽฏ Alignment Left, center, right, justify

๐Ÿ“ฆ Installation #

Step 1: Add Dependency #

Add to your pubspec.yaml:

dependencies:
  quill_web_editor:
    git:
      url: https://github.com/ff-vivek/flutter_quill_web_editor.git

Step 2: Copy Web Assets #

Copy the HTML files from the package to your app's web/ directory:

your_app/
โ””โ”€โ”€ web/
    โ”œโ”€โ”€ index.html
    โ”œโ”€โ”€ quill_editor.html    โ† Copy from package
    โ””โ”€โ”€ quill_viewer.html    โ† Copy from package

Step 3: Import #

import 'package:quill_web_editor/quill_web_editor.dart';

๐Ÿš€ Quick Start #

๐Ÿ’ก New to Quill Web Editor? Try the live playground to see it in action before diving into code!

Simple Usage (No Controller) #

When you don't need programmatic access, just use callbacks:

QuillEditorWidget(
  onContentChanged: (html, delta) {
    print('Content: $html');
  },
  initialHtml: '<p>Start writing...</p>',
)

For programmatic control, use QuillEditorController:

import 'package:flutter/material.dart';
import 'package:quill_web_editor/quill_web_editor.dart';

class MyEditor extends StatefulWidget {
  const MyEditor({super.key});

  @override
  State<MyEditor> createState() => _MyEditorState();
}

class _MyEditorState extends State<MyEditor> {
  final _controller = QuillEditorController();
  String _currentHtml = '';

  @override
  void dispose() {
    _controller.dispose();  // You manage the lifecycle
    super.dispose();
  }

  void _insertGreeting() {
    _controller.insertText('Hello, World!');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
            onPressed: _insertGreeting,
            icon: Icon(Icons.add),
          ),
          IconButton(
            onPressed: () => _controller.undo(),
            icon: Icon(Icons.undo),
          ),
        ],
      ),
      body: QuillEditorWidget(
        controller: _controller,
        onContentChanged: (html, delta) {
          setState(() => _currentHtml = html);
        },
        initialHtml: '<p>Start writing...</p>',
      ),
    );
  }
}

Read-Only Viewer #

QuillEditorWidget(
  readOnly: true,
  initialHtml: '<p>This content is read-only</p>',
)

๐Ÿ“– API Reference #

For detailed developer documentation, including architecture, feature deep-dives, and extension guides, see the Developer Guide.

QuillEditorController #

A controller for programmatic access to the editor, similar to TextEditingController.

Properties

Property Type Description
isReady bool Whether editor is ready for commands
html String Current HTML content
currentZoom double Current zoom level (1.0 = 100%)
registeredActionNames Set<String> Names of registered custom actions

Methods

Method Description
insertText(String text) Insert plain text at cursor
setHTML(String html, {bool replace = true}) Set editor content from HTML
insertHtml(String html) Insert HTML at cursor position
setContents(dynamic delta) Set content from Quill Delta
getContents() Request current content
clear() Clear all editor content
focus() Focus the editor
undo() Undo the last operation
redo() Redo the last undone operation
format(String format, dynamic value) Apply formatting to selection
insertTable(int rows, int cols) Insert a table at cursor
zoomIn() Increase zoom by 10%
zoomOut() Decrease zoom by 10%
resetZoom() Reset zoom to 100%
setZoom(double level) Set specific zoom level (0.5 - 3.0)

Custom Actions

Register and execute user-defined actions:

// Define a reusable action
final timestampAction = QuillEditorAction(
  name: 'insertTimestamp',
  onExecute: () => print('Inserting timestamp...'),
);

// Register the action
_controller.registerAction(timestampAction);

// Execute with optional parameters
_controller.executeAction('insertTimestamp', parameters: {
  'format': 'ISO',
});

// Or execute one-off actions without registration
_controller.executeCustom(
  actionName: 'insertSignature',
  parameters: {'name': 'John Doe'},
);

QuillEditorWidget #

The main editor widget that embeds Quill.js via an iframe.

Constructor Parameters

Parameter Type Default Description
controller QuillEditorController? null Controller for programmatic access
width double? null Editor width (expands to fill if null)
height double? null Editor height (expands to fill if null)
onContentChanged Function(String html, dynamic delta)? null Callback when content changes
onReady VoidCallback? null Callback when editor is initialized
readOnly bool false Enable viewer mode
initialHtml String? null Initial HTML content
initialDelta dynamic null Initial Quill Delta content
placeholder String? null Placeholder text
editorHtmlPath String? 'quill_editor.html' Custom editor HTML path
viewerHtmlPath String? 'quill_viewer.html' Custom viewer HTML path

Note: If no controller is provided, an internal controller is created automatically (like TextField).

Method Description
setHTML(String html, {bool replace = true}) Set editor content from HTML
insertHtml(String html, {bool replace = false}) Insert HTML at cursor position
setContents(dynamic delta) Set content from Quill Delta
insertText(String text) Insert plain text at cursor
getContents() Request current content (triggers callback)
clear() Clear all editor content
focus() Focus the editor
undo() Undo the last operation
redo() Redo the last undone operation
format(String format, dynamic value) Apply formatting to selection
insertTable(int rows, int cols) Insert a table at cursor
zoomIn() Increase zoom by 10%
zoomOut() Decrease zoom by 10%
resetZoom() Reset zoom to 100%
setZoom(double level) Set specific zoom level (0.5 - 3.0)

Properties

Property Type Description
currentZoom double Current zoom level (1.0 = 100%)
isReady bool Whether editor is ready for commands

๐Ÿงฉ Components #

SaveStatusIndicator #

Displays save status with animated transitions.

SaveStatusIndicator(status: SaveStatus.saved)
SaveStatusIndicator(status: SaveStatus.saving)
SaveStatusIndicator(status: SaveStatus.unsaved)

ZoomControls #

Zoom in/out controls with percentage display.

// With controller (reactive)
ListenableBuilder(
  listenable: _controller,
  builder: (context, _) => ZoomControls(
    zoomLevel: _controller.currentZoom,
    onZoomIn: () => _controller.zoomIn(),
    onZoomOut: () => _controller.zoomOut(),
    onReset: () => _controller.resetZoom(),
  ),
)

OutputPreview #

Tabbed preview showing HTML source and plain text.

OutputPreview(html: _currentHtml)

StatCard & StatCardRow #

Display document statistics.

// Single stat
StatCard(label: 'Words', value: '150')

// Multiple stats in a row
StatCardRow(
  stats: [
    (label: 'Words', value: '150', icon: Icons.text_fields),
    (label: 'Characters', value: '890', icon: Icons.format_size),
  ],
)

AppCard #

Styled container card with optional title.

AppCard(
  title: 'Document Info',  // Optional
  child: YourContent(),
)

HtmlPreviewDialog #

Full-screen HTML preview dialog with copy and print options.

// Show preview dialog
HtmlPreviewDialog.show(context, htmlContent);

InsertHtmlDialog #

Dialog for inserting raw HTML into the editor.

final result = await InsertHtmlDialog.show(context);
if (result != null) {
  if (result.replaceContent) {
    _controller.setHTML(result.html);
  } else {
    _controller.insertHtml(result.html);
  }
}

โšก Custom Actions #

Custom actions allow you to extend the editor with your own functionality.

Defining Actions #

// Create a reusable action
final timestampAction = QuillEditorAction(
  name: 'insertTimestamp',
  parameters: {'format': 'ISO'},
  onExecute: () => print('Inserting timestamp...'),
  onResponse: (response) => print('Done: $response'),
);

// Register with controller
_controller.registerAction(timestampAction);

Executing Actions #

// Execute a registered action
_controller.executeAction('insertTimestamp');

// Override parameters at execution time
_controller.executeAction('insertTimestamp', parameters: {
  'format': 'readable',
});

// Execute one-off action without registration
_controller.executeCustom(
  actionName: 'insertSignature',
  parameters: {'name': 'John Doe'},
  onResponse: (response) => print('Signature inserted'),
);

Use Cases #

  • Insert dynamic content (timestamps, user info, templates)
  • Trigger custom formatting or transformations
  • Integrate with external services
  • Add business-specific editor commands

๐Ÿ› ๏ธ Services #

DocumentService #

Utility service for document operations.

Download Files

// Download as HTML (with styles)
DocumentService.downloadHtml(
  htmlContent,
  filename: 'document.html',
  cleanHtml: true,  // Remove editor artifacts
);

// Download as plain text
DocumentService.downloadText(
  textContent,
  filename: 'document.txt',
);

Clipboard Operations

// Copy to clipboard
final success = await DocumentService.copyToClipboard(text);

// Read from clipboard
final text = await DocumentService.readFromClipboard();

Print

// Opens print-ready document in new tab
DocumentService.printHtml(htmlContent);

Local Storage

// Save draft
DocumentService.saveToLocalStorage('my-draft', htmlContent);

// Load draft
final draft = DocumentService.loadFromLocalStorage('my-draft');

// Check if exists
if (DocumentService.hasLocalStorage('my-draft')) {
  // ...
}

// Remove
DocumentService.removeFromLocalStorage('my-draft');

๐Ÿ”ง Utilities #

HtmlCleaner #

Process HTML for export.

// Clean editor artifacts (selection classes, data attributes)
final clean = HtmlCleaner.cleanForExport(dirtyHtml);

// Extract plain text from HTML
final text = HtmlCleaner.extractText(html);

// Check if content is empty
if (HtmlCleaner.isEmpty(html)) {
  print('No content');
}

// Normalize color to hex
final hex = HtmlCleaner.normalizeColor('rgb(255, 0, 0)');  // '#ff0000'

TextStats #

Calculate document statistics.

final stats = TextStats.fromHtml(html);

print('Words: ${stats.wordCount}');
print('Characters: ${stats.charCount}');
print('Characters (no spaces): ${stats.charCountNoSpaces}');
print('Paragraphs: ${stats.paragraphCount}');
print('Sentences: ${stats.sentenceCount}');

ExportStyles #

Generate CSS for exported HTML.

// Get full CSS string
final css = ExportStyles.fullCss;

// Generate complete HTML document
final fullHtml = ExportStyles.generateHtmlDocument(
  content,
  title: 'My Document',  // Optional
);

๐ŸŽจ Theming #

Using Built-in Theme #

MaterialApp(
  theme: AppTheme.lightTheme,
  home: MyApp(),
)

Color Palette #

AppColors.accent        // Primary accent color (#C45D35)
AppColors.surface       // Card/surface color (white)
AppColors.background    // Scaffold background
AppColors.textPrimary   // Primary text color
AppColors.textSecondary // Secondary text color
AppColors.border        // Border color
AppColors.success       // Success state color
AppColors.warning       // Warning state color
AppColors.error         // Error state color

Text Styles #

AppTheme.serifTextStyle  // Crimson Pro, 18px
AppTheme.sansTextStyle   // DM Sans, 14px
AppTheme.monoTextStyle   // Source Code Pro, 14px

Decorations #

Container(
  decoration: AppTheme.editorContainerDecoration,
  child: QuillEditorWidget(...),
)

Container(
  decoration: AppTheme.cardDecoration,
  child: YourContent(),
)

โš™๏ธ Configuration #

EditorConfig #

// Zoom settings
EditorConfig.minZoom         // 0.5 (50%)
EditorConfig.maxZoom         // 3.0 (300%)
EditorConfig.defaultZoom     // 1.0 (100%)
EditorConfig.zoomStep        // 0.1 (10%)

// Table defaults
EditorConfig.defaultTableRows  // 3
EditorConfig.defaultTableCols  // 3
EditorConfig.maxTableRows      // 20
EditorConfig.maxTableCols      // 10

// Timing
EditorConfig.contentChangeThrottleMs  // 200ms
EditorConfig.autoSaveDebounceMs       // 500ms

AppFonts #

// Available fonts
AppFonts.availableFonts  // List<FontConfig>

// Available sizes
AppFonts.availableSizes  // [small, normal, large, huge]

// Available line heights
AppFonts.availableLineHeights  // [1.0, 1.5, 2.0, 2.5, 3.0]

๐Ÿ“ Project Structure #

lib/
โ”œโ”€โ”€ quill_web_editor.dart              # Main export file
โ””โ”€โ”€ src/
    โ”œโ”€โ”€ core/
    โ”‚   โ”œโ”€โ”€ constants/
    โ”‚   โ”‚   โ”œโ”€โ”€ app_colors.dart        # Color palette
    โ”‚   โ”‚   โ”œโ”€โ”€ app_fonts.dart         # Font configurations
    โ”‚   โ”‚   โ””โ”€โ”€ editor_config.dart     # Editor settings
    โ”‚   โ”œโ”€โ”€ theme/
    โ”‚   โ”‚   โ””โ”€โ”€ app_theme.dart         # Theme data
    โ”‚   โ””โ”€โ”€ utils/
    โ”‚       โ”œโ”€โ”€ html_cleaner.dart      # HTML processing
    โ”‚       โ”œโ”€โ”€ text_stats.dart        # Document statistics
    โ”‚       โ””โ”€โ”€ export_styles.dart     # Export CSS generation
    โ”œโ”€โ”€ widgets/
    โ”‚   โ”œโ”€โ”€ quill_editor_widget.dart   # Main editor widget
    โ”‚   โ”œโ”€โ”€ quill_editor_controller.dart # Controller for programmatic access
    โ”‚   โ”œโ”€โ”€ save_status_indicator.dart # Save status display
    โ”‚   โ”œโ”€โ”€ zoom_controls.dart         # Zoom UI
    โ”‚   โ”œโ”€โ”€ output_preview.dart        # HTML/text preview
    โ”‚   โ”œโ”€โ”€ stat_card.dart             # Statistics cards
    โ”‚   โ”œโ”€โ”€ app_card.dart              # Styled container
    โ”‚   โ”œโ”€โ”€ html_preview_dialog.dart   # Preview dialog
    โ”‚   โ””โ”€โ”€ insert_html_dialog.dart    # HTML insertion dialog
    โ””โ”€โ”€ services/
        โ””โ”€โ”€ document_service.dart      # Document operations

web/
โ”œโ”€โ”€ quill_editor.html                  # Editor HTML (full-featured)
โ””โ”€โ”€ quill_viewer.html                  # Viewer HTML (read-only)

๐Ÿงช Testing #

The package includes comprehensive tests. Run them with:

flutter test

Testing with Google Fonts #

The package uses Google Fonts which require special setup for testing. Font files are bundled in test/fonts/ and configured in flutter_test_config.dart.


๐Ÿ’ก Examples #

Complete Editor with Sidebar #

class EditorPage extends StatefulWidget {
  @override
  State<EditorPage> createState() => _EditorPageState();
}

class _EditorPageState extends State<EditorPage> {
  final _controller = QuillEditorController();
  String _html = '';
  SaveStatus _status = SaveStatus.saved;

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Editor'),
        actions: [
          // Undo/Redo
          IconButton(
            onPressed: () => _controller.undo(),
            icon: Icon(Icons.undo),
            tooltip: 'Undo',
          ),
          IconButton(
            onPressed: () => _controller.redo(),
            icon: Icon(Icons.redo),
            tooltip: 'Redo',
          ),
          SizedBox(width: 8),
          // Zoom - uses controller's reactive zoom level
          ListenableBuilder(
            listenable: _controller,
            builder: (context, _) => ZoomControls(
              zoomLevel: _controller.currentZoom,
              onZoomIn: () => _controller.zoomIn(),
              onZoomOut: () => _controller.zoomOut(),
              onReset: () => _controller.resetZoom(),
            ),
          ),
          SaveStatusIndicator(status: _status),
          FilledButton.icon(
            onPressed: () => DocumentService.downloadHtml(_html),
            icon: Icon(Icons.save),
            label: Text('Save'),
          ),
        ],
      ),
      body: Row(
        children: [
          // Editor
          Expanded(
            child: Container(
              margin: EdgeInsets.all(24),
              decoration: AppTheme.editorContainerDecoration,
              child: ClipRRect(
                borderRadius: BorderRadius.circular(16),
                child: QuillEditorWidget(
                  controller: _controller,
                  onContentChanged: (html, delta) {
                    setState(() {
                      _html = html;
                      _status = SaveStatus.unsaved;
                    });
                  },
                ),
              ),
            ),
          ),
          // Sidebar
          SizedBox(
            width: 320,
            child: Padding(
              padding: EdgeInsets.all(24),
              child: Column(
                children: [
                  AppCard(
                    title: 'Statistics',
                    child: StatCardRow(
                      stats: [
                        (
                          label: 'Words',
                          value: TextStats.fromHtml(_html).wordCount.toString(),
                          icon: null,
                        ),
                      ],
                    ),
                  ),
                  SizedBox(height: 16),
                  Expanded(
                    child: AppCard(
                      title: 'Preview',
                      child: OutputPreview(html: _html),
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Auto-Save Implementation #

Timer? _saveTimer;

void _onContentChanged(String html, dynamic delta) {
  setState(() => _status = SaveStatus.unsaved);
  
  _saveTimer?.cancel();
  _saveTimer = Timer(Duration(milliseconds: 500), () {
    setState(() => _status = SaveStatus.saving);
    
    // Save to backend or local storage
    DocumentService.saveToLocalStorage('draft', html);
    
    setState(() => _status = SaveStatus.saved);
  });
}

๐Ÿ”— Running the Example #

cd example
flutter run -d chrome

๐Ÿš€ Deployment #

For production deployments, you can host the HTML files on a CDN or static hosting service and reference them via editorHtmlPath and viewerHtmlPath parameters.

Quick Setup #

QuillEditorWidget(
  editorHtmlPath: 'https://your-cdn.com/quill-editor/quill_editor.html',
  viewerHtmlPath: 'https://your-cdn.com/quill-editor/quill_viewer.html',
  onContentChanged: (html, delta) {
    // Handle changes
  },
)

Files to Host #

  • quill_editor.html - Main editor HTML
  • quill_viewer.html - Viewer HTML
  • js/ folder - Custom JavaScript overrides
  • styles/ folder - Custom CSS (e.g., mulish-font.css)
  • fonts/ folder - Font files (optional, if hosting locally)

See DEPLOYMENT.md for detailed deployment instructions, folder structure, and hosting configuration.


๐Ÿ“„ License #

MIT License - see LICENSE file for details.


๐Ÿค Contributing #

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


Made with โค๏ธ using Flutter & Quill.js

2
likes
130
points
282
downloads

Publisher

unverified uploader

Weekly Downloads

A rich text editor package for Flutter Web powered by Quill.js. Provides full-featured editing with tables, media embedding, custom fonts, and HTML export.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter, google_fonts, pointer_interceptor

More

Packages that depend on quill_web_editor