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

@14ch/svelte-md-outliner

v0.0.15

Published

An outliner-style Markdown editor for Svelte 5. Headings form a real section tree you can fold, focus, and re-nest from the keyboard.

Readme

@14ch/svelte-md-outliner

An outline editor built on Markdown, for Svelte 5. Headings form a real nested hierarchy you can fold, focus, and rearrange with Tab. What you see is the document — there is no separate preview.

日本語版: README.ja.md

<script lang="ts">
	import { MarkdownEditor } from '@14ch/svelte-md-outliner';
	import '@14ch/svelte-md-outliner/styles/editor.css';

	let value = $state('# Getting started\n\nSome text\n');
</script>

<MarkdownEditor bind:value />

What it does

  • Build the document out of headingsTab / Shift-Tab re-nest a heading together with everything under it; Mod-↑ / Mod-↓ swap siblings
  • Fold to see the shape — per-heading folding, or collapse to a chosen level (Mod-Alt-16)
  • Focus one section — zoom into a single branch, with a breadcrumb to get back
  • Markup converts as you type# , - , 1. , [ ] , > , **bold**, `code`
  • Tables, Mermaid diagrams, task lists and front matter
  • Built-in context menu and shortcut sheet, both of which you can turn off
  • Replaceable strings and theme — English and Japanese bundled, colors and sizes as CSS custom properties

How it differs from a plain Markdown editor

Being an outline editor, the heading hierarchy is the document structure rather than decoration. That produces three behaviours worth knowing:

  • Tab on a heading moves it and everything under it one level deeper — it does not indent text.
  • Heading levels come from structural depth. # A followed by ### B saves as ## B: the parent/child relationship survives, the skipped level does not.
  • Folding, focus and the breadcrumb all operate on that hierarchy.

Install

npm install @14ch/svelte-md-outliner

svelte@^5 is a peer dependency. Import the stylesheet yourself — it already pulls in the default theme:

import '@14ch/svelte-md-outliner/styles/editor.css';

Props

<MarkdownEditor bind:value bind:viewState label="Meeting notes" maxWidth="42rem" />

| | Type | Default | | | ------------------- | ----------------------------------------- | -------- | ----------------------------------------------------------- | | value | string | '' | Markdown source (bindable) | | onChange | (markdown: string) => void | — | Not needed when you use bind:value | | viewState | ViewState | — | Which headings are folded, which section is zoomed | | onViewStateChange | (viewState: ViewState) => void | — | Not needed when you use bind:viewState | | label | string | built-in | Accessible name for the editing surface | | readonly | boolean | false | Rejects typing, commands and pastes | | autofocus | boolean | false | Focus on mount | | class | string | '' | Extra class names | | padding | string | token | Written exactly like CSS padding | | maxWidth | string | token | Line length. The clickable surface keeps the full width | | lang | 'en' \| 'ja' | 'en' | Bundled UI language | | messages | EditorMessagesOverride | — | Replace individual strings, or supply a whole language | | shortcutHelp | boolean | true | Built-in shortcut sheet (Mod-Alt-/) | | contextMenu | boolean | true | Built-in context menu (right click / Mod-/) | | uploadImage | (file: File) => Promise<string \| null> | — | Takes a pasted image, returns the URL to display |

viewState is how you are looking at the document, not its content. This module stores nothing, so persist it wherever you like — or not at all.

Images

![alt](src) is rendered as a real image. Paste an image and uploadImage receives the raw File; the URL you return is inserted as image notation at the paste position. Where the bytes go, which formats and sizes you accept, and how you sanitize them (SVG, for instance) are all yours — this module only checks whether the clipboard holds an image file, and never stores anything itself.

const uploadImage = async (file: File) => {
	const url = await myStorage.put(file); // or return null to reject the paste
	return url;
};

While it waits, a marker is shown at the paste position and the document is not touched; a failure removes the marker and leaves the document exactly as it was. Without uploadImage the paste falls through to the ordinary one. Pastes are ignored entirely while readonly.

Rendering an image fetches it. A document that names an external URL will request it from that host every time it is shown — that is what drawing image notation means.

For a language that is not bundled, start from a bundled set:

import { en, type EditorMessages } from '@14ch/svelte-md-outliner';

