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

string-width-ts

v1.0.0

Published

Get the visual width of a string with enhanced TypeScript support and modern features

Readme

📏 string-width-ts

npm version TypeScript License: MIT Node.js Version

YouTube Buy Me a Coffee

A modern, TypeScript-first string width calculator with enhanced Unicode, emoji, and ANSI support


🎯 What is this?

Ever wondered why your terminal output looks misaligned when using emojis, CJK characters, or ANSI colors? string-width-ts solves this by accurately calculating the visual width of strings - how many columns they actually occupy on screen.

// ❌ This looks wrong:
console.log("Name".padEnd(10) + "Age");
console.log("古川".padEnd(10) + "25"); // Misaligned!

// ✅ This looks perfect:
import { padString } from "string-width-ts";
console.log(padString("Name", 10) + "Age");
console.log(padString("古川", 10) + "25"); // Perfectly aligned!

🚀 Quick Start

Installation

npm install string-width-ts

Basic Usage

import { stringWidth } from "string-width-ts";

// Regular characters
stringWidth("Hello"); // → 5

// Wide characters (CJK)
stringWidth("こんにちは"); // → 10 (each character = 2 columns)

// Emojis
stringWidth("Hello 👋"); // → 7 (emoji = 2 columns)

// ANSI codes (ignored by default)
stringWidth("\x1b[31mRed text\x1b[0m"); // → 8 (ANSI codes don't affect width)

✨ Why Choose string-width-ts?

🆚 Comparison with Alternatives

| Feature | string-width | wcwidth | string-width-ts | | ---------------------- | ------------- | ------- | ------------------------------ | | TypeScript Native | ⚠️ Types only | ❌ | ✅ Built with TS | | Zero Dependencies | ❌ | ❌ | ✅ 100% self-contained | | Modern ES Features | ❌ | ❌ | ✅ ES2023 & latest TS | | Enhanced Emoji Support | ⚠️ Basic | ❌ | ✅ Complex emoji sequences | | Multi-line Support | ❌ | ❌ | ✅ Built-in utilities | | Detailed Analysis | ❌ | ❌ | ✅ Comprehensive info | | String Manipulation | ❌ | ❌ | ✅ Truncate, pad, align | | Custom Width Rules | ❌ | ❌ | ✅ Configurable mapping |

🎯 Key Features

  • 🎯 Pixel-Perfect Accuracy: Handles fullwidth, emojis, combining characters, and ANSI codes
  • 🌍 Complete Unicode Support: East Asian characters, complex scripts, modern emojis
  • 🎨 ANSI-Aware: Properly handles terminal colors and formatting
  • 📊 Detailed Analytics: Get comprehensive string composition analysis
  • 🔧 Highly Configurable: Custom width rules, normalization, edge case handling
  • ⚡ Zero Dependencies: No external packages required
  • 🛡️ Type Safe: Full TypeScript support with strict typing
  • 🧪 Battle Tested: Comprehensive test suite with 28+ test cases

📖 Complete API Reference

Core Functions

stringWidth(string, options?)

Calculate the visual width of a string.

import { stringWidth } from "string-width-ts";

// Examples
stringWidth("a"); // → 1
stringWidth("古"); // → 2
stringWidth("👨‍👩‍👧‍👦"); // → 2 (family emoji)
stringWidth("\u001B[1m古\u001B[22m"); // → 2 (ignores ANSI)

// With options
stringWidth("🌈", { emojiAsNarrow: true }); // → 1 instead of 2
stringWidth("古", { ambiguousIsNarrow: false }); // → 2 (wide mode)

getStringWidthInfo(string, options?)

Get detailed analysis of string composition.

import { getStringWidthInfo } from "string-width-ts";

const info = getStringWidthInfo("Hello 👋 世界");
console.log(info);
// {
//   width: 11,           // Total visual width
//   characters: 9,       // Character count
//   graphemes: 9,        // Grapheme clusters
//   ansiSequences: 0,    // ANSI escape codes
//   emojis: 1,          // Emoji count
//   zeroWidthChars: 0,   // Zero-width characters
//   combiningChars: 0,   // Combining characters
//   widthBreakdown: {
//     width0: 0,         // Zero-width chars
//     width1: 7,         // Narrow chars
//     width2: 2          // Wide chars
//   }
// }

Multi-line Support

getWidestLineWidth(string, options?)

Find the width of the widest line in multi-line text.

import { getWidestLineWidth } from "string-width-ts";

