quill_web_editor 1.1.0
quill_web_editor: ^1.1.0 copied to clipboard
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.
๐ฎ Try It Out #
Experience the editor live in your browser:
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>',
)
Using with Controller (Recommended) #
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
controlleris provided, an internal controller is created automatically (likeTextField).
| 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();
// 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 HTMLquill_viewer.html- Viewer HTMLjs/folder - Custom JavaScript overridesstyles/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