wrap-ansi

Word-wraps strings containing ANSI escape codes without breaking color, style, or hyperlink sequences.

Library
npm
v10.0.1
139stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
58/100Fair
Development Activity52
Maintenance32
Community68
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
73/100Good
Architecture78
Code Quality92
Innovation75
Learning Curve45

wrap-ansi word-wraps a string to a specified column width while correctly handling ANSI escape codes — the invisible sequences that terminal libraries like chalk use to add color, bold, underline, and hyperlinks. A naive word-wrap implementation would count escape codes as visible characters, corrupt sequences mid-code, or leave styles bleeding past the point they were meant to end. wrap-ansi instead parses each line into a sequence of plain-text tokens and complete ANSI/OSC control sequences, measures only the visible text against the target width, and reopens any interrupted SGR (color/style) sequence or OSC 8 hyperlink after every forced line break, so each wrapped row renders correctly in isolation.

It supports both soft wrapping (long unbreakable words may exceed the column width) and hard wrapping (nothing exceeds the width, splitting words mid-character if necessary), configurable trimming of leading/trailing whitespace per row, and a toggle for whether word boundaries are respected at all. Because it measures width over Unicode grapheme clusters via Intl.Segmenter rather than raw UTF-16 length, it correctly handles wide characters, combining marks, and emoji. This makes it a foundational building block across the chalk/sindresorhus terminal-tooling ecosystem — CLI libraries that lay out colorized text (boxes, tables, prompts, progress output) depend on it to wrap that text without dropping or misplacing escape sequences.

What You Get

  • A single wrapAnsi(string, columns, options) function with zero configuration required for the common case
  • Soft and hard wrap modes, controlled via the hard option
  • Automatic style and hyperlink continuation across wrapped lines, so colors and links never leak past their intended scope
  • Grapheme-cluster-aware width measurement via string-width, correctly handling wide, emoji, and combining characters
  • Shipped TypeScript type definitions (index.d.ts) with no separate @types package needed

Common Use Cases

  • CLI help text formatting - CLI tool authors wrap colorized usage/help output to the terminal’s width without breaking ANSI codes mid-sequence
  • Terminal UI libraries - libraries that render boxes, tables, and progress displays use wrap-ansi internally to lay out styled text within a fixed width
  • Log line wrapping - logging utilities wrap long colorized log messages to a fixed console width for readability
  • Interactive prompt rendering - CLI prompt libraries wrap multi-line colored responses so they display correctly regardless of terminal width

Under The Hood

Architecture The library is a single ESM module (index.js) organized as a linear pipeline rather than a class hierarchy: forEachSegment walks a string as alternating plain-text runs and complete escape sequences (matched via a sticky regex covering CSI/SGR and OSC 8 forms), getTokens/splitWords build on it to produce grapheme-aware tokens and word boundaries, exec performs the actual column-fitting logic row by row, and restoreStylesAcrossRows does a final pass that tracks active SGR styles and any open OSC 8 hyperlink so it can close them before a forced line break and reopen them after. The public wrapAnsi export just normalizes line endings, expands tabs, and maps exec over each input line. Changing the core escape-sequence regex would ripple through every downstream function, since all of them assume sequences are matched as complete, zero-width units.

Tech Stack Pure ESM with no build step or bundler — package.json declares "type": "module" and ships index.js/index.d.ts directly via the exports map, targeting Node >=20. Runtime dependencies are minimal and both from the same author’s ecosystem: ansi-styles (^6.2.3) for the set of known SGR close codes, and string-width (^8.2.0) for visible-width measurement. Dev dependencies are test/lint-only: chalk, has-ansi, and strip-ansi for fixtures and assertions, tsd for type-definition testing, and xo (an opinionated ESLint preset) for linting. CI (GitHub Actions) runs the test suite against Node 20 and 24.

Code Quality Tests live in a single test.js using Node’s built-in node:test runner and assert/strict, with dozens of cases covering soft/hard wrap, trim, wordWrap, tab expansion, multi-line style/hyperlink continuation, and edge cases like empty or whitespace-only input. npm test chains xo (lint), node --test (unit tests), and tsd (type-level tests against index.d.ts), so type correctness is checked as rigorously as runtime behavior. Naming is consistent and explicit — protocol constants are named (ANSI_SGR_RESET_FOREGROUND, ANSI_ESCAPE_LINK, etc.) rather than left as magic strings, and inline comments explain non-obvious algorithmic choices (why the regex is sticky, why ASCII text skips grapheme segmentation) throughout the source.

What Makes It Unique Most naive ANSI-aware wrapping implementations strip escape codes before measuring and reinsert them by position, which breaks down once styles need to be closed and reopened across an inserted line break. wrap-ansi instead treats sequences as complete, zero-width tokens from the start and does an explicit state-tracking pass (restoreStylesAcrossRows) that models active SGR parameters and OSC 8 hyperlinks as a small state machine, closing and reopening them precisely at each row boundary — including the colon-delimited RGB/256-color and underline-color SGR forms, not just the common numeric codes. It also takes a deliberate ASCII fast path (ASCII_PRINTABLE_REGEX) to avoid grapheme segmentation for plain text, trading a small amount of code for a real performance win on the common case.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search