Skip to content
← ALL EXPERIMENTS

14 / CSS / 2026-04-11

Bar Chart Toy

Edit rows, ASCII bars update live.

How it works

// 01 DPI and pixel density

The render loop does three things. First, it finds the max value across all rows, which becomes the scale denominator. Second, it calculates each row's bar width as (value / max) times (total chart width minus label width minus value width). Third, it constructs each bar string by flooring the bar width to get the count of full block characters, then appending a single partial-width character for the fractional remainder.

// 02 Bleed and cut tolerance

Partial-width characters come from the Unicode block elements range: full block (U+2588), seven-eighths (U+2589), three-quarters (U+258A), five-eighths (U+258B), half (U+258C), three-eighths (U+258D), quarter (U+258E), eighth (U+258F). Mapping the fractional remainder to one of these eight characters gives the bar approximately one-eighth-character resolution at its right edge. The result looks smooth despite being built from text.

// 03 Safe zone and trim accuracy

Labels left-align to a fixed width (padded with spaces) and values right-align after the bar. Every row ends up the same total character width, which is what gives the chart its clean column structure. The whole chart is built in a single template string, which makes it trivial to copy as text or render inside a pre-wrap element.

OBJECT / bars.asciiCSS
......
......

// why this exists

data viz from scratch

A bar chart is the simplest piece of data visualization there is. Label on the left, value on the right, a horizontal bar sized to the value. The version in this widget renders those bars with text characters instead of SVG rectangles, because sometimes what you want is a chart that reads as content instead of as a graphic. Edit a row in the table, watch the ASCII bars update on every keystroke.

Terminal-style ASCII charts belong to a specific visual vocabulary: monospaced, text-first, markdown-compatible, zero dependency. They show up in build logs, CLI output, GitHub readmes, and the kind of internal dashboards that live inside a Slack message. Building one in the browser lets you prototype the content before you wire up a real charting library, or it lets you skip the library entirely because the ASCII version is what you actually want in the final output.

The widget accepts up to fifteen rows. Each row has a label (arbitrary text) and a value (a positive number). The bar for each row is a string of block characters (full block, seven-eighths, three-quarters, half, quarter, and so on) whose total width represents the row's value scaled to the largest value in the set. Because the bar uses partial-width characters at the end, it can represent values with sub-character resolution instead of quantizing to the nearest whole character.

Copy the rendered ASCII with one click. Paste it into a readme, a blog post, a Slack message, or a CLI tool's output. It works wherever a monospaced font is rendered, which is everywhere. No image hosting, no embed code, no JavaScript dependency on the receiving page. It is text. It renders instantly and ages well.

I use this for internal reporting and for the occasional content piece where a real chart would feel like overreach. It is also a good teaching artifact for anyone learning data viz, because it strips the work down to the essentials: scale a number to a length, draw the length, label the row.

Frequently asked questions

Why ASCII instead of a real chart library?

ASCII charts are text. They render in readmes, Slack, CLI output, and email without any dependency. For small data sets and fast communication, they are often better than a rendered SVG.

What are Unicode block characters?

A set of characters in the Unicode U+2580 to U+259F range that render as filled rectangles of various widths and heights. They are the alphabet of ASCII bar charts.

How many rows can this handle?

The widget caps at 15 rows because beyond that the labels get hard to scan in a single column. For a larger dataset, switch to a tool that does ranking and pagination.

Can I sort the bars by value?

Yes, with the sort toggle. Ascending, descending, or input order, which matters when the row order has meaning (like a month-over-month series).

What font renders block characters correctly?

Any monospace font shipped in the last decade. DM Mono, JetBrains Mono, Menlo, Consolas, SF Mono, and the default monospace stack all support the full block elements range.

Why do some of my copied charts look different in Slack?

Slack normalizes some Unicode characters and strips consecutive spaces in certain contexts. Use the Slack-compatible export, which substitutes plain pipe and dash characters for the block elements.

How do I embed this in a blog post?

Copy the ASCII output, paste it inside a `<pre>` block in your markdown or MDX. The monospace font will take care of alignment. That is the entire integration.

Can I add colors?

Terminal ANSI colors can be injected as escape sequences if your target renders them. For browser use, wrap each bar character in a span with a CSS color.

What happens if I have a negative value?

The widget ignores negative values because a horizontal ASCII bar chart cannot express them cleanly. For negative data, use a diverging chart variant or convert to absolute values with a sign column.