const text = `
Hello World
こんにちは 👋
Short
`;

getWidestLineWidth(text); // → 12 (from "こんにちは 👋")

getMultiLineWidthInfo(string, options?)

Comprehensive analysis for multi-line strings.

import { getMultiLineWidthInfo } from "string-width-ts";

const info = getMultiLineWidthInfo("Hello\n世界\nTest 👋");
console.log(info);
// {
//   lines: [
//     { content: "Hello", width: 5, /* ... */ },
//     { content: "世界", width: 4, /* ... */ },
//     { content: "Test 👋", width: 7, /* ... */ }
//   ],
//   maxWidth: 7,         // Widest line
//   lineCount: 3,        // Number of lines
//   averageWidth: 5.33   // Average width
// }

String Manipulation

truncateString(string, targetWidth, options?, suffix?)

Intelligently truncate strings to fit specific widths.

import { truncateString } from "string-width-ts";

// Basic truncation
truncateString("Hello World", 8); // → "Hello..."

// With wide characters
truncateString("Hello 世界", 6); // → "Hel..."

// Custom suffix
truncateString("Hello World", 8, {}, "…"); // → "Hello W…"

// Preserve words
truncateString("Hello beautiful world", 10, { preserveWords: true }); // → "Hello..."

padString(string, targetWidth, options?, padString?, position?)

Pad strings to specific visual widths.

import { padString } from "string-width-ts";

// Right padding (default)
padString("世界", 6); // → "世界  "

// Left padding
padString("世界", 6, {}, " ", "start"); // → "  世界"

// Center padding
padString("世界", 8, {}, " ", "both"); // → "  世界  "

// Custom padding character
padString("Hello", 10, {}, "-"); // → "Hello-----"

⚙️ Configuration Options

interface StringWidthOptions {
  // Treat ambiguous characters as narrow (1) instead of wide (2)
  ambiguousIsNarrow?: boolean; // default: true

  // Include ANSI escape codes in width calculation
  countAnsiEscapeCodes?: boolean; // default: false

  // Include zero-width characters in calculation
  includeZeroWidth?: boolean; // default: false

  // Treat emojis as narrow (width 1) instead of wide (width 2)
  emojiAsNarrow?: boolean; // default: false

  // Custom width rules for specific Unicode code points
  customWidthMap?: Map<number, number>;

  // Unicode normalization before calculation
  normalize?: boolean | "NFC" | "NFD" | "NFKC" | "NFKD"; // default: false
}

🎯 Real-World Examples

1. Terminal Tables with Perfect Alignment

import { stringWidth, padString } from "string-width-ts";

const data = [
  { name: "John", city: "New York", emoji: "🇺🇸" },
  { name: "田中", city: "東京", emoji: "🇯🇵" },
  { name: "José", city: "Madrid", emoji: "🇪🇸" },
];

// Calculate column widths
const nameWidth = Math.max(...data.map((row) => stringWidth(row.name))) + 2;
const cityWidth = Math.max(...data.map((row) => stringWidth(row.city))) + 2;

// Print perfectly aligned table
data.forEach((row) => {
  const line = [
    padString(row.name, nameWidth),
    padString(row.city, cityWidth),
    row.emoji,
  ].join(" | ");
  console.log(line);
});

2. CLI Progress Bars with Unicode

import { stringWidth, truncateString } from "string-width-ts";

function createProgressBar(
  label: string,
  progress: number,
  totalWidth: number
) {
  const progressChars = Math.floor((progress / 100) * 20);
  const bar = "█".repeat(progressChars) + "░".repeat(20 - progressChars);

  const availableWidth = totalWidth - 30; // Reserve space for bar and percentage
  const truncatedLabel = truncateString(label, availableWidth);

  return `${truncatedLabel} [${bar}] ${progress.toFixed(1)}%`;
}

console.log(createProgressBar("Processing 大きなファイル.txt", 75.5, 60));
// → "Processing 大きなファイ... [███████████████░░░░░] 75.5%"

3. Smart Text Wrapping

import { stringWidth, getStringWidthInfo } from "string-width-ts";

function wrapText(text: string, maxWidth: number): string[] {
  const words = text.split(" ");
  const lines: string[] = [];
  let currentLine = "";

  for (const word of words) {
    const testLine = currentLine ? `${currentLine} ${word}` : word;

    if (stringWidth(testLine) <= maxWidth) {
      currentLine = testLine;
    } else {
      if (currentLine) lines.push(currentLine);
      currentLine = word;
    }
  }

  if (currentLine) lines.push(currentLine);
  return lines;
}

