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

monowind

v0.3.0

Published

Text-based user interfaces (TUI) on the web, from ordinary HTML and Tailwind utility classes

Downloads

3,820

Readme

monowind

Build text-based user interfaces (TUIs) on the web from ordinary HTML and Tailwind utility classes. A Web Component lays your HTML out on a strict character grid — box-drawing borders, integer-cell geometry, monospace everything — while native links, buttons, inputs, focus, forms, and accessibility semantics stay fully intact.

Early development. APIs and behavior will change.

Try it — no build step

<script src="https://unpkg.com/monowind/dist/cdn.js"></script>

<mono-wind>
  <div class="flex min-h-5 items-center justify-between border px-1">
    <div>left</div>
    <button>right</button>
  </div>
</mono-wind>

The host is a container like any element: <mono-wind>hello</mono-wind> lays its own text out too.

With your own Tailwind (v4) build

npm install monowind
/* app.css */
@import "tailwindcss";
@import "monowind";
import { defineMonoWind } from "monowind";
defineMonoWind();

Selection

select="grid" (the default) selects the rendered grid: drag across the art and copy exactly what you see. Double- and triple-click select the element under the pointer — the word or the paragraph, as on any page — and drag extends word by word or paragraph by paragraph. select="text" selects your elements' text instead, character by character, the way a page does. In both modes the highlight is drawn on the grid, cell for cell, as reverse video: each cell's colors swap, so colored text selects as a band of its color. A copy of element text is plain text laid out by the standard innerText rules (paragraphs separated by a blank line, table cells by tabs).

Glyph widths follow the terminal convention: CJK, Hangul, and emoji take two cells, everything else one, whatever the font draws — a glyph the font lacks is scaled into its cells so the grid never drifts. clusterWidth, clusterAdvances, graphemes, and textCells are exported for code that lays out text of its own.

Keyboard focus

Tab moves focus as on any page. focus="arrows" on <mono-wind> adds the arrow keys: from the focused element, an arrow moves focus to the nearest focusable element in that direction on the grid, the way a terminal form does. Controls keep the arrows they use — Left and Right in a text field, all four in a textarea, a radio group's own — a modifier makes any arrow native, and nothing wraps.

Scrolling

overflow-y-auto (or -scroll, either axis) makes the element a scroll container: the browser owns the scroll physics — wheel, touch, keyboard, scrollIntoView, scrollTop — while the engine mirrors it on the grid in whole-cell steps and draws the scrollbar as characters (track , thumb , draggable; themable via glyph sets; scrollbar-color honored and defaulting to currentColor like borders, scrollbar-width: none honored, thickness via scrollbar-<n> cells, per bar with scrollbar-x-<n> / scrollbar-y-<n>; scrollbar-inset-<n> keeps cells clear around the bars for your own arrow buttons). Scroll containers pinned to the bottom stay pinned as content grows — chat logs need no code.

Border & rule glyphs

Border styles render through a glyph set — swap the characters without touching your markup. Pick a built-in with a borders-* utility on the element that owns the decoration (or any ancestor — it inherits):

<div class="border borders-rounded">╭─╮ corners</div>
<div class="border border-double borders-ascii">+=+ everywhere</div>
<div class="border border-double borders-single">─│ only, DEC-style</div>

Built-ins: default, rounded, ascii, single, blocks, cp437. Or register your own (per-glyph fallback — override only what you need) and reference it the same way:

import { registerBorderGlyphs } from "monowind";
registerBorderGlyphs("stars", { solid: { tl: "✧", tr: "✧", bl: "✧", br: "✧" } });
.fancy {
  --mw-border-glyphs: stars; /* what the borders-* utilities set */
}

A registered set is frozen; to change one, register it again.

A set can also register corner glyphs by border-radius — a corner draws the registration nearest its radius in cells, the plain corner counting at 0 (the defaults round light-line corners to ╭ ╮ ╰ ╯ from rounded-xs up) — and the shade ramp box shadows step through, densest first (default █ ▓ ▒ ░):

registerBorderGlyphs("soft", {
  solid: {
    rounded: [{ radius: 2, tl: "◜", tr: "◝", bl: "◟", br: "◞" }],
    shadow: ["▓", "▒", "░", "·"],
  },
});

border-width is a weight the set interprets: the defaults draw border-2 and up as heavy lines (━ ┃ ┏ ┓ ┗ ┛) in one cell, and rule-2 on a gap the same way. A set registers its own weight bands — glyphs for a width, and the cells it takes — so a theme whose font has no heavy glyphs can draw two rings instead, which is what ascii, single, rounded, and blocks do (cp437 draws double, as DOS did):

registerBorderGlyphs("rings", { solid: { weights: [{ width: 2, cells: 2 }] } });
registerBorderGlyphs("bold", {
  solid: { weights: [{ width: 2, h: "═", v: "║", tl: "╔", tr: "╗", bl: "╚", br: "╝" }] },
});

Borders, blocks, and scrollbars tile in any font: where a font draws its box-drawing or block glyphs shorter than the row (Menlo and SF Mono do, and any font under a taller leading-*), the grid fits them to it, so rows never show a seam.

Shadows and rounded corners

shadow-* paints a box's silhouette behind it in shade glyphs from the glyph set: offsets in whole cells (a 4px offset is one cell, the classic DOS shadow), blur as rings that fade outward, spread in cells, a translucent color as a lighter shade leaning on the theme's foreground, inset shadows inside the padding box. Tailwind's presets are pixel recipes, and their blur and spread convert on the spacing scale (4px to a cell), so shadow-2xl is a six-ring halo. For presets tuned to the grid, redefine them in your Tailwind theme:

@theme {
  --shadow-sm: 4px 4px 0 0 rgb(0 0 0 / 0.1);
  --shadow-md: 4px 4px 8px 0 rgb(0 0 0 / 0.1);
  --shadow-lg: 4px 4px 16px 0 rgb(0 0 0 / 0.1);
}

rounded-* picks the corner glyphs a set registers nearest the radius — the defaults' ╭ ╮ ╰ ╯ for light-line borders — while heavy and double borders stay square.

Gradients

bg-linear-*, bg-radial, and bg-conic (with from-*, via-*, to-*, positions, and a space such as /srgb, /oklch, /longer) paint a color per cell: each cell takes the gradient's color at its center, in CSS's geometry for the box, the stops interpolated as CSS does, layers composited over the plain color. Text keeps its own color, and bg-clip-text text-transparent shows the gradient through it. Sizes, positions, and repeats of the background image are ignored, as are url() images: a gradient always covers the box once.

Companion packages

The core is self-contained; these are optional:

  • @monowind/themes — class-scoped themes modeled on real systems (dos, c64, green-phosphor, …): authentic palettes, period fonts, era-correct border characters
  • @monowind/ascii<mono-ascii> FIGlet banner text with gradient/metal effects
  • @monowind/qr-code<mono-qr> scannable QR codes, packed into the grid's cells
  • @monowind/vite — zero-config Vite plugin, Tailwind included

Docs