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

velvet-writer

v1.3.5

Published

Zero-dependency React rich text editor with glassmorphism UI, slash commands, markdown shortcuts, tables, and dark mode. ~55KB minified. Tiptap & Quill alternative.

Readme

Velvet Writer — React Rich Text Editor

CI npm version npm downloads GitHub stars bundle size license sponsor

Zero-dependency React rich text editor with a built-in glassmorphic UI, Notion-style slash commands, inline markdown shortcuts, contextual tables, and automatic light/dark mode. One component, no configuration maze.

npm install velvet-writer

| | | | :--- | :--- | | 🎮 Live demo | poojagohel.github.io/velvet-writer | | ⚡ StackBlitz starter | Open in browser | | 📦 npm | velvet-writer | | 📊 Comparison | vs Tiptap, Slate, Quill |

If Velvet Writer saves you time, star the repo — it helps other developers discover the package.


Contents


⚖️ Why Velvet Writer?

  • Zero runtime dependencies — no Slate, no ProseMirror, no helper libraries. Pure React and native DOM APIs, ~55KB minified.
  • Out-of-the-box UI — a single component installs a fully-styled, premium editor. No headless assembly required.
  • Notion-style / commands — type / to insert headings, quotes, tables, images, and more from a floating menu.
  • Inline markdown shortcuts**bold**, # heading, - list, `code` auto-format as you type.
  • Four visual variants — Premium, Flat, Classic, and Comment, each suited to a different UI context.
  • Granular toolbar control — show/hide entire tool groups, or individual tools within a group.
  • Production-ready — automated tests, CI, error boundaries, and safe local-storage auto-save.

📦 Installation

npm install velvet-writer
# or
yarn add velvet-writer

[!NOTE] Optional emoji support The emoji toolbar button depends on emoji-picker-react. Install it only if you want the picker enabled:

npm install emoji-picker-react

Without it, the emoji button and insertEmoji tool are automatically hidden — nothing breaks.


🚀 Quick Start

import { AdvanceTextEditor } from 'velvet-writer';
import 'velvet-writer/dist/index.css';

function App() {
  return (
    <AdvanceTextEditor
      accentColor="#a855f7"
      mode="system"
      onChange={(html) => console.log(html)}
    />
  );
}

That's it — import the component, import the stylesheet once anywhere in your app, and render it. No providers, no context setup.


🎨 Variants

Pass variant to switch the editor's visual chrome. All four share the same props and behavior — only the shell styling changes.

| Variant | Best for | Look | | :--- | :--- | :--- | | 'premium' (default) | Landing pages, feature-rich editors | Frosted glassmorphism, rounded corners, soft shadow | | 'flat' | Embedding inside your own card/panel | No shadow or background chrome — just the content | | 'classic' | Documents, long-form writing | Paper-like sheet with a centered writing column | | 'comment' | Chat boxes, discussion threads, replies | Compact, low-profile, action bar docked to one edge |

<AdvanceTextEditor variant="comment" toolbarPosition="bottom" maxHeight="240px" />

⚙️ Props Reference

| Prop | Type | Default | Description | | :--- | :--- | :--- | :--- | | accentColor | string | '#a855f7' | Primary color for highlights, the cursor, selection, and focus outlines. | | mode | 'light' \| 'dark' \| 'system' | 'dark' | Color scheme. 'system' follows the OS/browser preference automatically. | | variant | 'premium' \| 'flat' \| 'classic' \| 'comment' | 'premium' | Visual shell — see Variants. | | toolbarPosition | 'top' \| 'bottom' | 'top' | Docks the formatting toolbar to the top or bottom of the editor. | | placeholder | string | 'Start typing your masterpiece...' | Placeholder shown when the document is empty. | | initialValue | string | '<p><br></p>' | Starting HTML content. | | onChange | (html: string) => void | — | Called on every content change with clean, semantic HTML. | | minHeight | string \| number | — | Minimum height of the writing area. | | maxHeight | string \| number | 620px internally | Caps the writing area's height; content scrolls internally once it grows past this. | | padding | string | — | Inner padding of the writing area (e.g. '24px 32px'). | | fontSize | string | — | Base content font size (e.g. '1rem'). | | autoSaveKey | string | — | When set, persists content to localStorage under this key and restores it on mount. | | className | string | — | Extra class name(s) applied to the outer container. | | visibleTools | ToolbarToolId[] \| object | MINIMAL_TOOLBAR_TOOLS | Which toolbar tools to show — see Toolbar customization. |


🛠️ Toolbar Customization

By default (no visibleTools passed) the toolbar shows only history and basicFormatting — a minimal starting point. Pass visibleTools to control exactly what's shown.

The full tool catalog

