Invisible nickname: a blank display name is not blank once it is drawn

Short answer: an invisible nickname is a display name with nothing readable in it, and the part that surprises people is not the name — it is what gets drawn from it. Measured in Chromium 153, the character placed in a 40 px avatar circle is 0.00 px wide for a zero-width space, 4.00 px for a no-break space and 16.00 px for a Hangul filler, against 11.00 px for the capital A of a real name — so the filler draws a wider blank than the letter it replaced. Four ordinary ways of asking “what is the first character” disagree about the same name, which means the avatar letter is a property of the function the platform called and not of the name. And position decides what changes: a character on the end leaves the avatar letter alone, and the end is where most people paste it.

Fill it with a character that is hard to see, then read the right-hand pane:

Then move it from one end to the other and watch which readings change:

Ada

    Type or paste a nickname above.

    Every row is read in your browser from your own text: the character rows are the four primitives a platform would have to call, the widths come from a measuring span, and the ink row is a canvas pixel count of the glyph.

    The avatar letter is whichever function the platform called

    An avatar made from a display name needs one character, and there is more than one way to ask for it. Four of them are ordinary: take the first grapheme with Intl.Segmenter, take charAt(0), uppercase the name and take the first character, or trim the name first and then take the first character. On an ASCII name all four return the same letter. On a name that starts with something invisible they do not, and the disagreement is not a rounding difference — it decides whether the circle gets a letter or a blank.

    NameFirst graphemecharAt(0)toUpperCase()[0]trim() then first
    AdaU+0041U+0041U+0041U+0041
    U+200B then AdaU+200BU+200BU+200BU+200B
    U+3164 then AdaU+3164U+3164U+3164U+3164
    U+00A0 then AdaU+00A0U+00A0U+00A0U+0041
    U+FEFF then AdaU+FEFFU+FEFFU+FEFFU+0041
    Ada then U+200BU+0041U+0041U+0041U+0041
    Ada then U+3164U+0041U+0041U+0041U+0041

    The two rows in the middle are the ones that bite. A no-break space and a byte order mark are both characters that a trim removes and that no uppercase step touches, so the same name has two possible avatars depending on the order the platform did things in: one that draws a blank circle, and one that draws the letter A. Neither implementation is broken, and nothing in the name says which one you will get. A service that renders a name on the client and matches it on the server can show one avatar and store the other.

    Notice also that the last two rows are identical to the first. A character on the end of the name does not reach the front of it, and every one of the four primitives reads from the front. That is worth stating plainly because the end of the name is the convenient place to paste something — it does not require moving the cursor past a word — and it is the position where the avatar is guaranteed to come out unchanged.

    What the avatar circle actually draws

    The circle is usually a fixed size with the character centred in it, so what the character occupies is a measurable width rather than a guess. Measured in Chromium at a 16 px font, the same character that decides the avatar draws a very different amount of the circle depending on which one it is.

    Character in the circleWidth it occupiesAgainst a real letter
    U+0041 (A, from Ada)11.00 pxthe reference
    U+200B zero width space0.00 pxa genuinely empty circle
    U+2060 word joiner0.00 pxa genuinely empty circle
    U+FEFF byte order mark0.00 pxa genuinely empty circle
    U+00A0 no-break space4.00 pxless than half a letter
    U+3164 Hangul filler16.00 pxwider than the letter it replaced

    The Hangul filler is the row that changes the picture, because it is the only one of the six that draws more than the letter. Its name says filler and its category is a letter, and the measurement agrees with the category rather than the name: it reserves a full em of horizontal space while painting nothing. So an avatar built from a name that starts with one is not an empty circle — it is a circle with a full-width blank pushed to one side of it, and in a small circle that blank is what the eye reads as the avatar being off-centre rather than missing.

    How much of the row a nickname takes

    The same character also decides how wide the name is wherever it is listed, and here the effect compounds, because a name can hold more than one. Measured in Chromium at 16 px, with a clean Ada as the baseline at 27.00 px of text width.

    NameGraphemesText widthAgainst Ada
    Ada327.00 pxbaseline
    U+200B then Ada427.00 px+0.00 px
    U+2060 then Ada427.00 px+0.00 px
    U+FEFF then Ada427.00 px+0.00 px
    U+00A0 then Ada431.00 px+4.00 px
    U+3164 then Ada443.00 px+16.00 px
    ten U+200B100.00 pxa row with no text in it
    ten U+316410160.00 pxabout six times the baseline

    Two rows at the bottom are the reason this is a layout problem rather than a curiosity. Ten zero-width spaces take up no width at all, so the name column collapses and the row reads as a blank line with an avatar beside it. Ten Hangul fillers take up 160.00 px, which in a narrow list is a row that pushes everything to its right — and each individual character is still invisible, so nothing on screen explains the width. The count of graphemes tells the same story from the other side: a name of ten fillers reports ten characters and shows none.

    Whether two blank names are the same name is a different question, and it is measured on the invisible username page, where a collator calls a name with a zero-width space appended equal to the name without it. What is on this page is the display side: what is drawn, how wide, and where.

    Why a character on the end changes none of it

    This is the part that makes the problem quiet. Every primitive that produces an avatar reads the name from the front, so a character appended to the end is not in any of them, and the avatar comes out exactly as it would for the clean name. Measured across four characters, the first character is U+0041 in every case, identical to a name that never had the character at all.

    Appended to AdaFirst graphemeText widthWhat changes
    U+200BU+004127.00 pxnothing visible at all
    U+3164U+004143.00 pxthe row width, not the avatar

    The second row is the split worth keeping: the avatar is untouched and the row is not. So the same character in the same name has an effect that depends entirely on which end it is on — in front of the name it takes over the avatar, behind the name it only widens the row. A rule of thumb that says “an invisible character in a nickname is harmless” is describing the appended case and missing the prepended one, and the appended case is the easier one to produce.

    What this page does not tell you

    What any particular platform does. The measurements above are of the primitives that every platform has to use, run in Chromium 153 and Node 22.22.2, and no real service was queried. A platform that stores a nickname and draws it later can apply any of the four primitives, or its own, or none; a platform that draws the avatar from the profile image instead never reaches the name at all. Which one a given service does is not measured here and cannot be from the outside.

    The characters are six of the many that render as nothing, chosen because they behave differently from each other rather than because they are the complete set. The widths are from one font stack at one size on one build; the ratios between the six are what the table is about, and the exact hundredths will move with the font. Grapheme counts and code point counts come from Intl.Segmenter with Unicode 17.0 as shipped in Node 22.22.2 and Chromium 153, and a different Unicode version can split a cluster differently.