A powerful React rich text editor with AI generation, internationalization support, markup conversion, and customizable theming
npm install richblocksA modern, fully customizable WYSIWYG Markdown editor for React with AI-powered text generation.
- ✏️ 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
``bash`
npm install richblocks
`tsx
import { RichEditor } from 'richblocks';
function App() {
return (
console.log(data.markdown);
console.log(data.html);
}}
placeholder="Start writing..."
/>
);
}
`
---
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:
`bash`
GOOGLE_API_KEY=your_key pnpm run start:ai-proxy
Then 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 (
Type in the editor above to see the rendered output here...
function App() {
const [markdown, setMarkdown] = useState('');
const [darkMode, setDarkMode] = useState(false);
const [aiTheme, setAiTheme] = useState
return (
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.
}
}}>
,
background: darkMode ? '#333' : '#f5f5f5',
color: darkMode ? '#fff' : '#333',
}}
>
{AI_THEMES.map(theme => (
))}
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'}
,
borderRadius: '8px',
padding: '16px',
background: darkMode ? '#1e1e1e' : '#fff',
minHeight: '100px'
}}>
Configuration with Provider
`tsx
import { RichBlocksProvider, RichEditor } from 'richblocks';function App() {
return (
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' }
}
}
}}>
);
}
`RichKey - Premium Licensing
RichBlocks supports a licensing system for premium features via
RichKey:`tsx
richKey: 'RICHKEY-XXXXXXXX-XXXX' // Your premium license key
}}>
`$3
- Free: Basic editor, markdown support, basic AI features
- Premium: Advanced AI, custom themes, priority support, advanced export
- Key format:
RICHKEY-XXXXXXXX-XXXX
- Enterprise: All premium features + white-label, custom integrations, SLA support
- Key format: RICHKEY-ENT-XXXXXXXX-XXXX$3
`tsx
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):
`tsx
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' },
}
}}>
`$3
1. 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-*2. Tailwind Config Detection: Attempts to access Tailwind theme from
window.tailwind.config3. Priority Order:
- Base: Detected CSS variables
- Override: Detected Tailwind config
- Final Override: Your custom
styles configuration$3
`tsx
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
`tsx
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
$3
| 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 |$3
8 built-in themes:
default, gradient, liquid-glass, minimal, neon, sunset, ocean, forest, rose-gold`tsx
ai: {
theme: 'liquid-glass',
twoStepMode: true
}
`$3
`tsx
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
`tsx
import { RichBlocksProvider, useRichBlocks } from 'richblocks';function Preview({ content }: { content: string }) {
const { format, toHtml } = useRichBlocks();
// As React component with styles (returns JSX)
return
{format(content)};
// With dark mode background included
// return {format(content, { includeBackground: true })};
// Or as HTML string
// const html = toHtml(content);
}// Must be wrapped in RichBlocksProvider to use useRichBlocks
function App() {
return (
);
}
`Image Upload
`tsx
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;
}
}}>
`Migration from Other Editors
`tsx
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
$3
`tsx
import { LazyRender, preloadOnHover, preloadWhenIdle, createLazyComponent, SuspenseWrapper } from 'richblocks';// Render only when visible in viewport
// Preload on hover
// Preload when browser is idle
preloadWhenIdle(() => import('richblocks'));
// Create lazy component
const LazyEditor = createLazyComponent(
() => import('richblocks').then(m => ({ default: m.RichEditor }))
);
// Use with SuspenseWrapper
Loading...
$3
`tsx
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 | 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 | Link |!alt | Image |- item | Bullet list |1. item | Numbered list |``tsx
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';
``
Chrome, Firefox, Safari, Edge (latest versions)
ISC