const text = "Hello 世界 this is a long text with 絵文字 👋 and symbols";
const wrapped = wrapText(text, 20);
console.log(wrapped);
// Each line fits within 20 visual columns

4. Advanced Emoji Handling

import { getStringWidthInfo } from "string-width-ts";

// Complex emoji sequences
const family = "👨‍👩‍👧‍👦"; // Family emoji with ZWJ sequences
const flag = "🇺🇸"; // Flag emoji (regional indicators)
const skinTone = "👋🏽"; // Emoji with skin tone modifier

const familyInfo = getStringWidthInfo(family);
console.log(
  `Family emoji: width=${familyInfo.width}, graphemes=${familyInfo.graphemes}`
);
// → Family emoji: width=2, graphemes=1

const flagInfo = getStringWidthInfo(flag);
console.log(
  `Flag emoji: width=${flagInfo.width}, characters=${flagInfo.characters}`
);
// → Flag emoji: width=2, characters=2

🔧 Advanced Configuration

Custom Width Mapping

import { stringWidth } from "string-width-ts";

// Define custom width rules
const customMap = new Map([
  [65, 3], // 'A' takes 3 columns
  [0x1f600, 1], // 😀 takes 1 column instead of 2
  [0x4e00, 3], // 一 (CJK) takes 3 columns
]);

const result = stringWidth("A😀一B", { customWidthMap: customMap });
console.log(result); // → 8 (3+1+3+1)

Unicode Normalization

import { stringWidth } from "string-width-ts";

// These look the same but are different Unicode sequences
const precomposed = "é"; // Single character U+00E9
const decomposed = "e\u0301"; // e + combining acute accent

console.log(stringWidth(precomposed, { normalize: "NFD" })); // Normalize to decomposed
console.log(stringWidth(decomposed, { normalize: "NFC" })); // Normalize to composed

// Useful for consistent width calculation across different input sources

🧪 Testing & Development

# Run all tests
npm test

# Run tests in watch mode
npm run test:watch

# Build the project
npm run build

# Development mode (watch for changes)
npm run dev

# Type checking only
npm run lint

📋 Requirements

  • Node.js: 18.0.0 or higher
  • TypeScript: 5.9.2 (latest) for development
  • Zero runtime dependencies 🎉

🤝 Contributing

We welcome contributions! Please see our contributing guidelines for details.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📚 Learn More

📺 Video Tutorials

Check out our YouTube channel for in-depth tutorials and examples!

☕ Support the Project

If this project helps you, consider buying me a coffee!

🔗 Related Projects

📜 License

MIT © ReactJS BD


Made with ❤️ using TypeScript 5.9.2

GitHub stars GitHub forks

Returns

  • number: The visual width of the string

Options

interface StringWidthOptions {
  // Count ambiguous characters as narrow (1) instead of wide (2)
  ambiguousIsNarrow?: boolean; // default: true

  // Whether to count ANSI escape codes in width calculation
  countAnsiEscapeCodes?: boolean; // default: false

  // Whether to include zero-width characters
  includeZeroWidth?: boolean; // default: false

  // Treat emojis as narrow (width 1) instead of wide (width 2)
  emojiAsNarrow?: boolean; // default: false

  // Custom width mapping for specific characters
  customWidthMap?: Map<number, number>;

  // Normalize Unicode before calculation
  normalize?: boolean | "NFC" | "NFD" | "NFKC" | "NFKD"; // default: false
}

getStringWidthInfo(string, options?)

Get detailed information about string width calculation.

import { getStringWidthInfo } from "string-width-ts";

const info = getStringWidthInfo("Hello 👋 世界");
console.log(info);
// {
//   width: 11,
//   characters: 9,
//   graphemes: 9,
//   ansiSequences: 0,
//   emojis: 1,
//   zeroWidthChars: 0,
//   combiningChars: 0,
//   widthBreakdown: {
//     width0: 0,  // zero-width characters
//     width1: 7,  // narrow characters
//     width2: 2   // wide characters
//   }
// }

getWidestLineWidth(string, options?)

Get the width of the widest line in a multi-line string.

import { getWidestLineWidth } from "string-width-ts";

const width = getWidestLineWidth("Hello\n世界\nTest");
console.log(width); // 5

getMultiLineWidthInfo(string, options?)

Get comprehensive width analysis for multi-line strings.

import { getMultiLineWidthInfo } from "string-width-ts";

