Framework-agnostic
Web Components, not a wrapper. Works in plain HTML, React, Vue, Svelte, Astro, Rails, WordPress — anything that can load a script.
0 dependenciesMarkdown platform · writer-ir.v1
Writer is a premium, framework-agnostic Markdown editor with Web Components, a stable intermediate model and SDKs for JavaScript/TypeScript, PHP and Python. You write once; the same document comes out the same everywhere.
Sanitisation at the IR boundary in JS, PHP and Python, a first-party @writer/pdf, React/Vue/Svelte wrappers, server examples and 32 conformance fixtures. No DOCX/EPUB or CRDT — only what is actually ready.
Features
What you see is Markdown. What comes out is structured. No framework, no lock-in, no second draft for the preview.
Web Components, not a wrapper. Works in plain HTML, React, Vue, Svelte, Astro, Rails, WordPress — anything that can load a script.
0 dependenciesOne document, five modes. DOM changes are written back to Markdown — not to a second, out-of-sync draft.
one documentA stable JSON contract (writer-ir.v1) between parser, editor and renderers. Verified across 32 fixtures × 3 languages.
Five themes, variables, font faces and print CSS. The same typography from the editor to HTML and PDF — not a different “sort of like this”.
5 themesAlignment, insert/delete rows and columns, header toggle, CSV/TSV paste and keyboard navigation. Tables, not piles of pipes.
real tablesProviders, not hardcoded uploads: memory, HTTP, PHP/Python (local/FTP/SFTP/S3) or the Writer Media Protocol. The media manager also runs as a standalone component.
outside the editorBootstrap, Tailwind, Bulma, Foundation, UIkit. Detection, grid classes, buildGrid() and lint — without guessing class names.
Callouts, footnotes, task lists, TOC, math, mark, attrs, figures, code fences with line highlights. In the preset or opt-in — always structured.
FULL_PRESET @writer/pdf provides a first-party engine with embedded fonts (DejaVu); alternatively Dompdf/ReportLab or print HTML. Without a renderer or engine, asPDF() returns NO_PDF_ENGINE — it does not disguise HTML as PDF.
Architecture
Writer does not convert HTML to Markdown hoping for the best. It keeps a normalized IR and builds every output from it.
WYSIWYG, source or split. The same document, not copies.
mdast → IR with stable IDs, order and attributes. Deterministic.
Commands, plugins, tables, media. Every change goes through history.
HTML, IR, JSON, Markdown, text, print/PDF. Same structure, same content.
Render lab
Write on the left. The preview, HTML, IR and JSON are the same document — no server, no build step.
SDKs
The editor in the browser, rendering on the server. All three SDKs share the same IR and the same conformance fixtures.
import { WriterRender } from '@writer/core';
const html = WriterRender(markdown).asHTML();
const ir = WriterRender(markdown).asIR();
const json = WriterRender(markdown).asJSON({}, true);
const pdf = await WriterRender(markdown).asPDF();
use Writer\Writer;
$html = WriterRender($markdown)->asHTML();
$ir = WriterRender($markdown)->asIR();
$json = WriterRender($markdown)->asJSON(true);
$pdf = WriterRender($markdown)->asPDF(); // dompdf
from writer import WriterRender
html = WriterRender(markdown).as_html()
ir = WriterRender(markdown).as_ir()
json = WriterRender(markdown).as_json(pretty=True)
pdf = WriterRender(markdown).as_pdf()
Quickstart
The same bundle is self-hosted at /assets/writer/. One script tag, or one import — your choice.
<link rel="stylesheet" href="https://writer.gr/assets/writer/writer.css">
<script src="https://writer.gr/assets/writer/writer.min.js"></script>
<div id="editor"></div>
<script>
const editor = Writer.mount('#editor', {
value: '# Hello',
theme: 'serif-editorial'
});
editor.addEventListener('wr-change', () => {
console.log(editor.getMarkdown());
});
</script>
import { useEffect, useRef } from 'react';
import { mount } from '@writer/editor';
export function Editor({ value }: { value: string }) {
const host = useRef<HTMLDivElement>(null);
useEffect(() => {
const editor = mount(host.current!, {
value,
theme: 'serif-editorial'
});
return () => editor.destroy();
}, []);
return <div ref={host} />;
}
<?php
require 'vendor/autoload.php';
$html = WriterRender($markdown)->asHTML();
$ir = WriterRender($markdown)->asIR();
$pdf = WriterRender($markdown)->asPDF(); // DompdfEngine
from writer import WriterRender
html = WriterRender(markdown).as_html()
ir = WriterRender(markdown).as_ir()
pdf = WriterRender(markdown).as_pdf()
Live demos
The demos run the real bundle — not screenshots. Change mode, open panels, upload a file, inspect the IR.
Modes, themes, chrome, panels, IR inspector and event log — with a real editor.
Open the playgroundStandalone component, outside the editor. Folders, upload, insert — with a custom provider.
See the componentWithout the editor. Core only: HTML, IR, JSON, text, Markdown and a PDF test.
Open the render labFAQ
@writer/pdf in JS (embedded fonts, straight from the IR) or an engine/print HTML; the PHP/Python editions include Dompdf/ReportLab. If neither is available, asPDF() fails with NO_PDF_ENGINE instead of returning HTML with a .pdf extension.writer.min.js is ≈130 kB gzip (editor + core). writer-core.min.js, for render-only pages, is ≈100 kB gzip. Both are self-contained — no framework runtime.Try the editor, inspect the IR, pick an SDK — or download the bundle and start.