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

typeset-p

v0.1.2

Published

Browser-only Web Component for advanced typesetting — Knuth-Plass line breaking, optical margin alignment, smart typography.

Readme

typeset-p

Repository

A Web Component for paragraph typesetting in the browser, aiming for professional quality. Drop it in wherever you render body text and get Knuth-Plass optimal line-breaking, smart quotes, soft hyphenation, and optical margin alignment — all without touching your existing markup or styles.

<typeset-p mode="custom" align="justify">
  It was a truth generally accepted that a man with a fortune must be
  in need of a wife, yet the quiet countryside seemed to have forgotten this.
</typeset-p>

Installation

npm install typeset-p

Quick start

Import once — anywhere in your app — to register the custom element globally:

import 'typeset-p';

To tune optical margin pull amounts for your font (optional):

import 'typeset-p/styles.css';

Then use <typeset-p> anywhere in your HTML or component templates. It inherits font, size, and color from CSS like any other element.

Modes

The mode attribute controls how much processing is applied.

| Mode | What it does | |------|-------------| | custom | Default. Full JS pipeline: smart quotes → soft hyphenation → Knuth-Plass line-breaking → per-line word spacing | | browser | Native CSS only: text-wrap: pretty, hyphens: auto, hanging-punctuation: first last. No JavaScript runs — quality depends entirely on what the browser supports. | | default | Plain browser rendering, no enhancement |

custom is the default and produces the best results. If you hit issues (font not resolving, layout constraints, strict CSP), dial back to browser for a zero-JS CSS-only fallback, or default to opt out entirely.

Attributes

| Attribute | Type | Default | Description | |-----------|------|---------|-------------| | mode | "custom" | "browser" | "default" | "custom" | Processing mode | | align | "left" | "justify" | "right" | "left" | Text alignment | | font | string | inherited from CSS | Canvas measurement font family | | font-size | string | inherited from CSS | Canvas measurement font size | | hyphenate | "false" to disable | enabled | Soft hyphenation | | smart-quotes | "false" to disable | enabled | Typography normalization | | hanging-punctuation | "false" to disable | enabled | Optical margin alignment | | last-line | "average" | "justify" | "ragged" | "average" | Final-line spacing when align="justify" (custom mode only) |

font — Font family used for canvas measurement, e.g. "Lora, Georgia, serif". Must be a resolved family name — CSS variables are not evaluated on canvas. If omitted, read from getComputedStyle; CSS-only changes won't trigger a re-render.

font-size — Font size passed to canvas measurement, e.g. "18px".

hyphenate — Soft hyphenation for words ≥ 8 characters.

smart-quotes — Converts "straight" quotes to "curly" quotes, -- to em dashes, ... to ellipses.

hanging-punctuation — Optical margin alignment for quotes and similar punctuation. Behavior depends on mode:

| Mode | Mechanism | |------|-----------| | custom | Inline negative margins on line-initial opening quotes (works out of the box; tune with --typeset-pull-single / --typeset-pull-double on the element) | | browser | Native hanging-punctuation: first last (Safari today; other engines may ignore it) | | default | No hanging |

Set hanging-punctuation="false" to disable alignment entirely. In custom mode, quotes then stay inline and are included in Knuth-Plass line-width calculations like any other character.

Notes for custom mode

display: block is set automatically. The component sets display: block on itself before measuring line width, so you don't need to add it in CSS. If you need a different outer layout (e.g. display: grid), wrap it in a container element instead.

Pass a resolved font family via the font attribute. The Knuth-Plass pipeline measures glyph widths on a canvas using the value of font. CSS variables and computed values are read from getComputedStyle as a fallback, but var(--my-font) will not resolve on canvas — pass the actual family string:

<!-- good -->
<typeset-p mode="custom" font="Lora, Georgia, serif">…</typeset-p>

<!-- won't measure correctly — canvas can't resolve CSS variables -->
<typeset-p mode="custom" style="font-family: var(--body-font)">…</typeset-p>

align scope by mode. The align attribute has full effect in custom mode (line-breaking + per-line CSS). In browser mode the component sets the corresponding CSS itself. In default mode it sets text-align on the element. In all cases, align="justify" only produces true justified text in custom mode — browser justification via text-align: justify alone tends to be uneven and is not applied in browser mode.

Last line in justify mode. Body lines are fully justified to the column width. The final line intentionally keeps a ragged right edge: it receives the average word-spacing of the body lines (capped so it still fits), not full justification. Use last-line="justify" to stretch and justify the final line when it fits, or last-line="ragged" for normal word spacing and explicit left alignment.

Usage by framework

Vanilla HTML

<script type="module" src="https://unpkg.com/typeset-p/dist/index.js"></script>

<typeset-p mode="custom" align="justify">
  Your paragraph text here.
</typeset-p>

React

import 'typeset-p';

export function Article({ children }) {
  return (
    <typeset-p mode="custom" align="justify" font="Georgia, serif" font-size="18px">
      {children}
    </typeset-p>
  );
}

TypeScript users: add this once to a .d.ts file in your project to get typed JSX attributes:

import type { TypesetPAttributes } from 'typeset-p';

declare global {
  namespace React {
    namespace JSX {
      interface IntrinsicElements {
        'typeset-p': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>
          & TypesetPAttributes;
      }
    }
  }
}

Vue

<script setup>
import 'typeset-p';
</script>

<template>
  <typeset-p mode="custom" align="justify">
    Your paragraph text here.
  </typeset-p>
</template>

To suppress Vue's unknown element warning, tell it to ignore the tag:

// vite.config.js / vue.config.js
export default {
  vue: {
    compilerOptions: {
      isCustomElement: tag => tag === 'typeset-p',
    },
  },
};

Svelte

<script>
  import 'typeset-p';
</script>

<typeset-p mode="custom" align="justify">
  Your paragraph text here.
</typeset-p>

Angular

Register the CUSTOM_ELEMENTS_SCHEMA in your module or component, then import the package in main.ts:

// main.ts
import 'typeset-p';
// app.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';

@NgModule({
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}

Responding to attribute changes

The component re-runs the full pipeline whenever mode, align, font, font-size, or any feature toggle attribute changes. You can drive it from JavaScript like any other element:

const el = document.querySelector('typeset-p');
el.setAttribute('font-size', '20px');  // re-typesets immediately
el.setAttribute('align', 'justify');

It also watches its own width via ResizeObserver and re-breaks lines automatically when the container is resized.

Manual registration

If you need to control when or under what name the element is registered:

import { TypesetP } from 'typeset-p';

customElements.define('typeset-p', TypesetP);
// or a custom name:
customElements.define('my-paragraph', TypesetP);

SSR / server-side rendering

The package is safe to import in Node.js. The element registers itself only when customElements is available, so importing it in Next.js, Nuxt, SvelteKit, or Astro will not throw on the server. The element renders as plain text during SSR and hydrates in the browser.

TypeScript

The package ships full type declarations. document.querySelector('typeset-p') automatically returns TypesetP thanks to the HTMLElementTagNameMap augmentation included in the package.

Named types are exported for use in your own code:

import type { TypesetPMode, TypesetPAlign, TypesetPAttributes } from 'typeset-p';

Browser support

Requires a browser with ResizeObserver, FontFace API, and customElements — all evergreen browsers since 2020.