const info = getMultiLineWidthInfo("Hello\n世界\nTest 👋");
console.log(info);
// {
//   lines: [...],      // detailed info for each line
//   maxWidth: 7,       // width of widest line
//   lineCount: 3,      // number of lines
//   averageWidth: 5.33 // average width across lines
// }

truncateString(string, targetWidth, options?, suffix?)

Truncate a string to a specific visual width.

import { truncateString } from "string-width-ts";

truncateString("Hello World", 8); // 'Hello...'
truncateString("Hello 世界", 6); // 'Hel...'
truncateString("Hello World", 8, {}, "…"); // 'Hello W…'

padString(string, targetWidth, options?, padString?, position?)

Pad a string to a specific visual width.

import { padString } from "string-width-ts";

padString("世界", 6); // '世界  '
padString("世界", 6, {}, " ", "start"); // '  世界'
padString("世界", 6, {}, " ", "both"); // ' 世界 '

🎯 Use Cases

Terminal Applications

import { stringWidth, padString } from "string-width-ts";

// Create aligned columns
const items = ["Item", "项目", "アイテム", "Element"];
const maxWidth = Math.max(...items.map((item) => stringWidth(item)));

items.forEach((item) => {
  console.log(padString(item, maxWidth + 2) + "| Description");
});

CLI Progress Bars

import { stringWidth, truncateString } from "string-width-ts";

function createProgressBar(label: string, progress: number, width: number) {
  const availableWidth = width - 10; // Reserve space for percentage
  const truncatedLabel = truncateString(label, availableWidth);
  const padding = " ".repeat(availableWidth - stringWidth(truncatedLabel));

  return `${truncatedLabel}${padding} ${progress.toFixed(1)}%`;
}

console.log(createProgressBar("Processing 文件.txt", 75.5, 50));

Text Layout

import { getMultiLineWidthInfo, padString } from "string-width-ts";

function centerText(text: string, containerWidth: number): string {
  const info = getMultiLineWidthInfo(text);

  return info.lines
    .map((line) => padString("", containerWidth, {}, " ", "both"))
    .join("\n");
}

🆚 Comparison with string-width

| Feature | string-width | string-width-ts | | ----------------------- | ------------------- | ------------------ | | TypeScript Support | ✅ (types included) | ✅ (built with TS) | | Basic Width Calculation | ✅ | ✅ | | ANSI Escape Handling | ✅ | ✅ | | Emoji Support | ✅ | ✅ Enhanced | | Detailed Analysis | ❌ | ✅ | | Multi-line Utilities | ❌ | ✅ | | String Manipulation | ❌ | ✅ | | Custom Width Mapping | ❌ | ✅ | | Unicode Normalization | ❌ | ✅ | | Zero Dependencies | ❌ | ✅ | | Modern ES Features | ❌ | ✅ |

🔧 Advanced Usage

Custom Width Mapping

import { stringWidth } from "string-width-ts";

// Define custom widths for specific characters
const customMap = new Map([
  [65, 3], // 'A' has width 3
  [0x1f600, 1], // 😀 has width 1 instead of 2
]);

stringWidth("A😀B", { customWidthMap: customMap }); // 5 (3+1+1)

Unicode Normalization

import { stringWidth } from "string-width-ts";

const str1 = "é"; // Precomposed
const str2 = "e\u0301"; // Decomposed (e + combining acute)

stringWidth(str1, { normalize: "NFD" }); // Normalize to decomposed
stringWidth(str2, { normalize: "NFC" }); // Normalize to composed

Working with Complex Emojis

import { getStringWidthInfo } from "string-width-ts";

// Family emoji with zero-width joiners
const family = "👨‍👩‍👧‍👦";
const info = getStringWidthInfo(family);

console.log(`Width: ${info.width}`); // 2
console.log(`Graphemes: ${info.graphemes}`); // 1
console.log(`Characters: ${info.characters}`); // 11

🧪 Testing

npm test        # Run all tests
npm run test:watch  # Run tests in watch mode

🏗 Building

npm run build   # Build TypeScript to JavaScript
npm run dev     # Build in watch mode

📋 Requirements

  • Node.js 16.0.0 or higher
  • TypeScript 5.0.0 or higher (for development)

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

📜 License

MIT © Your Name

🙏 Acknowledgments

  • Inspired by sindresorhus/string-width
  • Built with modern TypeScript and enhanced features
  • Thanks to the Unicode Consortium for character width specifications

📚 Related