Thirteen Rulers, One Column

A terminal is a grid of cells, and every aligned thing you have ever seen in one depends on two separate systems agreeing about how many cells a character takes. Neither system is in charge. Here are twelve shipped width tables and one running terminal, asked about every assigned Unicode character, and then the fonts, asked whether they can draw what the tables reserved.

Artificial Wasteland · 2 September 2026 · measured against Unicode 17.0.0

Loading the measurements…

Type into the box. Each row lays your text out on a column grid the way one width authority would, and the number on the right is how many columns it thinks you used. They are all reading the same string.

character by character
codepointnamecolumns

Two systems, no referee

A terminal draws text by reserving a whole number of fixed-width cells for each character and then painting a glyph into them. The reservation is arithmetic: a lookup in a width table that returns 0, 1 or 2. The painting is typography: a font supplies a glyph with an advance width measured in units of an em square. Alignment is the claim that those two numbers match.

Nothing checks that claim. The width table is chosen by the program printing the text, or by the terminal, or by the multiplexer between them, and there are usually several in the stack disagreeing quietly. The font is chosen by the reader, and when the reader's font has no glyph for a character the system substitutes another font that never agreed to anything. This page measures both halves.

Every table, every character

The panel is twelve width tables plus one running terminal. Two of the twelve are the same specification recipe resolved the two legal ways, and two more are the same Go library with one boolean flipped, which is itself the finding: the disagreement starts before you have left a single source. The other nine are the defaults people actually run.

Asked about all 0 assigned scalar values that are not surrogates, private use, or control characters, the twelve tables agree on 0 of them and split on 0, which is 0 of the assigned characters in Unicode.

That headline number is inflated by tables nobody would defend as current, so the same question gets asked of stricter and stricter panels. The last row is the one that matters: six implementations that all claim a recent Unicode, judged only on characters old enough that none of them can plead ignorance.

paneltablescharactersthey split on

So the floor is 0 characters out of 0, or 0, on which six current implementations disagree with nothing to blame it on. That is small. It is also not zero, and the next section is about which characters are in it.

Why they split

characterscause

Those five buckets partition the 0 contested characters, in that order, so each character is counted once. Only the first is unfixable: for the 0 characters whose East Asian Width property is Ambiguous, Unicode Standard Annex #11 says in as many words that the width depends on the context the text is displayed in. Both readings are conformant. No amount of care inside any single program settles it, because the question was never given an answer to find.

The characters that exist only to be lined up

Box Drawing is a Unicode block of 128 characters whose entire purpose is to be laid out in a grid. Corners, tees, crossings, the light and heavy and double rules that every command line tool draws its tables with.

0 of those 128 have no agreed width, and every one of those is in the Ambiguous class. The characters invented so that terminals could draw aligned boxes are, by the standard's own definition, of undefined column width. Green below is agreed, red is disputed; click any character to load it into the bench.

blockrangeassigneddisputedAmbiguous class

Braille Patterns is the one clean block, all 256 agreed at one column, which is why the spinner in your package manager is usually made of Braille dots. It is the only graphical block in this list that every width table in the panel treats identically. That is not an accident of taste; it is a block that arrived after the Ambiguous mess and was given a definite width.

What year is each table living in

A width table is generated once from a Unicode release and then it ages. You can see how much without being told: for each Unicode release, ask what fraction of the characters that release introduced this table still gets right, and read the row. Green is agreement with the current specification, red is not. The Ambiguous class is excluded, because resolving it the other way is a choice rather than an age.

Some tables fall off a cliff, which is what a table generated once and left alone looks like from outside:

A cliff here means the last release where the table matched the specification on more than 90% of that release's new characters, followed immediately by one where it matched on under 10%, counting only releases that introduced at least fifty characters. The tables without a cliff are not necessarily current; several are simply ragged.

One package, one million characters, one wrong unit

The npm package wcwidth is a JavaScript port of Markus Kuhn's C reference implementation, the ancestor of most of the tables above. It was downloaded 50,020,020 times in the week ending 29 August 2026.

It reports every single one of the 1,048,576 codepoints above U+FFFF as two columns wide. Not the East Asian ones. All of them. Deseret, Linear B, Gothic, Old Italic, the mathematical alphanumerics, the playing cards, every emoji and every character in every script Unicode has added to the supplementary planes since 1999.

The cause is visible in eight lines of its own source. Its string function loops over str.charCodeAt(i), which walks UTF-16 code units, not characters. An astral character arrives as a surrogate pair, each half falls through the table to the default of 1, and the sum is 2. It is not a width table saying "wide". It is a string length in the wrong unit wearing a width table's name. In the census above this accounts for 0 of the contested characters, filed separately from real disagreement because it is not one.

The terminal's answer is its C library's answer

The thirteenth row of the bench is not a table. Each character was written to the tty of a live tmux pane and the cursor column read back, so it reports what a terminal did rather than what a library thinks. That makes it possible to ask a question the tables cannot answer about themselves: where does a running terminal get its widths?

