npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@tooark/wysiwyg

v1.3.0

Published

Tooark WYSIWYG — rich text editor and viewer (<ark-wysiwyg-editor>, <ark-wysiwyg-viewer>) built on Tiptap, featuring sanitized JSON content

Readme

@tooark/wysiwyg

npm License

<ark-wysiwyg-editor> and <ark-wysiwyg-viewer>: a Tiptap rich-text editor and its read-only viewer as Custom Elements, with sanitized JSON content, opt-in toolbar groups and external media through an upload hook.

🌍 Languages: USA Flag English (this file) · Brazil Flag Português


Contents


📖 Overview

The @tooark/wysiwyg package provides:

  • content as Tiptap JSON (never raw HTML), sanitized on the way in by sanitizeWysiwygContent: unknown nodes and marks dropped, href/src/poster restricted to http(s), mailto, tel or a relative path starting with /, #, ?, ./ or ../ (uploads/x.png is refused), colors validated (hex, a CSS color name or rgb()/hsl());
  • toolbar built from opt-in groups (style, marks, color, align, lists, link, media, blocks, clear, history; all, none), role="toolbar" with one tab stop, labels in en/pt/es;
  • links with a URL popover (unsafe schemes refused), text color and highlight palettes, alignment, list indentation, clear formatting;
  • images and videos only through the uploadFile(file, kind) hook — your storage, the JSON keeps the URL, never base64; without the hook the media group does not render, pasted/dropped files are refused and <img>/<video> are stripped from pasted HTML; with it, pasted <img>/<video> only enter with an allowed src; pasted/dropped files that are not images or videos are refused with unsupported-type;
  • the same schema in the viewer, which renders links with target="_blank" and rel="noopener noreferrer nofollow";
  • createWysiwygEditor/createWysiwygViewer engine for use without the elements; theme follows the page (theme="auto").

🔧 Installation

pnpm add @tooark/wysiwyg   # Tiptap is installed as a dependency

⚙️ Configuration

Register the elements once; the package injects its own CSS (no Tailwind needed) and follows the page's color-scheme:

import { registerTooarkWysiwyg } from "@tooark/wysiwyg";

registerTooarkWysiwyg();

In React, Vue or Angular use the tag directly (the framework READMEs show how); a JS property the framework assigns before this call, such as content bound on the tag, is applied when the element upgrades, so the order does not matter.

Pick the toolbar groups per instance with toolbar and, to enable images and videos, assign the uploadFile property (a function that stores the file and resolves { src, alt?, title?, poster? }).


📦 Components

