Skip to main content
Best Emojis

Home > Blog > Tutorials

Tutorials
braille art
dot art
text art

How Braille Dot Art Works (and Why It Breaks When You Paste It)

By BestEmojis Team·October 22, 2026·10 min read
How Braille Dot Art Works (and Why It Breaks When You Paste It)

Braille dot art works because every character in Unicode's Braille Patterns block is a tiny 2 by 4 bitmap. The block runs from U+2800 to U+28FF, exactly 256 characters, one for every possible combination of eight dots. A converter shrinks your picture, decides which pixels count as dark, and spells each 2 by 4 patch as the braille character with those dots raised. It breaks on paste for three main reasons: fonts that give braille a different width from spaces, apps that wrap long lines, and dark mode flipping what counts as ink.

Below we walk through the exact algorithm our dot art generator runs, using output we produced by running its own conversion code on simple shapes, and then go through each way a pasted piece falls apart, with measurements where we could take them.

One character, eight pixels: how the dots map to bits

Unicode added the Braille Patterns block in version 3.0, in 1999. Each of the eight dot positions owns one bit, and the character's code point is U+2800 plus the sum of the raised dots' bits. Dot numbering follows braille tradition rather than reading order, which trips up nearly everyone the first time:

  • Dots 1, 2 and 3 run down the left column (bits 1, 2 and 4).
  • Dots 4, 5 and 6 run down the right column (bits 8, 16 and 32).
  • Dot 7 is bottom left (bit 64) and dot 8 is bottom right (bit 128).

The odd ordering exists because six-dot braille came first. Dots 7 and 8 were added underneath later for eight-dot computer braille, so they got the next two numbers instead of slotting into the columns.

Two worked examples. ⢠ is U+28A0. Subtract U+2800 and you have 0xA0, which is 128 plus 32: dot 8 and dot 6, both in the right column, at the bottom two rows. ⣿ is U+28FF, all eight bits set, a solid block. And U+2800 itself, with no bits set, is BRAILLE PATTERN BLANK, the empty cell that makes up every blank area of a dot art piece.

The conversion, step by step

This is what the code in our generator does, in order. Nothing here is a simplification; these are the actual steps and default values.

  1. Resize. You pick a width in characters, 40 by default. The image is scaled to twice that many pixels across, because each character holds two columns. Height keeps the aspect ratio and is rounded to a whole number of 4-pixel rows.
  2. Convert to grey. Each pixel becomes a brightness from 0 (black) to 1 (white) using the weights 0.2126 red, 0.7152 green and 0.0722 blue. Green dominates because the eye is most sensitive to it.
  3. Invert, if asked. With invert on, every value becomes 1 minus itself before anything else happens, so the rest of the code can always treat dark as ink.
  4. Threshold. A pixel darker than the threshold becomes a raised dot. The default is 50%.
  5. Dither, if asked. With dithering on, each pixel's rounding error is passed to its unvisited neighbours using Floyd-Steinberg weights: 7/16 to the right, 3/16 below left, 5/16 directly below and 1/16 below right. The scan goes left to right along every row.
  6. Pack. Every 2 by 4 block of ink pixels is added up into its bit value and written as one character. Rows of characters are joined with line breaks.
  7. Swap blanks, if asked. Optionally, every U+2800 is replaced by ⠄ (U+2804, a single dot 3) or by an ordinary space. The default keeps U+2800.

The built-in sample shapes skip the first two steps. They are drawn from maths rather than pixels, with each pixel sampled nine times on a 3 by 3 grid and averaged, so edges come out as soft greys rather than jagged steps.

The smallest possible example: a diagonal line

We fed the converter an 8 by 8 white square with a single black diagonal running from the top-left corner. Eight pixels of ink, threshold 50%, no dithering. It became two lines of four characters:

⠑⢄⠀⠀
⠀⠀⠑⢄

U+2811 U+2884 U+2800 U+2800
U+2800 U+2800 U+2811 U+2884

Read the first character. ⠑ is U+2811, bits 1 and 16, which is dot 1 (top left) and dot 5 (second row, right): the line's first two pixels, stepping down and across. ⢄ is U+2884, bits 4 and 128: dot 3 (third row, left) and dot 8 (bottom right), the next two pixels. The line then drops into the second row of characters and the pattern repeats. Half of the eight cells are blank, and that ratio matters more than it looks, as the paste problems below show.

A heart at 12 characters wide

Here is the converter's built-in heart, 12 characters wide, straight threshold at 50%:

⠀⢀⣠⣤⣤⡀⢀⣤⣤⣄⡀⠀
⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄
⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇
⠀⢻⣿⣿⣿⣿⣿⣿⣿⣿⡟⠀
⠀⠀⠙⢿⣿⣿⣿⣿⡿⠋⠀⠀
⠀⠀⠀⠀⠉⠻⠟⠉⠀⠀⠀⠀