Compare its readings to each table, over only the characters that table will answer about at all.

tablecharacters it will answer aboutthe terminal differed on

One row is not like the others. Over the 0 characters this machine's C library has an opinion about, the terminal gave a different answer 0 times. Not thousands. Here they all are, and they are all pictographs that the terminal widens to two columns and the C library still calls one.

codepointnameglibcthe terminal

Which leaves the characters the C library has never heard of, because it was built against an older Unicode. There are 0 of those in the probed set, and the terminal gave every one of them the same answer: 1 column. For 0 of them the current specification says two. Those are not exotic: 0 of them are characters Unicode 0 added as Wide, which this terminal is quietly giving one column each.

So the width of your terminal is a property of your C library, not of your terminal. Upgrading the terminal will not move these numbers; upgrading libc will. That is not written down anywhere as far as this page's author could find, and it is measurable in about a minute with the script in the research directory.

And a tab is not a width

The probe wrote the control characters too. Sixty-four of the sixty-five left the cursor in column zero. The exception is U+0009, which moved it eight columns, because a tab is not a character with a width, it is an instruction to go somewhere. Every table in the panel answers 0 or refuses. The terminal answers 8, and the terminal is right, and the question was wrong. This is why the census excludes the control characters from the width comparison.

What a table cannot answer at all

Every table above answers about one codepoint. People do not type codepoints. A family emoji is seven codepoints and one character; a flag is a base and five invisible tag characters; a skin-toned waving hand is two. Unicode publishes a list of the 0 fully-qualified emoji sequences it recommends for general interchange, and every one of them is meant to occupy two columns.

Summing a per-codepoint table over a sequence gets this wrong most of the time, which is why the good libraries stopped summing. Below, the four instruments that take a whole string rather than a character, including a real terminal that had to decide rather than compute.

instrumentgave 2 columnsofwhat it gave instead

tmux 3.4, asked by writing each sequence into a live pane and reading the cursor column back, moved the cursor by something other than two columns on 0 of them, which is 0 of the emoji Unicode currently recommends. Here are 0 of them, the number after each being the columns the terminal actually moved. Click one to put it on the bench.

The other half: the fonts were never asked

Suppose every table above agreed. The terminal reserves two cells for an ideograph, and now a font has to fill them. Two cells is twice the advance width of the letter A in whatever monospace font the reader chose. An ideograph, in every CJK font measured here and as drawn by the browser, is exactly one em.

Those two quantities have no reason to be equal, and mostly are not.

monospace fonttwo cells, in emsan ideograph fillsan emoji fills

Only a font whose cell is exactly half an em makes two cells exactly one em. Of the 0 faces in this table that anyone actually sets a terminal to, exactly 0 is: . In every other one, an ideograph is narrower than the space reserved for it and an emoji is wider, in the same terminal, on the same line, with the same width table saying 2 for both. The two faces at the bottom of the table that do hit one em exactly are GNU Unifont, which was designed as a dual-width grid font, and a CJK face, which nobody codes in.

It gets worse when you ask what the monospace font actually contains. These are the font files read directly: the advance width per glyph out of hmtx, the character-to-glyph map out of cmap, one cell defined as the advance of U+0041.

fontcell, in emsclaims fixed pitchcharacters at two cellsoff gridbox drawingbraille

All 0 of the terminal faces have not one single glyph two cells wide. They do not contain the characters the width tables have been arguing about. When one appears, the system goes and finds it in another font, and that font's glyph is whatever width it happens to be.

Two smaller things fall out of the same table. contain none of the 128 box drawing characters at all, and contain well under half, so the aligned tables they appear to draw are drawn by a substitute font. And 0 of them, , report isFixedPitch as false in their post table, which is a monospace font declaring in its own metadata that it is not one.

Your machine, not this one

The fonts above are the ones this study had. Yours are different, so here is the same measurement made in your browser, just now, on the stacks your system may or may not resolve. A cell is the advance of A; the other columns are how many cells your browser gives each character.

font stack requestedcell, px at 100px😀

The check

What this does not show

The shape of it

The received account of this problem is that emoji broke terminals. The measurement says something narrower and stranger. The characters Unicode invented for drawing aligned grids were given a width property that explicitly means "it depends". The libraries that resolve it are internally consistent and mutually incompatible, and several of them ship a switch so you can pick. The one reference implementation most of them descend from, ported to JavaScript, silently swapped the unit of measurement. And underneath all of it, the fonts that were supposed to fill the cells do not contain the characters in question at all, so the glyph that arrives comes from a font that never agreed to the cell size, and misses it by anything from a fifth too narrow to a quarter too wide.

An aligned terminal is not a solved problem with some edge cases. It is a coincidence that holds for ASCII, and everything past ASCII is two independent systems being asked to agree with no mechanism for doing so.