Open-source project
BearToCode/carta avatar
BearToCode/carta

Carta, a Svelte Markdown editor that stays out of the way

A lightweight, fast and extensible Svelte Markdown editor and viewer.

782 stars32 forksTypeScriptMIT

At a glance

What is it?
BearToCode/carta gives Svelte teams an embeddable Markdown editor and viewer built on unified, remark and rehype, with plugins for maths, code blocks and slash commands. It is deliberately thin, which means you supply the sanitiser and the font yourself.
Who is it for?
Adopt Carta if your team ships a Svelte app that needs an embedded Markdown editing surface and can accept a plugin API built on remark. Skip it if you want a batteries-included rich text editor or a project with no appetite for sanitiser and stylesheet wiring.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 18 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The gap Carta fills inside Svelte applications

Plenty of applications need a place for a person to write Markdown and for other people to read the rendered result, but few want to build that surface from scratch. Carta is a Svelte component set for exactly that job: an editor that is described in its own README as "just a textarea with syntax highlighting, shortcuts and more", plus a viewer for the rendered output.

The target audience is narrow on purpose. This is not a desktop Markdown application and not a note-taking product. It is a library for product teams who already write Svelte, want Markdown input inside their own interface, and would rather compose behaviour from packages than adopt a monolithic editor. The feature list in the README covers Markdown syntax highlighting through Shiki, an extensible toolbar, extensible keyboard shortcuts, scroll sync between editor and preview, server-side rendering compatibility and an accessibility-friendly stance. Maths, slash commands, emoji, TikZ, attachments, heading anchors and embedded components all live outside the core as separate plugin packages.

That split is the central design decision. The core package stays small, and the pieces a given product does not need are simply not installed.

How the unified pipeline produces the rendered view

Carta is powered by unified, remark and rehype, and the README makes a specific promise built on that stack: it supports the more than 150 plugins listed in remark's own plugin documentation. In practice that means the Markdown text is parsed by remark into a syntax tree, transformed by plugins, and rendered through rehype into HTML, rather than being rendered by a bespoke parser written for the editor.

The practical consequence is that the plugin ecosystem is not Carta's own. A team that already knows remark plugins can reuse that knowledge, and a team that needs a specific transformation can write a standard remark plugin instead of adopting a Carta-specific extension point. The trade-off is that the editor surface itself offers less control over the document model than editors built on a rich text schema would.

The core package also provides the document-facing pieces: the Carta instance holds configuration, and the MarkdownEditor component binds a value to it. The repository is a pnpm workspace with the main package under packages/carta-md and a plugin package per feature, published to npm under the carta-md and @cartamd scopes.

Installing the core package and mounting the first editor

The installation path is short. The core package goes in with npm:

bash
npm i carta-md

Individual features are separate packages, installed the same way:

bash
npm i @cartamd/plugin-name

The README's basic configuration shows the shape of a first use. A Carta instance is created, the default theme stylesheet is imported, and the MarkdownEditor component is bound to a piece of state:

svelte
<script lang="ts">
	import { Carta, MarkdownEditor } from 'carta-md';
	import 'carta-md/default.css';

	const carta = new Carta({
	});

	let value = $state('');
</script>

<MarkdownEditor bind:value {carta} />

Two details in that snippet matter more than they look. The default stylesheet is imported from the package rather than copied into the project, so theme changes follow the package version. And the state syntax shown is the Svelte 5 rune form, which means Carta's own example assumes a current Svelte version rather than the older store-based style. A project on older Svelte conventions will have to adapt the binding rather than paste the example unchanged.

The two things you must supply: sanitiser and monospace font

The README carries a warning box that any reviewer should read before shipping this component. Carta does not deal with sanitisation. The application must pass a sanitiser into the options, and the README points at isomorphic-dompurify as a suggested choice and sanitize-html as another common one, with an example in the documentation.

This is not a warning about a rough edge in a preview mode. The editor renders Markdown to HTML and, if the plugin set allows raw HTML, untrusted content can reach the page. Leaving the sanitiser option unset means you have accepted responsibility for every string a user types. Treat the sanitiser as a required field in your own wrapper component, not as an optional hardening step.