That is 6 lines and 72 cells, which hold 576 possible dot positions. 340 of them are raised. 16 cells are U+2800 blanks, all of them in the margins around the shape. Look at the left edge of row two: ⢠ is the dots-6-and-8 character from earlier, the soft curve of the heart's shoulder captured in just two pixels.

Copied with its line breaks, the piece is 77 characters long. Our generator counts it as 149 on X, because X's open-source twitter-text configuration (version 3) weighs any character outside a few early Unicode ranges as two, and braille sits outside all of them. 72 cells times two plus 5 line breaks makes 149.

Threshold versus dithering on a shaded ball

Flat shapes like the heart barely need dithering. Shading does. The converter's shaded ball is lit from the upper left, so its brightness runs from near white to near black. At 50% with no dithering, only the shadow side survives:

⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣆⠀
⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⡆
⠀⠀⠀⠀⠀⠀⣠⣴⣿⣿⣿⠇
⠀⠱⣶⣶⣶⣿⣿⣿⣿⣿⠟⠀
⠀⠀⠈⠛⠻⠿⠿⠟⠛⠁⠀⠀

The bright half of the ball is above the threshold, so it simply disappears, and what is left reads as a crescent. Turn dithering on at the same threshold and the ball comes back as dot density:

⠀⠀⠀⠀⠀⠠⠐⢠⠠⡀⠀⠀
⠀⠀⠀⠀⠀⠐⢈⢐⢱⢱⣆⠀
⢀⠐⠀⠄⡈⠄⢅⢢⢣⡳⣽⡆
⠐⡌⢌⠔⡠⣑⢅⢧⣳⣻⣟⠇
⠀⠪⣎⢮⣪⢮⣞⣷⣟⣯⠏⠀
⠀⠀⠈⠙⠛⠿⠯⠛⠋⠁⠀⠀

We ran the ball at three thresholds, with and without dithering, and counted raised dots out of the 576 available:

  • Threshold 30%: 106 dots plain, 178 dithered.
  • Threshold 50%: 170 dots plain, 181 dithered.
  • Threshold 70%: 248 dots plain, 185 dithered.

The plain counts swing by more than double across the three settings. The dithered counts barely move, 178 to 185, because error diffusion makes the overall dot density track the image's average darkness whatever threshold you set. That is the practical rule. With dithering off, threshold is your main control. With it on, threshold matters far less and the source image's contrast does most of the work.

Why the picture skews in some apps: font width

Dot art only holds its shape if every cell takes the same horizontal space. We checked the fonts installed on a Mac running macOS 27.0.1, reading each font file's own width tables:

  • Helvetica, Arial, Arial Unicode MS, Times New Roman, Menlo, Courier New, the system font SF and SF Mono contain no braille characters at all. Any braille shown in those fonts is borrowed from another font.
  • The fonts that do contain it are the Apple Braille family (five files) and Apple Symbols. Each carries all 256 patterns, and every pattern is exactly 0.684 em wide.
  • A space is 0.205 em in the system font, 0.278 em in Helvetica and Arial, and 0.602 em in Menlo.

So within pure braille, cells line up, because they all share one width. Mix in an ordinary space and the line goes short: one braille cell is about 3.3 system-font spaces. Even a monospace code font does not save you here. 12 braille cells borrowed from Apple Braille come to 8.2 em, while 12 spaces in Menlo come to 7.2 em, so a row that uses spaces for its margin drifts left against the rows around it.

That is why the U+2800 blank is so valuable. It is a full-width braille cell that happens to have no dots, so blank areas stay the same width as inked ones. We only measured macOS; Windows, Android and other systems ship different fonts, and the right test is to paste a piece and compare the edges of each row.

Why the picture collapses: trimming and whitespace

Ordinary spaces are fragile in web pages. The W3C's CSS Text Module Level 3 (Candidate Recommendation Draft, 14 August 2026) applies white space processing to spaces, tabs and line breaks, and under the default setting runs of spaces collapse and spaces at the ends of lines are removed. A dot art piece that uses spaces for its margins loses them the moment it lands in normal page text.

U+2800 escapes that rule because it is not whitespace. Unicode gives it the general category So, an "other symbol", like ♥. We tested the standard tools: JavaScript's trim() leaves it in place, the regex \s does not match it, and Python's isspace() returns False. Compare U+3000, the ideographic space, which \s does match.

So when an app does remove U+2800, it is making a deliberate choice rather than applying a standard trim. If your piece arrives with its left margin gone or its first row missing, that is the likely cause, and the generator's option to swap blanks for ⠄ is the workaround: a single low dot is never treated as empty.

Why it wraps into a mess: line length

