Skip to Content
  • Website
Codsen
  • Home
  • Open Source
  • Articles
  • About

prevOpen Source→remark-typographynext

remark-typography0.8.6

Remark plugin to fix typography: quotes, dashes and so on.

Downloads per monthChangelogMIT Licenselibera manifesto
  • the top
  • Installation
  • Quick Take
  • Examples
  • FIX ENGLISH TY…
  • QUICK START
  • TYPOGRAPHY…
  • MARKDOWN BO…
  • API
  • EXPORTED TYPE…
  • Changelog

Permalink to InstallationInstallation

Permalink to Quick TakeQuick Take

Permalink to ExamplesExamples

  • Convert a three-dot ellipsis
  • Fix apostrophes following inline code nodes
  • Convert a multiplication marker
  • Report document progress and completion
  • Convert straight quotes and apostrophes
  • Keep the last two words together
  • Convert a spaced dash

Fix English typography in Markdown

remark-typography is a Remark plugin that improves English typography while preserving Markdown structure. It converts contextual quotes and apostrophes, dashes, ellipses, multiplication markers, and the final eligible word break in each supported block.

The plugin emits raw Unicode characters. It does not encode punctuation as HTML entities.

Quick start

Await .process() to finish the pipeline before reading the output:

import { remark } from "remark";
import fixTypography from "remark-typography";

const file = await remark()
  .use(fixTypography)
  .process("Yes that's true but...");

console.log(file.toString().trim());
// => Yes that’s true but…

The space between the final two words in that output is U+00A0, a non-breaking space.

When you assemble a Unified pipeline yourself, run this MDAST plugin after syntax extensions such as remark-gfm and before remark-rehype:

import remarkGfm from "remark-gfm";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import fixTypography from "remark-typography";
import { unified } from "unified";

const processor = unified()
  .use(remarkParse)
  .use(remarkGfm)
  .use(fixTypography)
  .use(remarkRehype);

Running it after remark-rehype is too late because the tree is HAST by then.

Typography rules

Input patternResultNotes
'single', "double", that's
'single', "double", that's‘single’, “double”, that’sQuote direction is chosen from the surrounding phrasing text.
word - word
word - wordword — wordSpaced prose dashes become em dashes.
1-2, 3 - 4
1-2, 3 - 41–2, 3 – 4Numeric ranges use en dashes.
...
...…Every run of exactly three dots is converted; longer dot runs are retained.
3 x 4, -3.5kg x +4m
3 x 4, -3.5kg x +4m3 × 4, -3.5kg × +4mBoth operands must be unambiguous ASCII-style quantities separated from x by horizontal whitespace.
A qualifying final word break
A qualifying final word breakU+00A0Widow prevention runs once per supported block or line.

The multiplication rule leaves ambiguous prose, currency expressions, Unicode digits, identifier fragments, and line-spanning expressions unchanged.

Markdown boundaries

Typography is calculated over each paragraph, heading, and GFM table cell. A paragraph nested in a list item or blockquote is therefore handled normally. Hard breaks and <br> elements bound line-level widow handling.

Visible phrasing context crosses text nodes inside emphasis, strong text, and link labels. For example, The **compiler**'s output receives the same apostrophe as its unformatted equivalent.

The plugin changes only mutable MDAST text values. It preserves node identity, tree structure, and existing source positions. Inline and fenced code, link and image destinations, titles, image alt text, and definitions remain unchanged. Inline code and image alt text can still supply immutable context for adjacent punctuation. Literal HTML inside inline code or escaped text is visible text, so it does not become an HTML attribute range. A final inline-code span stays unchanged while its eligible preceding mutable space receives widow prevention. This also applies after earlier replacements in another line of the same block.

API

The package has one runtime export: the default fixTypography Unified plugin. Both calls below are valid:

remark().use(fixTypography);
remark().use(fixTypography, {});

Options

OptionTypeDefaultDescription
reportProgressFunc
Type: function, false, or null
Default: null
reportProgressFuncfunction, false, or nullnullReceive finite, strictly increasing integer percentages.
reportProgressFuncFrom
Type: integer from 0 through 100
Default: 0
reportProgressFuncFrominteger from 0 through 1000Set the inclusive start of the reported range.
reportProgressFuncTo
Type: integer from 0 through 100
Default: 100
reportProgressFuncTointeger from 0 through 100100Set the inclusive end of the reported range.

The start cannot exceed the end. The callback receives both configured endpoints; equal endpoints are reported once. Callback exceptions propagate and abort the transform.

Use the range options when this plugin is one stage in a larger progress sequence:

const progress = [];

const file = remark()
  .use(fixTypography, {
    reportProgressFunc: (percentageDone) => progress.push(percentageDone),
    reportProgressFuncFrom: 20,
    reportProgressFuncTo: 40,
  })
  .processSync("Wait...");

console.log(progress.at(0), progress.at(-1));
// => 20 40

Completion data

After transformation, file.data.remarkTypography contains a plain, JSON-representable completion record:

FieldDescription
blocksProcessed
blocksProcessedSupported paragraph, heading, and table-cell blocks visited.
textNodesProcessed
textNodesProcessedMutable text nodes inspected inside those blocks.
charactersProcessed
charactersProcessedUTF-16 code units inspected in the blocks’ logical phrasing streams.
textNodesChanged
textNodesChangedText nodes whose values changed.
replacementsApplied
replacementsAppliedTotal accepted replacement ranges.
apostrophesConverted
apostrophesConvertedQuote or apostrophe replacements applied.
dashesConverted
dashesConvertedEn-dash and em-dash replacements applied.
ellipsesConverted
ellipsesConvertedExact three-dot runs converted.
multiplicationSignsConverted
multiplicationSignsConvertedQuantity separators changed from x to ×.
widowMeasuresAdded
widowMeasuresAddedNon-breaking widow measures inserted.
timeTakenInMilliseconds
timeTakenInMillisecondsBest-effort elapsed transform time.

The final progress endpoint is emitted only after mutations and completion data are available.

Exported types

import type {
  Opts,
  RemarkTypographyCompletion,
} from "remark-typography";

Permalink to changelogChangelog

Open Changelog
↑ back to top
prev next

Copyright

All rights reserved © Roy Revelt 2026
All our open source packages are under MIT licenceopens in a new tab

Activities

🐛 See a bug? Raise an issueopens in a new tab
💘 Check out the Indiewebopens in a new tab and Libera manifestoopens in a new tab