| Group ID | Label | Children (individually toggleable) | | :--- | :--- | :--- | | history | Undo / Redo | — | | basicFormatting | Bold / Italic / Underline / Strike | — | | blockFormat | Paragraph / Heading dropdown (H1–H4) | — | | fontFamily | Font family dropdown | — | | fontSize | Font size dropdown | — | | textCase | UPPERCASE / lowercase / Title Case | — | | lists | Ordered list, bullet list, blockquote | — | | scripts | Subscript / superscript | — | | indent | Outdent / indent | — | | align | Left / center / right / justify | — | | colors | Text color / highlight color | — | | insert | Insert Media | insertLink, insertImage, insertTable, insertHr, insertEmoji | | utility | Utilities | utilityZen, utilityExport, utilityClear, utilityCodeView, utilityFullscreen, utilityHelp |

TOOLBAR_TOOLS (exported) contains this same data at runtime — including label and description for each entry — so you can build a settings UI from it directly instead of hardcoding labels.

Presets

import { MINIMAL_TOOLBAR_TOOLS, DEFAULT_TOOLBAR_TOOLS } from 'velvet-writer';
  • MINIMAL_TOOLBAR_TOOLS['history', 'basicFormatting']. Used automatically if visibleTools is omitted.
  • DEFAULT_TOOLBAR_TOOLS — every top-level group (and therefore every child tool too).

Array syntax

Pass a flat array of ToolbarToolIds. A parent ID implicitly includes all of its children.

<AdvanceTextEditor
  visibleTools={['history', 'basicFormatting', 'blockFormat', 'insert']}
  // 'insert' here means: Link + Image + Table + Divider + Emoji, all shown
/>

To show only some children of a group, list the child IDs instead of the parent:

<AdvanceTextEditor
  visibleTools={['history', 'basicFormatting', 'insertLink', 'insertImage']}
  // only Link and Image show — Table, Divider, and Emoji stay hidden
/>

Object syntax

For a friendlier config shape (handy for JSON-driven settings, e.g. from a CMS or database), visibleTools also accepts an object — or an array mixing strings and objects:

<AdvanceTextEditor
  visibleTools={{
    history: true,
    basicFormatting: true,
    blockFormat: true,
    insert: ['link', 'image', 'table'],   // only these three Insert tools
    utility: ['export', 'help'],           // only these two Utility tools
  }}
/>
// Mixed array form works too
<AdvanceTextEditor
  visibleTools={['history', 'basicFormatting', { insert: ['link', 'table'] }]}
/>

Accepted child keys: insertlink, image, table, hr (alias divider), emoji. utilityzen, export, clear (alias clearFormatting), codeView, fullscreen, help.

Building a live toggle UI

import { useState } from 'react';
import { AdvanceTextEditor, TOOLBAR_TOOLS, MINIMAL_TOOLBAR_TOOLS, type ToolbarToolId } from 'velvet-writer';
import 'velvet-writer/dist/index.css';

function App() {
  const [visibleTools, setVisibleTools] = useState<ToolbarToolId[]>(MINIMAL_TOOLBAR_TOOLS);

  return (
    <div>
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginBottom: 16 }}>
        {TOOLBAR_TOOLS.map((tool) => (
          <button
            key={tool.id}
            title={tool.description}
            onClick={() =>
              setVisibleTools((prev) =>
                prev.includes(tool.id) ? prev.filter((t) => t !== tool.id) : [...prev, tool.id]
              )
            }
          >
            {visibleTools.includes(tool.id) ? '✓' : '+'} {tool.label}
          </button>
        ))}
      </div>

      <AdvanceTextEditor accentColor="#a855f7" mode="dark" visibleTools={visibleTools} />
    </div>
  );
}

↕️ Toolbar Position

Dock the toolbar to either edge of the editor with toolbarPosition:

<AdvanceTextEditor toolbarPosition="bottom" />

This is especially useful for the comment variant, where a bottom-docked action bar matches familiar chat/reply UI patterns (Slack, Discord, GitHub comments). All four variants support both positions.


🌈 Theming

  • accentColor drives every highlight in the UI — active toolbar buttons, the text cursor, selection color, link color, and focus rings. Any valid CSS color works.
  • mode switches between a built-in light and dark palette, or 'system' to follow the OS preference reactively.
  • The editor's theme is fully independent of your app's own theme — you can run a dark-mode editor inside a light-mode page, or vice versa.

🖥️ Framework Integration

Vite / Create React App

Works out of the box. See the minimal Vite starter or run it on StackBlitz.

Next.js (App Router)

Velvet Writer touches the DOM directly, so load it client-side only:

'use client';

import dynamic from 'next/dynamic';
import 'velvet-writer/dist/index.css';

const AdvanceTextEditor = dynamic(
  () => import('velvet-writer').then((m) => m.AdvanceTextEditor),
  { ssr: false, loading: () => <p>Loading editor…</p> }
);

export default function EditorPage() {
  return (
    <main style={{ padding: 24 }}>
      <AdvanceTextEditor mode="system" accentColor="#a855f7" />
    </main>
  );
}

🔌 Advanced Usage

Blog post / CMS editor with auto-save

import { useState } from 'react';
import { AdvanceTextEditor } from 'velvet-writer';
import 'velvet-writer/dist/index.css';