The second requirement is cosmetic but breaks rendering when missed. The example stylesheet sets a monospace font on the global .carta-font-code class, and the comment in the README marks it as required for the editor to work correctly. Projects that override typography, use a CSS reset, or scope their global styles need to make sure that class keeps a monospace family, otherwise code blocks in the editor render with the wrong metrics.

Plugin packages as the extension surface

The feature set is delivered as published plugin packages rather than flags on the core: plugin-math for KaTeX, plugin-code for syntax highlighted code blocks, plugin-emoji with a search index, plugin-slash for slash commands, plugin-tikz for TikZ figures, plugin-attachment for file attachments, plugin-anchor for heading anchors and plugin-component for embedding components in the document.

There is also a community tier that is not published by the maintainer. The README lists carta-plugin-video for online video, carta-plugin-imsize for rendering images at specific sizes, carta-plugin-subscript for sub and superscripts, and carta-plugin-ins-del for ins and del tags. These live in separate repositories under the maisonsmd organisation, so their maintenance status is not governed by Carta's own release cycle.

A useful detail for planning upgrades: the package names carry a major version of their own. The recent release history shows the core at carta-md-v4.11.5 and the component plugin at @cartamd/plugin-component-v1.1.2, so plugin majors do not track core majors. An upgrade that moves the core from v4 to v5 is not automatically a plugin upgrade, and the reverse is equally true.

Release cadence, licence and the alternative to weigh

The repository is not archived, and the last push was on 2026-10-07, with carta-md-v4.11.5 published the same day and v4.11.4 two days earlier. That pattern of small patch releases suggests a project shipping continuously rather than in seasonal batches. The licence is MIT, and the published artifacts carry the same terms, so embedding Carta in a commercial product does not impose a copyleft obligation. The project is developed as a pnpm workspace with semantic-release, husky, eslint and prettier in place.

The alternative worth comparing against is a rich text editor built on a document schema rather than a Markdown pipeline. Editors in that family, TipTap on ProseMirror for example, store a tree of typed nodes and characters instead of Markdown text, which gives collaborative editing and structured content at the cost of a custom serialisation story. Carta keeps the storage model as plain Markdown, which is trivial to save, diff and version, but pushes structured content into plugins and custom components. If your product needs to read the document as Markdown later, Carta's choice is the one that saves you work; if your product needs node-level editing semantics, the tree-based route is the better fit.

Carta is also not the place to look if you want a finished writing environment. There is no file tree, no version history and no publishing pipeline here. The scope stops at an editable Markdown surface inside your own app.

Editorial conclusion

Adopt Carta if your team ships a Svelte app that needs an embedded Markdown editing surface and can accept a plugin API built on remark. Skip it if you want a batteries-included rich text editor or a project with no appetite for sanitiser and stylesheet wiring. Before committing, run npm i carta-md in a scratch app, mount the MarkdownEditor with your own sanitiser, and confirm the monospace font class renders your code blocks correctly.

Frequently asked questions

How do I install Carta and add a plugin to a Svelte project?

Install the core with npm i carta-md, then add the feature package you need, for example npm i @cartamd/plugin-name, and pass it to the Carta instance. The core package is published as carta-md and features are published under the @cartamd scope.

Does Carta sanitise the HTML it renders?

No. The README states that sanitisation is not dealt with by Carta and that you must provide a sanitizer in the options. It points at isomorphic-dompurify as a suggested choice and sanitize-html as another option.

Is Carta compatible with server-side rendering?

Yes. The feature list in the README marks Carta as SSR compatible, and the installation section describes reducing the server-side bundle by excluding Shiki from it. The README does not document a rollback path for that configuration.

Which Svelte version does the Carta example target?

The example in the README uses rune-based state such as $state in a script block with lang="ts", which corresponds to Svelte 5 style code. A project using older Svelte conventions has to adapt the value binding itself.

Why do code blocks look wrong after I add Carta?

The README example sets a monospace font on the global .carta-font-code class and marks that as required for the editor to work correctly. If your project overrides global typography or uses a CSS reset, confirm that class still resolves to a monospace family.

Official sources

  1. BearToCode/carta on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/beartocode-carta.svg)](https://hysenlabs.com/projects/beartocode-carta)

Was this useful?