richblocks
v2.2.8
Published
A powerful React rich text editor with AI generation, internationalization support, markup conversion, and customizable theming
Downloads
51
Maintainers
Readme
RichBlocks
A modern, fully customizable WYSIWYG Markdown editor for React with AI-powered text generation.
Features
- ✏️ WYSIWYG Editing — Write visually, export Markdown or HTML
- 🤖 AI Text Generation — Built-in support for OpenAI, Anthropic, Groq, Google Gemini, and Mistral
- 💻 Code Blocks — Syntax highlighting with copy button
- 🔗 Smart Links — Auto-fetch previews with title and favicon
- 🖼️ Image Upload — Custom upload handlers
- 🌐 i18n — English, Portuguese, and French
- 🎨 Theming — 8 AI themes + dark mode support
- 📱 Responsive — Mobile-first design
- ⚡ Performance — Lazy loading and optimization utilities
- 🔄 Migration — Import from Quill, WordPress Gutenberg, and legacy formats
- 📦 TypeScript — Fully typed API
- 🎨 External Styles — Auto-detect and integrate Tailwind CSS and custom styles
- 🔑 RichKey — Premium licensing system for advanced features
Installation
npm install richblocksQuick Start
import { RichEditor } from 'richblocks';
function App() {
return (
<RichEditor
onChange={(data: { markdown: string; html: string }) => {
console.log(data.markdown);
console.log(data.html);
}}
placeholder="Start writing..."
/>
);
}Demo: AI Proxy (recommended)
For security and reliability, do not embed cloud AI API keys in client-side demo code. Instead run a local proxy that holds your API key in an environment variable and forwards requests to the AI provider. A simple demo proxy is provided at scripts/ai-proxy.js.
Start it with:
GOOGLE_API_KEY=your_key pnpm run start:ai-proxyThen enable "Use local AI proxy" in the demo UI (top-right) and set the proxy URL (default http://localhost:3001/api/ai/stream). Remember to rotate any keys that were exposed in source code.
'sunset',
'ocean',
'midnight',
'rose',
'cyber',
'minimal',
];
// Component that renders the markdown content const ContentPreview: React.FC<{ markdown: string; darkMode: boolean }> = ({ markdown, darkMode }) => { const { format } = useRichBlocks();
if (!markdown) {
return (
<p style={{ color: darkMode ? '#888' : '#999', fontStyle: 'italic' }}>
Type in the editor above to see the rendered output here...
</p>
);
}
return <>{format(markdown)}</>;};
function App() { const [markdown, setMarkdown] = useState(''); const [darkMode, setDarkMode] = useState(false); const [aiTheme, setAiTheme] = useState('default');
return (
<RichBlocksProvider config={{
themeColor: '#3b82f6',
darkMode: darkMode,
language: 'en',
// Optional: Enable auto-detection of project styles
externalStyles: {
autoDetect: true, // Detects Tailwind and CSS variables
},
// Optional: Premium license key
// richKey: 'RICHKEY-XXXXXXXX-XXXX',
codeBlock: {
showCopyButton: true,
showLanguageLabel: true,
theme: 'dark'
},
editor: {
minHeight: '200px',
maxHeight: '400px'
},
toolbar: {
items: [
'bold', 'italic', 'underline', 'strikethrough',
'h1', 'h2', 'h3', 'paragraph',
'ul', 'ol', 'quote', 'code',
'link', 'image',
'alignLeft', 'alignCenter', 'alignRight',
'undo', 'redo',
'ai' // AI button
]
},
// AI Configuration
ai: {
enabled: true,
theme: aiTheme,
twoStepMode: false,
// defaultModel: (optional) leave unset to use provider-discovered models (recommended) or set to a model id
temperature: 0.7,
maxTokens: 2048,
// Providers: you can pass provider settings either as an object
// (recommended when you need baseUrl or other options) or as a
// shorthand string containing the API key for convenience:
//
// providers: {
// google: { apiKey: 'your-api-key' },
// }
//
// OR (short form):
// providers: {
// google: 'your-api-key',
// openai: 'sk-...'
// }
providers: {
google: 'your-api-key',
}
Note: the demo does not include a UI to enter provider API keys. Developers should pass keys via the `providers` property on `RichBlocksProvider` (e.g., `providers: { google: 'APIKEY', openai: 'sk-...' }`). Do not embed production keys in public clients.
Model discovery: when provider keys are present, RichBlocks will attempt to fetch available models from the configured providers and populate the model selector dynamically. Avoid hardcoding provider-specific model ids in demo code.
}
}}>
<div style={{
maxWidth: '900px',
margin: '0 auto',
padding: '20px',
fontFamily: 'system-ui, -apple-system, sans-serif',
background: darkMode ? '#121212' : '#fff',
minHeight: '100vh',
color: darkMode ? '#e0e0e0' : '#333',
transition: 'all 0.3s ease',
}}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<h1>RichBlocks Demo</h1>
<div style={{ display: 'flex', gap: '12px' }}>
<select
value={aiTheme}
onChange={(e) => setAiTheme(e.target.value as AIThemeTemplate)}
style={{
padding: '8px 12px',
borderRadius: '8px',
border: `1px solid ${darkMode ? '#444' : '#ddd'}`,
background: darkMode ? '#333' : '#f5f5f5',
color: darkMode ? '#fff' : '#333',
}}
>
{AI_THEMES.map(theme => (
<option key={theme} value={theme}>AI: {theme}</option>
))}
</select>
<button
onClick={() => setDarkMode(!darkMode)}
style={{
padding: '8px 16px',
borderRadius: '8px',
border: 'none',
background: darkMode ? '#333' : '#f0f0f0',
color: darkMode ? '#fff' : '#333',
cursor: 'pointer',
}}
>
{darkMode ? '☀️ Light Mode' : '🌙 Dark Mode'}
</button>
</div>
</div>
<h2>Editor</h2>
<RichEditor
darkMode={darkMode}
onChange={(data) => {
console.log('Content changed:', data);
setMarkdown(data.markdown);
}}
placeholder="Start writing... Click AI button to generate content"
/>
<h2 style={{ marginTop: '32px' }}>Rendered Output</h2>
<div style={{
border: `1px solid ${darkMode ? '#333' : '#e0e0e0'}`,
borderRadius: '8px',
padding: '16px',
background: darkMode ? '#1e1e1e' : '#fff',
minHeight: '100px'
}}>
<ContentPreview markdown={markdown} darkMode={darkMode} />
</div>
<h2 style={{ marginTop: '32px' }}>Markdown Output</h2>
<pre style={{
background: '#1e1e1e',
color: '#d4d4d4',
padding: '16px',
borderRadius: '8px',
whiteSpace: 'pre-wrap',
minHeight: '100px'
}}>
{markdown || 'Type in the editor to see the Markdown output here...'}
</pre>
</div>
</RichBlocksProvider>
);}
## Configuration with Provider
```tsx
import { RichBlocksProvider, RichEditor } from 'richblocks';
function App() {
return (
<RichBlocksProvider config={{
themeColor: '#3b82f6',
darkMode: false,
language: 'en',
// Optional: RichKey for premium features
richKey: 'RICHKEY-XXXXXXXX-XXXX',
// Optional: Auto-detect external styles
externalStyles: {
autoDetect: true, // Automatically detect Tailwind CSS and CSS variables
},
toolbar: {
show: true,
items: ['bold', 'italic', 'h1', 'h2', 'link', 'image', 'code', 'ai']
},
editor: {
minHeight: '200px',
maxHeight: '600px'
},
codeBlock: {
showCopyButton: true,
showLanguageLabel: true,
theme: 'dark'
},
ai: {
enabled: true,
theme: 'gradient',
twoStepMode: false,
defaultModel: 'gemini-2.0-flash',
temperature: 0.7,
maxTokens: 2048,
providers: {
google: { apiKey: 'your-api-key' }
}
}
}}>
<RichEditor />
</RichBlocksProvider>
);
}RichKey - Premium Licensing
RichBlocks supports a licensing system for premium features via RichKey:
<RichBlocksProvider config={{
richKey: 'RICHKEY-XXXXXXXX-XXXX' // Your premium license key
}}>
<RichEditor />
</RichBlocksProvider>License Tiers
- Free: Basic editor, markdown support, basic AI features
- Premium: Advanced AI, custom themes, priority support, advanced export
- Key format:
RICHKEY-XXXXXXXX-XXXX
- Key format:
- Enterprise: All premium features + white-label, custom integrations, SLA support
- Key format:
RICHKEY-ENT-XXXXXXXX-XXXX
- Key format:
Using RichKey in Your Code
import { useRichKey, hasFeature } from 'richblocks';
function MyComponent() {
const richKeyValidation = useRichKey();
console.log('License Tier:', richKeyValidation.tier);
console.log('Is Valid:', richKeyValidation.isValid);
console.log('Features:', richKeyValidation.features);
// Check for specific features
if (hasFeature(richKeyValidation.tier, 'advanced-ai')) {
// Enable advanced AI features
}
}External Styles Integration
RichBlocks can automatically detect and integrate styles from your project's Tailwind CSS configuration and CSS custom properties (CSS variables):
<RichBlocksProvider config={{
externalStyles: {
autoDetect: true, // Enable automatic detection
},
// Your custom styles will override detected styles
styles: {
container: { padding: '20px' }, // This overrides any detected container styles
editor: { fontSize: '16px' },
}
}}>
<RichEditor />
</RichBlocksProvider>How It Works
CSS Variables Detection: Automatically detects CSS custom properties from
:root- Colors:
--color-primary,--bg,--text-* - Spacing:
--spacing-*,--gap-*,--margin-* - Typography:
--font-size-*,--text-* - Border Radius:
--radius,--rounded-*
- Colors:
Tailwind Config Detection: Attempts to access Tailwind theme from
window.tailwind.configPriority Order:
- Base: Detected CSS variables
- Override: Detected Tailwind config
- Final Override: Your custom
stylesconfiguration
Manual Style Integration
import { integrateExternalStyles, detectCSSVariables, detectTailwindConfig } from 'richblocks';
// Manually detect styles
const cssVars = detectCSSVariables();
const tailwindConfig = detectTailwindConfig();
// Merge with custom styles
const mergedStyles = integrateExternalStyles(
{ autoDetect: true },
{ padding: '16px', borderRadius: '8px' }
);Toolbar Items
toolbar: {
items: [
'bold', 'italic', 'underline', 'strikethrough',
'h1', 'h2', 'h3', 'paragraph',
'ul', 'ol',
'quote', 'code', 'link', 'image',
'alignLeft', 'alignCenter', 'alignRight',
'undo', 'redo',
'ai'
]
}AI Text Generation
Supported Providers
| Provider | Models |
|----------|--------|
| OpenAI | gpt-4o, gpt-4o-mini, gpt-4-turbo |
| Anthropic | claude-3-5-sonnet, claude-3-opus |
| Groq | llama-3.1-70b-versatile, mixtral-8x7b |
| Google | gemini-2.0-flash, gemini-1.5-pro |
| Mistral | mistral-large-latest |
AI Themes
8 built-in themes: default, gradient, liquid-glass, minimal, neon, sunset, ocean, forest, rose-gold
ai: {
theme: 'liquid-glass',
twoStepMode: true
}Programmatic AI Service
import { createAIService } from 'richblocks';
const ai = createAIService({
providers: {
openai: { apiKey: 'sk-...' }
}
});
await ai.generateStream('Write a poem', {
model: 'gpt-4o-mini',
onChunk: (chunk: { content: string }) => console.log(chunk.content),
onComplete: (fullText: string) => console.log('Done:', fullText)
});Rendering Markdown
import { RichBlocksProvider, useRichBlocks } from 'richblocks';
function Preview({ content }: { content: string }) {
const { format, toHtml } = useRichBlocks();
// As React component with styles (returns JSX)
return <div>{format(content)}</div>;
// With dark mode background included
// return <div>{format(content, { includeBackground: true })}</div>;
// Or as HTML string
// const html = toHtml(content);
}
// Must be wrapped in RichBlocksProvider to use useRichBlocks
function App() {
return (
<RichBlocksProvider config={{ darkMode: true }}>
<Preview content="# Hello **World**" />
</RichBlocksProvider>
);
}Image Upload
<RichBlocksProvider config={{
onImageUpload: async (file: File) => {
const formData = new FormData();
formData.append('file', file);
const res = await fetch('/api/upload', { method: 'POST', body: formData });
const { url } = await res.json();
return url;
}
}}>
<RichEditor />
</RichBlocksProvider>Migration from Other Editors
import { normalizeContent, normalizeMarkdown, normalizeHtml, deltaToHtml, gutenbergToHtml } from 'richblocks';
// Auto-detect format and normalize
const markdown = normalizeContent(legacyContent, { format: 'auto' });
// Normalize legacy markdown (GFM, Obsidian, WordPress shortcodes)
const cleanMarkdown = normalizeMarkdown(legacyMarkdown);
// Clean HTML from TinyMCE, CKEditor, Quill, Draft.js
const cleanHtml = normalizeHtml(dirtyHtml);
// Convert Quill Delta to HTML
const html = deltaToHtml(quillDelta);
// Convert WordPress Gutenberg blocks to HTML
const html = gutenbergToHtml(gutenbergBlocks);Performance Optimization
Lazy Loading
import { LazyRender, preloadOnHover, preloadWhenIdle, createLazyComponent, SuspenseWrapper } from 'richblocks';
// Render only when visible in viewport
<LazyRender rootMargin="100px" threshold={0.1}>
<RichEditor />
</LazyRender>
// Preload on hover
<button onMouseEnter={preloadOnHover(() => import('richblocks'))}>
Open Editor
</button>
// Preload when browser is idle
preloadWhenIdle(() => import('richblocks'));
// Create lazy component
const LazyEditor = createLazyComponent(
() => import('richblocks').then(m => ({ default: m.RichEditor }))
);
// Use with SuspenseWrapper
<SuspenseWrapper fallback={<div>Loading...</div>}>
<LazyEditor />
</SuspenseWrapper>Utilities
import { debounce, throttle, rafThrottle } from 'richblocks';
// Debounce - wait until pause in calls
const debouncedSave = debounce((content: string) => save(content), 500);
// Throttle - max calls per interval
const throttledUpdate = throttle(() => update(), 100);
// RAF Throttle - sync with animation frames
const rafUpdate = rafThrottle(() => updateAnimation());Editor Props
| Prop | Type | Description |
|------|------|-------------|
| onChange | (data: { markdown: string; html: string }) => void | Content change callback |
| initialValue | string | Initial Markdown content |
| placeholder | string | Placeholder text |
| maxLength | number \| null | Character limit |
| language | 'en' \| 'pt' \| 'fr' | UI language |
| themeColor | string | Theme color |
| darkMode | boolean | Enable dark mode |
| onImageUpload | (file: File) => Promise<string> | Image upload handler |
| className | string | Container class |
| toolbar | object | Toolbar configuration |
| editor | object | Editor configuration |
| codeBlock | object | Code block configuration |
| styles | object | Custom styles |
Supported Markdown
| Syntax | Output |
|--------|--------|
| # H1 | Heading 1 |
| ## H2 | Heading 2 |
| ### H3 | Heading 3 |
| **bold** | bold |
| *italic* | italic |
| __underline__ | underline |
| ~~strike~~ | ~~strike~~ |
| > quote | Blockquote |
| `code` | Inline code |
| ```js | Code block |
| [text](url) | Link |
|  | Image |
| - item | Bullet list |
| 1. item | Numbered list |
TypeScript
import type {
RichBlocksConfig,
RichEditorProps,
ToolbarItem,
AIConfigWithTheme,
AIConfig,
AIModel,
AIThemeTemplate,
LegacyFormat,
LazyComponentOptions,
// External styles
ExternalStylesConfig,
DetectedStyles,
// RichKey
RichKeyValidationResult,
} from 'richblocks';
import type { Language } from 'richblocks';
// Using RichKey utilities
import {
validateRichKey,
hasFeature,
getTierName,
useRichKey
} from 'richblocks';Browser Support
Chrome, Firefox, Safari, Edge (latest versions)
License
ISC