function BlogEditor() {
  const [content, setContent] = useState('');

  const handlePublish = async () => {
    await fetch('/api/posts', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ htmlContent: content }),
    });
  };

  return (
    <div style={{ maxWidth: 800, margin: '0 auto', padding: 20 }}>
      <AdvanceTextEditor
        accentColor="#6366f1"
        mode="light"
        variant="flat"
        placeholder="Start draft..."
        onChange={setContent}
        autoSaveKey="my-unique-blog-key"
      />
      <button onClick={handlePublish} style={{ marginTop: 12 }}>Publish Post</button>
    </div>
  );
}

Editing existing content

import { AdvanceTextEditor } from 'velvet-writer';
import 'velvet-writer/dist/index.css';

function EditPost({ initialHtml }: { initialHtml: string }) {
  return (
    <AdvanceTextEditor
      initialValue={initialHtml}
      accentColor="#10b981"
      mode="dark"
      variant="premium"
      minHeight="500px"
      maxHeight="800px"
      padding="30px 40px"
      fontSize="1.1rem"
    />
  );
}

React Hook Form

import { Controller, useForm } from 'react-hook-form';
import { AdvanceTextEditor } from 'velvet-writer';
import 'velvet-writer/dist/index.css';

function FormWrapper() {
  const { control, handleSubmit } = useForm({
    defaultValues: { bodyContent: '<h2>Pre-filled Header</h2>' },
  });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <Controller
        name="bodyContent"
        control={control}
        render={({ field }) => (
          <AdvanceTextEditor initialValue={field.value} onChange={field.onChange} accentColor="#a855f7" />
        )}
      />
      <button type="submit">Submit</button>
    </form>
  );
}

Comment / reply box

<AdvanceTextEditor
  variant="comment"
  toolbarPosition="bottom"
  visibleTools={['basicFormatting', 'insertLink', 'utilityHelp']}
  maxHeight="200px"
  placeholder="Write a reply…"
/>

✨ Features

  • Slash commands — type / for a floating menu to insert paragraphs, headings, quotes, tables, images, and more.
  • Interactive tables — insert via a row/column grid picker or custom size input; a floating contextual toolbar lets you add/delete rows and columns from any cell.
  • Image resizing — click any inserted image to drag-resize it directly, with a numeric width/height panel.
  • Zen mode — a distraction-free focus toggle for long-form writing.
  • Code view — toggle between rich text and the raw HTML source, with a built-in formatter.
  • Export — export to Markdown (.md), print/PDF, or copy clean HTML to the clipboard.
  • Word/character count with goals — live counts in the status bar; click to set a target word count and track progress.
  • Auto-save — opt-in local-storage persistence via autoSaveKey, restored automatically on mount.
  • Fixed height + internal scroll — the writing area caps at a sensible height by default (override with maxHeight) instead of growing the page indefinitely.
  • Fullscreen — expand the editor to fill the viewport for focused editing sessions.

⌨️ Keyboard & Markdown Shortcuts

| Shortcut | Action | | :--- | :--- | | Ctrl/Cmd + B | Bold | | Ctrl/Cmd + I | Italic | | Ctrl/Cmd + U | Underline | | Ctrl/Cmd + S or Ctrl/Cmd + Shift + X | Strikethrough | | Ctrl/Cmd + Z | Undo | | Ctrl/Cmd + Y or Ctrl/Cmd + Shift + Z | Redo | | Tab / Shift + Tab | Indent/outdent a list item, or move between table cells when inside a table |

[!NOTE] Ctrl/Cmd + S triggers strikethrough inside the editor rather than a browser "Save Page" dialog — this only applies while the editor is focused.

Markdown shortcuts (type at the start of a line, then Space)

| Syntax | Result | | :--- | :--- | | # | Heading 1 | | ## | Heading 2 | | ### | Heading 3 | | > | Blockquote | | - | Bulleted list | | 1. | Numbered list |

Inline markdown (parsed as you type)

| Syntax | Result | | :--- | :--- | | **text** | Bold | | *text* | Italic | | ~~text~~ | ~~Strikethrough~~ | | `code` | Inline code |


📤 What You Can Import

import {
  AdvanceTextEditor,   // the editor component
  EDITOR_VERSION,       // current package version, as a string

  TOOLBAR_TOOLS,         // full tool catalog with labels/descriptions/children
  DEFAULT_TOOLBAR_TOOLS, // every tool group enabled
  MINIMAL_TOOLBAR_TOOLS, // ['history', 'basicFormatting']

  type AdvanceTextEditorProps,
  type EditorMode,       // 'light' | 'dark' | 'system'
  type EditorStats,      // { words: number; characters: number }
  type ToolbarToolId,
  type ToolbarToolMeta,
} from 'velvet-writer';

import 'velvet-writer/dist/index.css';

🤝 Support & Contribution

Contributions are welcome — open an issue to discuss a change before submitting a large PR.


📄 License

MIT © Pooja Gohel