const fr: EditorMessages = { ...en, editorLabel: 'Éditeur' /* … */ };

Modifier symbols ( and friends) follow the platform, not the language.

Component API

<MarkdownEditor bind:this={editor} />
<button onclick={() => editor?.resetDocument('# Another document\n')}>Open another</button>

| | | | -------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | getMarkdown() | Current Markdown | | getViewState() | Current view state | | resetDocument(markdown) | Replace the document and clear undo history | | writeFrontMatterFields(fields) | Write front matter without touching undo history, cursor, selection or body. Skips while the user edits that entry | | focus() | Focus the editing surface |

Keyboard

Mod is on macOS, Ctrl elsewhere. Mod-Alt-/ opens the full list.

| | | | ------------------------- | ----------------------------------------------------- | | Tab / Shift-Tab | Move the target one level deeper / shallower | | Mod-↑ / Mod-↓ | Swap with the previous / next sibling | | Mod-Enter | Heading ⇄ list item | | Mod-Shift-D | Duplicate | | Mod-Shift-Backspace | Delete | | Mod-. | Fold / unfold | | Mod-Alt-16 | Show down to that heading level | | Mod-Alt-↑ / Mod-Alt-↓ | Previous / next heading | | Mod-A | Grow the selection (content → block → whole document) | | Mod-/ | Context menu |

Markup converts as you type (# , - , 1. , [ ] , > , ```, ---, **bold**, `code`). Backspace right after a conversion undoes it.

Markdown support

Headings, paragraphs, lists (bullet / ordered / task), block quotes, code blocks, Mermaid diagrams, tables, rules, hard breaks and front matter — plus bold, italic, strikethrough, inline code and links.

Opening and saving never changes what your markup means. Only spelling is normalized: * bullets become -, setext headings become #, table delimiters become ---.

Images, inline HTML and footnotes keep their source text exactly, but the editor does not display them as such. An image stays on screen as ![alt](img.png) rather than becoming a picture; a footnote marker is drawn as a superscript but does not link to its note, and notes are not collected at the end. Open the file in another tool and both read correctly — this editor simply does not render them.

Theming

Every color, font and heading scale is a CSS custom property, declared at zero specificity (:where()) so your own rules always win. Copy the bundled theme.css, keep the tokens you want to change, and load it after:

import '@14ch/svelte-md-outliner/styles/editor.css';
import './my-theme.css';
:where(:root) {
	--markdown-editor-accent: #2563eb;
	--markdown-editor-heading-scale-1: 1.75;
}

:where([data-theme='dark']) {
	--markdown-editor-surface: #16181d;
}

theme.css holds colors and typography. Spacing and line width live in editor.css and override the same way.

Reading Markdown without an editor

For list views, migration scripts and server-side code, a separate entry point exports pure functions — it never reaches for .svelte, so plain Node can import it.

import { readBody, readBodyBlocks, readFrontMatterField } from '@14ch/svelte-md-outliner/markdown';

const title = readFrontMatterField(markdown, 'title');
// Block offsets are relative to the body, so strip the front matter first
const blocks = readBodyBlocks(readBody(markdown));

| | | | ------------------------------------------ | -------------------------------------------------------------------- | | readFrontMatterField(markdown, key) | One front matter value | | readFrontMatterFields(markdown) | Every front matter entry | | readBody(markdown) | The body, front matter removed | | readBodyBlocks(body) | Top-level blocks: kind, offsets within the body, heading path | | readPlainText(markdown) | Readable text with inline markup stripped | | writeFrontMatterFields(markdown, fields) | Write entries; delimiters, quoting and placement are handled for you |

Values come back as strings and are never coerced. That boundary keeps this module from turning into a YAML parser.

Constraints worth knowing

  • No singleton state. Several editors on one page never interfere.
  • No side effects on import, so an SSR build is safe. The editor itself is client-side: rendering it on the server logs a warning and shows a placeholder.
  • No persistence. Document and view state go through the public API; storage is yours.
  • Small public surface. The ProseMirror schema, plugins and commands stay internal.

Development

npm run dev          # demo app (src/routes/)
npm run test         # unit, component and end-to-end
npm run check        # types
npm run lint         # formatting and static analysis

Specifications live in .kiro/specs/, conventions in .kiro/steering/ (Japanese).

License

MIT