Each line of dot art is one unbroken word as far as a layout engine is concerned. When the line is wider than the chat bubble or text box, the engine wraps it, and the right side of every row drops onto the next line, interleaving two halves of the picture.

You cannot control the reader's screen width, but you can measure your own. Before posting, paste the piece into the app and look at the longest row. If it wraps on your phone in portrait, it will wrap for many readers. Going narrower costs detail, but a 20 to 30 character piece that holds together beats a 60 character piece that shatters.

Why it looks like a negative: dark mode

The dots are drawn in the text colour. On a white background that colour is dark, so raised dots are ink and blank cells are paper, exactly as the converter intended. In dark mode the text colour is light, so the raised dots glow against a dark background and the picture inverts.

Shapes with a clear outline survive this; a heart reads as a heart either way. Shaded art does not, because highlights become shadows. If you know your audience mostly uses a dark theme, run the converter with invert on. This is the same heart, inverted:

⣿⡿⠟⠛⠛⢿⡿⠛⠛⠻⢿⣿
⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻
⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸
⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿
⣿⣿⣦⡀⠀⠀⠀⠀⢀⣴⣿⣿
⣿⣿⣿⣿⣶⣄⣠⣶⣿⣿⣿⣿

On a light background that looks like a heart-shaped hole in a block. On a dark one, it reads as a lit heart. The two versions are exact complements: 340 dots in the original, 236 in the inverse, 576 together.

Screen readers, braille displays and when that matters

Dot art is a picture made of characters, and assistive technology sees only the characters. Each cell has its own Unicode name, so the 12-wide heart is 72 separate symbols with names like BRAILLE PATTERN DOTS-1258. How much of that a screen reader announces varies, but none of it conveys "heart". Our post on how screen readers read emoji covers the same problem for emoji rows in bios.

It is also not readable braille. Literary braille uses six-dot cells, and the patterns stand for letters and contractions defined by each language's braille code. Dot art uses all eight dots as pixels and ignores those meanings, so on a refreshable braille display it is a run of patterns with no words in it. Unicode's braille block is the character set both uses share, nothing more. If you want individual braille characters to copy, the braille symbols set lists them one at a time.

When does it matter? Most in places people have to get through to reach your words: usernames, bios, the top of a post. A single decorative piece at the end of a message costs a listener little. A username made of braille cells makes you hard to mention or find. Put a short caption before the art and leave names in plain letters; our guide to fancy text usernames makes a similar case for styled letters.

Braille art compared with other text art

The 308 pieces in our text art collection use no braille at all. They are built from punctuation, box drawing and letters, such as the faces in lenny faces and the shapes in heart text art and cat text art. That style has opposite strengths: a lenny face is short, survives proportional fonts reasonably well, and can be typed. Braille art carries far more detail per character but only exists as converter output.

Kaomoji are closer to the lenny style, as explained in our kaomoji guide, and the text hub shows how all of these families relate. If you want colour rather than dots, the emoji pixel art maker works on the same grid idea with emoji as pixels, and fancy text covers styled letters. For dot art itself, the quickest way to learn the controls is to run the heart and the ball through the generator at a few thresholds and watch the counts change.

Frequently Asked Questions

How many braille characters does Unicode have?+

Exactly 256, in the Braille Patterns block from U+2800 to U+28FF, added in Unicode 3.0 in 1999. That covers every on and off combination of eight dots, so dot art has a character for any 2 by 4 patch of pixels.

Why is U+2800 used instead of a space in dot art?+

U+2800 is a braille cell with no dots, so it has the same width as every other cell and keeps rows aligned. It is classed as a symbol, not whitespace, so standard trimming leaves it alone, while runs of ordinary spaces collapse in web pages.

What threshold should I use for dot art?+

Start at 50%. Without dithering, lower it to keep only strong outlines and raise it to pull in faint detail. With dithering on, our tests on a shaded ball showed dot counts of 178 to 185 across thresholds of 30% to 70%, so threshold matters much less.

Why does my dot art look inverted in dark mode?+

The dots are drawn in the text colour. In dark mode that colour is light, so raised dots look bright and blank cells look dark, flipping the picture. Convert with invert switched on if most of your readers use a dark theme.

Can blind people read braille dot art?+

No. It uses braille characters as pixels, so on a braille display it is a string of meaningless patterns, and a screen reader meets dozens of separate symbol names. Add a short caption such as dot art of a heart before the piece.

Why does my dot art skew when there are spaces in it?+

Braille cells and spaces have different widths. On the Mac we measured, every braille pattern is 0.684 em wide while a space is 0.205 em in the system font and 0.602 em in Menlo, so any row padded with spaces ends up shorter than its neighbours.

About BestEmojis Team

The Best Emojis team is dedicated to helping you find the perfect emojis for every occasion. We provide comprehensive guides, tips, and a vast collection of free emojis for all your communication needs.

More from Our Blog