ark-wysiwyg-editor

  • Attributes: toolbar (groups and/or items, comma-separated; all; none; default style,marks,lists,link,blocks,clear,history), theme (auto | light | dark), placeholder, editable="false", lang (en | pt | es, or custom with locale-json), colors/highlights (JSON arrays of CSS colors: hex, a color name or rgb()/hsl(); others, such as oklch() or var(), are dropped), max-file-size (bytes, default 10 MiB), aria-label (names the editable content, Tiptap's role="textbox"), testid.
  • Toolbar items, to mix with the groups in toolbar (unknown names are ignored; image/video only with uploadFile): heading (block style select), heading-1 to heading-4, bold, italic, underline, strike, code, text-color, highlight, align-left, align-center, align-right, align-justify, bullet-list, ordered-list, outdent, indent, link, image, video, blockquote, horizontal-rule, clear-format, undo, redo.
  • Properties: content (Tiptap JSON, sanitized; setting it does not emit ark-wysiwyg-change and stays out of the undo history), uploadFile, colors, highlights (an array, or the attribute's JSON string, which is what a framework assigns), resolvedTheme, editor (the Tiptap instance); insertFile(file).
  • Events: ark-wysiwyg-change (detail = JSON), ark-wysiwyg-upload-error (detail: { reason, file, error? }, reasons no-uploader, unsupported-type, too-large, invalid-src, failed).
  • Hooks: the root carries data-ark="wysiwyg-editor", the toolbar wysiwyg-editor-toolbar and the editable content wysiwyg-editor-content; with testid, data-testid is the testid and <testid>-toolbar/<testid>-content.

ark-wysiwyg-viewer

  • Attributes: theme, testid. Properties: content, resolvedTheme.
  • Hooks: the root carries data-ark="wysiwyg-viewer" and the content wysiwyg-viewer-content, with the testid as in the editor.

Engine and helpers

  • createWysiwygEditor(element, options), createWysiwygViewer(element, options) → { editor, getJSON, setContent, isActive, setEditable, setTheme, resolvedTheme, insertFile, uploading, setLink, unsetLink, destroy }.
  • createWysiwygExtensions, Video (the video node), sanitizeWysiwygContent(json, schema), isSafeUrl, isSafeColor, resolveWysiwygLabels, resolveWysiwygTheme(theme, element), ARK_WYSIWYG_TOOLBAR_GROUPS, ARK_WYSIWYG_DEFAULT_TOOLBAR, EMPTY_DOC, DEFAULT_MAX_FILE_SIZE, HEADING_LEVELS.
  • Styles: ensureWysiwygStyles() injects wysiwygCss into <head> once (a <style> whose id is WYSIWYG_STYLE_ID); the elements call it on connect, the engine does not.
  • Types: ArkWysiwygInstance, ArkWysiwygContent, ArkWysiwygEditorOptions, ArkWysiwygViewerOptions, ArkWysiwygTheme, ArkWysiwygLang, ArkWysiwygLabels, ArkWysiwygToolbarGroup, ArkWysiwygToolbarItem, ArkWysiwygUploader, ArkWysiwygUploadKind, ArkWysiwygUploadResult, ArkWysiwygUploadError, ArkWysiwygUploadErrorReason, JSONContent (re-exported).

📝 Usage examples

Editor with media upload and a viewer

const editor = document.querySelector("ark-wysiwyg-editor")!;
editor.setAttribute("toolbar", "style,marks,color,lists,link,media,history");
editor.uploadFile = async (file, kind) => {
  const url = await api.upload(file); // your storage; kind is "image" | "video"
  return { src: url, alt: file.name };
};
editor.content = savedJson; // sanitized on the way in
editor.addEventListener("ark-wysiwyg-change", (event) => save((event as CustomEvent).detail));
editor.addEventListener("ark-wysiwyg-upload-error", (event) => {
  toast.error(`Upload refused: ${(event as CustomEvent).detail.reason}`);
});

document.querySelector("ark-wysiwyg-viewer")!.content = savedJson;

Sanitizing content outside the editor too

import { createWysiwygViewer, sanitizeWysiwygContent } from "@tooark/wysiwyg";

// The viewer already sanitizes on setContent; the helper is exported for your own validation layer.
const viewer = createWysiwygViewer(document.createElement("div"));
const safe = sanitizeWysiwygContent(untrustedJson, viewer.editor.schema); // javascript:, data:, unknown nodes dropped
viewer.destroy();

📋 Dependencies

Installed automatically unless marked as peer; peer dependencies are yours to install (the ranges are what the package declares).

| Package | Version | Description | | ---------------------------------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------ | | @tiptap/core | ^3.31.3 | Tiptap editor core (ProseMirror) | | @tiptap/extension-highlight | ^3.31.3 | Multicolor highlight mark (<mark>) | | @tiptap/extension-image | ^3.31.3 | Image node (base64 disabled) | | @tiptap/extension-placeholder | ^3.31.3 | Placeholder for the empty document | | @tiptap/extension-text-align | ^3.31.3 | Text alignment on paragraphs and headings | | @tiptap/extension-text-style | ^3.31.3 | Text style mark with color | | @tiptap/pm | ^3.31.3 | ProseMirror packages used by Tiptap | | @tiptap/starter-kit | ^3.31.3 | Base nodes and marks (paragraph, heading, lists, bold, link, underline…) | | @tooark/tokens | ^1.3.0 | Design tokens (colors, sizes, motion) and primitive types | | tslib | ^2.8.1 | TypeScript runtime helpers |


🪪 Contributing

Contributions are welcome! Open issues and pull requests in the Tooark/web-components repository; CONTRIBUTING.md covers the workflow, the commit convention and the checklist. @tooark/wysiwyg is released in lockstep with every other @tooark/* package.


📄 License

This project is licensed under the Apache License 2.0. See the LICENSE file for details.