The rich text editor
people actually enjoy.Notion-feel editing. Smart shortcuts. Free AI.
No sign-up. No credit card.
Drops into any stack
Most editors make you choose.
Framework lock-in, plugin hunts, and AI behind a paywall. Penna skips all three.
Framework lock-in
Most editors are React-only or ship a jQuery-era API. Penna is a plain element with thin React, Vue and Angular wrappers, so one editor serves every app you own.
The plugin hunt
Tables, images, embeds, find & replace, task lists: usually five plugins from five authors. Here they are one import, tested together, styled together.
AI behind a paywall
Built-in AI elsewhere means one vendor and a bill. Penna ships free Groq, Gemini and OpenRouter presets, plus Ollama for fully local.
Everything included. Nothing to hunt for.
Every block, menu and shortcut ships in the box and works the same in every framework.
Slash commands
Type / for 16 blocks: headings, lists, tables, media, callouts and AI. Fuzzy search, keyboard-first.
AI that's free
Continue, improve, fix, translate, summarize. Ships with free Groq, Gemini and OpenRouter presets. Any OpenAI-compatible key works.
Bubble menu
Select text and the formatting you need appears right there. Bold, links, highlight, AI, and Turn into any block type.
Shortcuts
Type /budget or /impact, or hit a hotkey, to drop in ready-made blocks. Select anything and press + to save your own.
Proofreader
Typos, wordy phrases and hard-to-read sentences get underlined as you write. One click to fix, and a live score. Nothing changes unless you accept.
Version history
Quiet autosaves, named versions, a word-by-word diff against now, and one-click restore. Pin comments to the exact words, too.
Mentions & merge fields
Type @ to mention someone from your API, or {{first_name}} for per-reader fields you can preview with real values.
Drag handles
Grab any block and move it. Click + to insert below. Notion-smooth, zero config.
Images & video
Drop, paste or upload. Resize by dragging, align, alt text. Inline by default, or bring your own storage.
Embeds
Paste a YouTube, Vimeo, Loom, Figma or CodePen link and it just becomes the thing.
Real tables
Add rows and columns, merge cells, header rows, drag to resize columns. Not an afterthought.
Markdown in & out
Type **bold**, # heading, - [ ] task. Import and export lossless Markdown, HTML or JSON.
Find & replace
With match counts, case toggle and replace-all. Ctrl+F like you'd expect.
Task lists
Real checkboxes that toggle, serialize to - [x] Markdown and render outside the editor.
Light, dark, sepia & RTL
Follows your app's theme or the system. Right-to-left for Urdu and Arabic, a focus mode that dims all but the current sentence, and every color is a CSS variable.
Render anywhere
renderToHTML(json) needs no DOM. Show saved content on the server, in an email, in a PDF pipeline.
Two minutes to ship.
Pick your framework. Same editor, same features, same CSS file.
<!-- one script tag, no build step --> <link rel="stylesheet" href="https://unpkg.com/@abhinavakhil/penna-core/dist/penna.css"> <script type="module" src="https://unpkg.com/@abhinavakhil/penna-element"></script> <penna-editor placeholder="Write something…"></penna-editor>
import { Penna } from '@abhinavakhil/penna-react' import '@abhinavakhil/penna-core/penna.css' export default function Page() { const [html, setHtml] = useState('') return <Penna value={html} onChange={({ html }) => setHtml(html)} /> }
<script setup> import { Penna } from '@abhinavakhil/penna-vue' import '@abhinavakhil/penna-core/penna.css' const html = ref('') </script> <template> <Penna v-model="html" placeholder="Write something…" /> </template>
import { PennaEditorComponent } from '@abhinavakhil/penna-angular' // angular.json styles: "node_modules/@abhinavakhil/penna-core/dist/penna.css" @Component({ imports: [PennaEditorComponent, FormsModule], template: `<ngx-penna [(ngModel)]="html" placeholder="Write something…" />`, }) export class Editor { html = '' }
<script> import '@abhinavakhil/penna-element' import '@abhinavakhil/penna-core/penna.css' let html = '' </script> <penna-editor value={html} on:change={(e) => (html = e.detail.html)} />
import { createEditor } from '@abhinavakhil/penna-core' import '@abhinavakhil/penna-core/penna.css' const editor = createEditor('#editor', { placeholder: 'Write something…', onUpdate: (e) => save(e.getJSON()), })
Free AI out of the box.
Paid models if you want them.
Free providers built in
Presets for every free tier, plus Ollama for fully local. Any OpenAI-compatible endpoint is one line.
Every writing action
Continue, improve, fix grammar, shorten, expand, simplify, summarize, translate, change tone, or type any instruction.
Preview before insert
Responses stream into a preview, and rewrites show a word-by-word diff. Accept, retry, or discard. Nothing touches the document until you say so.
Your key stays yours
Requests go straight from the browser to the provider you chose. Nothing is sent until you trigger an action.
import { createEditor } from '@abhinavakhil/penna-core' import { ai } from '@abhinavakhil/penna-ai' createEditor('#editor', { extensions: [ ai({ provider: 'groq', // free at console.groq.com apiKey: GROQ_KEY, }), ], }) // or any OpenAI-compatible API: ai({ provider: 'openai-compatible', baseUrl: 'https://your-proxy.dev/v1', model: 'gpt-4o-mini' })
What the others make you work around.
Side by side with the editors you'd otherwise reach for.
| Penna | Novel | Quill | Froala | |
|---|---|---|---|---|
| Works in any framework | incl. Angular forms | React only | Vanilla + wrappers | (jQuery-era API) |
| Slash commands & bubble menu | Partial | |||
| Drag handles | ||||
| Tables with merge / resize | Plugin | |||
| Images, video, embeds | Images | Images | ||
| Markdown import & export | lossless | Input only | (Delta) | |
| Render content without the editor | no DOM needed | |||
| Built-in AI | free providers | OpenAI only | ||
| Find & replace, word count, autosave | Plugins | |||
| Dark mode & CSS variables | light, dark, sepia | Tailwind only | Themes | |
| License | MIT, no telemetry | Apache-2.0 | BSD | Paid, license key |
Comparison reflects default, out-of-the-box behaviour of each project at time of writing.
// server, edge, worker — no DOM required import { renderToHTML } from '@abhinavakhil/penna-core' const html = renderToHTML(post.content) // same markup the editor produces, // styled by the same penna.css return `<article class="penna-content">${html}</article>`
Edit here. Display there. Same pixels.
Store JSON. Render it in the editor, in read-only mode, on the server, or in an email, and it looks identical, because it's literally the same serializer. No display component to keep in sync.
Rendering docsEverything you need. Nothing you don't.
One package, one CSS file, every framework. Numbers from the codebase, not a slide deck.
React, Vue, Angular, Svelte, Solid and plain HTML, from one package.
Headings, lists, tables, media, callouts, code, embeds and AI.
Groq, Gemini, OpenRouter, Together and Ollama, no card required.
MIT licensed. No tracking, no phone-home, no license key.