string-width: counting terminal columns, not characters
Get the visual width of a string - the number of columns required to display it
At a glance
- What is it?
- string-width answers a question that looks trivial and is not: how many columns does a string occupy when a terminal draws it. Fullwidth characters take two, ANSI escape sequences take none, and the ambiguous-width characters in East Asian text are the hard case, because whether they count as one column or two depends on context the library does not have. It resolves that by defaulting to narrow, and it is a two-dependency package that many CLI tools depend on without knowing it.
- Who is it for?
- Adopt string-width if you render text into a fixed-width layout, a table, a progress bar or a spinner, because reimplementing East Asian width and ANSI stripping is a specification-reading exercise that every CLI author should not have to repeat.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 29 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem, and why length is not the answer
string-width gets the visual width of a string, the number of columns required to display it.
That sounds like a job for a length function, and for ASCII it is exactly that. The reason it needs a package is that a string of N characters can occupy a number of columns different from N.
Some Unicode characters are fullwidth and use double the normal width, so a single CJK ideograph occupies two terminal columns even though it is one JavaScript string element. And ANSI escape codes are stripped and do not affect the width, which matters because a coloured terminal output string contains bytes that draw nothing at all.
The stated use is to be able to measure the actual width of command-line output, and that is the whole job. It is a primitive that a table renderer, a progress bar, a spinner or a wrapping function needs and that nobody should reimplement, because getting the East Asian width rules right is a large amount of specification-reading for a small result.
The primary language is JavaScript, the licence is MIT, the default branch is main, and the recent releases are v8.3.0 from 2026-09-24, v8.2.2 from 2026-07-08 and v8.2.1 from 2026-04-27. The last push was on 2026-09-24 with the project not archived.
The version number is at 8, which for a package this small is a signal worth pausing on. It is a mature, widely depended-upon package, and the release numbering is a way of communicating that the API has been stable for a long time and that changes are treated as breaking.
Usage, and the three cases that define it
The usage section is three examples, and between them they cover everything the package does.
import stringWidth from 'string-width';
stringWidth('a');
//=> 1The same calls with a fullwidth CJK ideograph and with that ideograph wrapped in ANSI bold escape sequences both return 2, which is the second and third cases in the README's usage section.
The first case, a plain ASCII letter, gives 1, which is the baseline. The second, a CJK ideograph, gives 2, which is the fullwidth rule. The third is the interesting one: the same ideograph wrapped in ANSI bold escape sequences, and the answer is still 2 rather than 2 plus the length of the escape codes.
That third example is the whole reason to use a package rather than writing a loop. The escape sequences carry a visible style change but occupy no columns, and the ideograph inside them occupies two. So a correct implementation has to strip the escapes and then count the remaining characters by their width class, and getting the strip right requires knowing where the escape sequence ends.
The package is ESM, and the import is a default import: import stringWidth from 'string-width'. The manifest sets type to module and the exports map has just a types entry and a default entry, so there is a single entry point with no subpath exports and nothing else to learn about.
The engines field says node >=20, and sideEffects is false, which lets a bundler drop the import entirely if the result is unused. For a package that is likely to end up in the dependency list of a dozen other CLI tools, both of those details matter, because the package is small enough that leaving it in the tree costs more than it should.
The ambiguous width question, and why the default is narrow
The options object has two settings, and one of them is where the real thought is.
The ambiguousIsNarrow option is a boolean defaulting to true, and it counts ambiguous width characters as having narrow width, a count of 1, instead of wide width, a count of 2. The reference is the Unicode report on East Asian Width, and the documentation quotes the rule that ambiguous characters behave like wide or narrow characters depending on the context, with the language tag, script identification, associated font, source of data, or explicit markup all able to provide that context. And the conclusion: if the context cannot be established reliably, they should be treated as narrow characters by default.
So the default is the conservative one, and the reasoning is that a library measuring a string has almost never got the context. A string might be Japanese text on a Japanese terminal, where the ambiguous characters are wide, or it might be the same characters in a European context, where they are narrow. Getting that right per call site would mean the caller had to declare the locale, and a default of wide would be wrong for every caller that did not.
The trade-off is real and it is the reason the option exists. If you are measuring Japanese text for a Japanese terminal, ambiguousIsNarrow false gives you the width that terminal will actually draw, and the cost is that you have decided to trust a context the library cannot verify.
The second option, countAnsiEscapeCodes, is a boolean defaulting to false, and it determines whether ANSI escape codes should be counted. Counting them is rarely what you want, since the point of the call is to reserve the right number of columns, but the option exists because sometimes you are measuring a string for a purpose where the raw byte count is the answer.
Notice that the ANSI handling is the opposite in spirit to the ambiguous handling. Escape codes are unambiguously zero-width for the purpose of drawing, so the default is to exclude them. Ambiguous characters are ambiguous, so the default is to pick the interpretation that under-counts rather than over-counts, because a table that overflows is worse than one that leaves a space.
The implementation, and the two dependencies
The repository is small and flat. There is index.js, index.d.ts, index.test-d.ts for the type tests, test.js for the runtime tests, readme.md, license, a .npmrc, an .editorconfig, .gitattributes and .github/. The package is published with only index.js and index.d.ts in the files list, so everything else in the repository is development-only.
The runtime dependencies are two:
"dependencies": {
"get-east-asian-width": "^1.5.0",
"strip-ansi": "^7.1.2"
},get-east-asian-width at about 1.5 determines the East Asian Width of a character, and strip-ansi at about 7.1 removes the escape sequences. So the package itself is the composition of two things it does not implement, plus the counting logic.
That is the right division of labour. The East Asian Width property is a large Unicode table with rules for combining characters and other special cases, and a maintained implementation of it is worth more than anything the author could write. Stripping ANSI escape codes correctly is also a small problem with a lot of edge cases, since the sequences come in several forms and an unterminated one has to be handled.
The development tooling follows the same minimal pattern:
"scripts": {
"test": "xo && ava && tsd"
},xo for linting, ava for the test runner and tsd for the type definitions. The three run in sequence, so a type error in index.d.ts fails the same way a runtime error does.
The keyword list in the manifest is unusually long and it is a good map of the problem domain: string, character, unicode, width, visual, column, columns, fullwidth, full-width, wcwidth, wcswidth, full, ansi, escape, codes, cli, command-line, terminal, console, cjk, chinese, japanese, korean, fixed-width and east-asian-width.
The presence of wcwidth and wcswidth in that list is the most informative part. Those are the names of the POSIX and C functions that do the same job in the terminal world, which tells you that the author was porting established behaviour rather than inventing a definition of width. And the presence of cjk, chinese, japanese and korean tells you the same.
The related packages, and how to evaluate the project
There are four related packages, and between them they show that this is a family of tools rather than a one-off.
string-width-cli is a CLI for this module, so you can measure a string from a shell script without writing any JavaScript. string-length gets the real length of a string, which is the count of code points rather than the display width, and the distinction matters for the same reason this package exists. widest-line gets the visual width of the widest line in a string, which is what you need when you are padding a block of text into a box. And get-east-asian-width determines the East Asian Width of a single Unicode character, which is the primitive this package uses internally and which is exposed separately for people who need the classification rather than a total.
So the ecosystem is: a low-level width classifier, a total-width function over a string, a maximum-width function over the lines of a string, and a command line front end. Four small packages, each doing one thing, all by the same author.
For an evaluation, four things decide it.
First, whether the default suits your case. The default of narrow for ambiguous characters is right for a library that has no context, and wrong for a table of Japanese text rendered in a Japanese terminal. If your use case is CJK, you need to set ambiguousIsNarrow to false and accept that you are now the one providing the context.
Second, the ANSI behaviour. The default of not counting escape sequences is right for measuring rendered output. If you are measuring a string that will be printed raw, for example to compute a buffer size, the countAnsiEscapeCodes option gives you the number you want instead.
Third, the version floor. The engines field requires Node 20 or newer and the package is ESM only, with a single default export and no CommonJS entry point. If your project is on an older Node or is still CommonJS, that is a hard stop rather than a preference, and the alternative is to stay on the older major of this package.
Fourth, what you are really depending on. Two dependencies is a small surface, but both of them are Unicode tables, and the behaviour of the package across Unicode versions is determined by when those tables were last updated. A string-width version bump is often a Unicode data bump, and that is a good reason to pin the version if your output alignment matters.
Editorial conclusion
Adopt string-width if you render text into a fixed-width layout, a table, a progress bar or a spinner, because reimplementing East Asian width and ANSI stripping is a specification-reading exercise that every CLI author should not have to repeat. Do not use its default for CJK-heavy text in a CJK terminal, because the ambiguous characters default to narrow and will be under-counted, so set ambiguousIsNarrow to false there and accept that you are then the one supplying the context the library cannot infer. Before you commit, check the Node 20 and ESM-only requirement, since the package is not usable unchanged on an older Node or in a CommonJS project, and pin the version if your output alignment matters, because a release of this package is often a Unicode data update underneath.
Frequently asked questions
What does the string-width package do?
It returns the visual width of a string, meaning the number of terminal columns required to display it. Fullwidth characters count as two columns, ANSI escape codes are stripped and do not affect the width, and the package exists to measure the actual width of command-line output.
How does string-width handle ambiguous width characters?
The ambiguousIsNarrow option controls it and defaults to true, counting ambiguous width characters as narrow, a count of 1, rather than wide, a count of 2. The documentation quotes the Unicode rule that such characters behave like wide or narrow depending on context, and that if the context cannot be established reliably they should be treated as narrow by default.
What are the string-width options?
There are two, both booleans. ambiguousIsNarrow defaults to true and controls whether ambiguous width characters count as one column or two. countAnsiEscapeCodes defaults to false and controls whether ANSI escape codes are counted in the width at all, which is usually what you do not want when reserving terminal columns.
What does string-width depend on?
Two runtime dependencies, get-east-asian-width at about 1.5 to determine the East Asian Width of a character and strip-ansi at about 7.1 to remove escape sequences. The development tooling is xo for linting, ava for the test runner and tsd for the type definition tests, and the three run in sequence as the test script.
What are the related packages to string-width?
string-width-cli is a command line front end for this module, string-length gets the real length of a string rather than its display width, widest-line gets the visual width of the widest line in a string, and get-east-asian-width determines the East Asian Width of a single character and is the primitive this package uses internally.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/sindresorhus-string-width)Was this useful?