SymbolFYI

Box Drawing Characters: Building Text-Based UI with Unicode

Long before CSS existed, programmers were drawing boxes and tables with characters. The box drawing tradition stretches from CP437 (the DOS character set), through DEC VT100 terminals, to the Unicode standard that preserves and extends it today. These characters remain genuinely useful for terminal output, documentation formatting, code comments, ASCII art diagrams, and any context where you need structured visual layout using only text.

The Box Drawing Block (U+2500–U+257F)

Unicode's Box Drawing block contains 128 characters covering every combination of line direction, thickness, and double-line variant. The block is logically organized:

Light Single Lines

The four directional segments form the foundation. Combine them to build any box:

Character Code Point Description
U+2500 BOX DRAWINGS LIGHT HORIZONTAL
U+2502 BOX DRAWINGS LIGHT VERTICAL
U+250C BOX DRAWINGS LIGHT DOWN AND RIGHT
U+2510 BOX DRAWINGS LIGHT DOWN AND LEFT
U+2514 BOX DRAWINGS LIGHT UP AND RIGHT
U+2518 BOX DRAWINGS LIGHT UP AND LEFT
U+251C BOX DRAWINGS LIGHT VERTICAL AND RIGHT
U+2524 BOX DRAWINGS LIGHT VERTICAL AND LEFT
U+252C BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
U+2534 BOX DRAWINGS LIGHT UP AND HORIZONTAL
U+253C BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL

A complete light-line box:

┌──────────────┐
│  Content     │
│  goes here   │
└──────────────┘

Heavy Single Lines

Heavy variants are the same structure but thicker:

Character Code Point Description
U+2501 BOX DRAWINGS HEAVY HORIZONTAL
U+2503 BOX DRAWINGS HEAVY VERTICAL
U+250F BOX DRAWINGS HEAVY DOWN AND RIGHT
U+2513 BOX DRAWINGS HEAVY DOWN AND LEFT
U+2517 BOX DRAWINGS HEAVY UP AND RIGHT
U+251B BOX DRAWINGS HEAVY UP AND LEFT
U+2523 BOX DRAWINGS HEAVY VERTICAL AND RIGHT
U+252B BOX DRAWINGS HEAVY VERTICAL AND LEFT
U+2533 BOX DRAWINGS HEAVY DOWN AND HORIZONTAL
U+253B BOX DRAWINGS HEAVY UP AND HORIZONTAL
U+254B BOX DRAWINGS HEAVY VERTICAL AND HORIZONTAL

A heavy-line box (good for section headers):

┏━━━━━━━━━━━━━━┓
┃  Section     ┃
┗━━━━━━━━━━━━━━┛

Double Lines

Double-line characters are the classic DOS/ANSI art style:

Character Code Point Description
U+2550 BOX DRAWINGS DOUBLE HORIZONTAL
U+2551 BOX DRAWINGS DOUBLE VERTICAL
U+2554 BOX DRAWINGS DOUBLE DOWN AND RIGHT
U+2557 BOX DRAWINGS DOUBLE DOWN AND LEFT
U+255A BOX DRAWINGS DOUBLE UP AND RIGHT
U+255D BOX DRAWINGS DOUBLE UP AND LEFT
U+2560 BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
U+2563 BOX DRAWINGS DOUBLE VERTICAL AND LEFT
U+2566 BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
U+2569 BOX DRAWINGS DOUBLE UP AND HORIZONTAL
U+256C BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL

A classic double-line dialog box:

╔══════════════╗
║  Warning     ║
╠══════════════╣
║ Are you sure?║
╚══════════════╝

Mixed Light/Heavy and Mixed Single/Double

The block includes corners and junctions for mixed-weight lines, allowing hierarchical visual structure:

Character Code Point Description
U+2552 BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE
U+2553 BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE
U+256A BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL DOUBLE
U+256B BOX DRAWINGS LIGHT VERTICAL DOUBLE AND HORIZONTAL SINGLE

Mixed-weight tables use these for header/body dividers:

╒══════╤═══════╕
│ Name │ Score │
╞══════╪═══════╡
│ Alice│   100 │
│ Bob  │    98 │
╘══════╧═══════╛

Dashed Lines

For lighter visual structures:

Character Code Point Description
U+254C BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL
U+254D BOX DRAWINGS HEAVY DOUBLE DASH HORIZONTAL
U+254E BOX DRAWINGS LIGHT DOUBLE DASH VERTICAL
U+254F BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL
U+2504 BOX DRAWINGS LIGHT TRIPLE DASH HORIZONTAL
U+2505 BOX DRAWINGS HEAVY TRIPLE DASH HORIZONTAL
U+2508 BOX DRAWINGS LIGHT QUADRUPLE DASH HORIZONTAL
U+2509 BOX DRAWINGS HEAVY QUADRUPLE DASH HORIZONTAL

The Block Elements Block (U+2580–U+259F)

Block elements are eight-directional partial-fill characters that enable pixel-art-style shading and progress bars using only text:

Character Code Point Description Fill
U+2580 UPPER HALF BLOCK Top 50%
U+2584 LOWER HALF BLOCK Bottom 50%
U+2588 FULL BLOCK 100%
U+2591 LIGHT SHADE ~25%
U+2592 MEDIUM SHADE ~50%
U+2593 DARK SHADE ~75%
U+258C LEFT HALF BLOCK Left 50%
U+2590 RIGHT HALF BLOCK Right 50%
U+2589 LEFT SEVEN EIGHTHS BLOCK 87.5%
U+258A LEFT THREE QUARTERS BLOCK 75%
U+258B LEFT FIVE EIGHTHS BLOCK 62.5%
U+258D LEFT THREE EIGHTHS BLOCK 37.5%
U+258E LEFT ONE QUARTER BLOCK 25%
U+258F LEFT ONE EIGHTH BLOCK 12.5%

These enable smooth progress bars and bar charts:

Progress: [████████░░░░░░░░] 50%
CPU:      [▓▓▓▓▓▓▓▓▒▒▒▒░░░░] 62%
Memory:   [█████████████░░░] 81%

Practical Examples

File Tree / Directory Listing

The most common non-table use of box drawing characters is a file tree. Most CLI tools (like tree on Linux/macOS) use this pattern:

project/
├── src/
   ├── components/
      ├── Button.tsx
      └── Input.tsx
   ├── pages/
      ├── index.tsx
      └── about.tsx
   └── utils/
       └── helpers.ts
├── public/
   └── favicon.ico
├── package.json
└── README.md

The characters used are ├ (U+251C), └ (U+2514), │ (U+2502), and ─ (U+2500). This pattern is ubiquitous in documentation and README files.

Data Tables in Plain Text

┌─────────────┬──────────┬──────────┐
│ Feature     │ Free     │ Pro      │
├─────────────┼──────────┼──────────┤
│ Projects    │ 3        │ Unlimited│
│ Storage     │ 5 GB     │ 100 GB   │
│ API calls   │ 1,000/mo │ 50,000/mo│
│ Support     │ Community│ Priority │
└─────────────┴──────────┴──────────┘

Flowcharts and State Diagrams in Code Comments

  ┌─────────┐     ┌──────────┐     ┌─────────┐
  │ Request │────▶│ Validate │────▶│ Process │
  └─────────┘     └──────────┘     └─────────┘
                       │                │
                       ▼                ▼
                  ┌─────────┐     ┌─────────┐
                  │  Error  │     │ Success │
                  └─────────┘     └─────────┘

(Note: ▶ is U+25B6 BLACK RIGHT-POINTING TRIANGLE, from the Geometric Shapes block U+25A0–U+25FF)

CLI Tool Output

  Server Status
  ─────────────────────────────────────
   nginx         active (running)
   postgresql    active (running)
   redis         inactive (dead)

  Disk Usage
  ┌──────────────────────┬──────┬──────┐
   Mount                 Used  Free 
  ├──────────────────────┼──────┼──────┤
   /                      45%  127G 
   /var                   62%   38G 
  └──────────────────────┴──────┴──────┘

CSS Requirements: Monospace is Non-Negotiable

Box drawing characters only align correctly with a monospace font. Each character must occupy exactly the same horizontal width. In a proportional font, the ─ horizontal line may be slightly narrower or wider than the space occupied by a letter, creating gaps or overlaps.

/* Box drawing requires monospace */
.box-drawing,
pre,
code {
  font-family:
    "Cascadia Code",
    "Fira Code",
    "JetBrains Mono",
    "Consolas",
    "Menlo",
    "Monaco",
    "Courier New",
    monospace;
}

Line Height Matters

Box drawing characters are designed to connect seamlessly when line-height is exactly 1. Any additional line spacing creates vertical gaps between horizontal runs:

.box-art {
  font-family: monospace;
  line-height: 1;          /* Critical: no gap between lines */
  letter-spacing: 0;       /* Prevent horizontal gaps */
  white-space: pre;        /* Preserve all spacing */
}

Color Styling

Box drawing characters respond to color like any text character, enabling colored terminal-style output:

.terminal {
  background: #1e1e1e;
  color: #cccccc;
  font-family: monospace;
  line-height: 1;
  padding: 1rem;
}

.terminal .header-box {
  color: #4ec9b0;  /* Teal for headers */
}

.terminal .error-box {
  color: #f44747;  /* Red for errors */
}

.terminal .success-box {
  color: #4caf50;  /* Green for success */
}

Block Elements as Progress Bars

Block element characters work well for inline progress indicators styled as monospace text:

.progress-bar {
  font-family: monospace;
  font-size: 1rem;
  letter-spacing: -0.05em;  /* Tighten the blocks slightly */
}

.progress-filled {
  color: #4caf50;
}

.progress-empty {
  color: #555555;
}
<span class="progress-bar">
  <span class="progress-filled">████████</span><span class="progress-empty">░░░░░░░░</span>
</span>
50%

Terminal Emulator Rendering Differences

One significant challenge with box drawing characters is that different terminal emulators render them differently — gaps between characters, slightly off-metric widths, and font substitution all affect alignment. Web rendering is more predictable because you fully control the font stack, but these concerns still apply to <pre> blocks that might be copy-pasted into terminals.

Safe practices for cross-context box drawing:

  1. Stick to the light single-line characters (U+2500–U+253C) — these have the most consistent rendering across platforms.
  2. Avoid mixing light and heavy lines unless you have tested in all target environments.
  3. Double-line characters (U+2550–U+256C) are well-supported but look slightly different across fonts.
  4. The dashed variants (triple dash, quadruple dash) are less reliably rendered in some legacy terminal fonts.

For web contexts specifically, fonts with excellent box drawing support include: Cascadia Code, Fira Code, JetBrains Mono, Inconsolata, and Iosevka. The system fallbacks (Consolas on Windows, Menlo/Monaco on macOS) also render box drawing correctly.

Using Box Drawing in HTML

When embedding box drawing art in HTML, use a <pre> element to preserve whitespace, and encode any characters that have HTML significance:

<pre class="box-drawing">
┌──────────────────┐
│  Unicode Symbol  │
│  Reference       │
└──────────────────┘
</pre>

You can also use box drawing characters directly in regular text within <code> spans when describing terminal output or CLI interfaces. No special HTML entities are needed — box drawing characters are standard Unicode and can be used directly in UTF-8 HTML.

For generated output (dynamic tables, tree views), you can construct box drawing strings in JavaScript:

const BOX = {
  topLeft: '┌', topRight: '┐',
  bottomLeft: '└', bottomRight: '┘',
  horizontal: '─', vertical: '│',
  teeRight: '├', teeLeft: '┤',
  teeDown: '┬', teeUp: '┴',
  cross: '┼'
};

function drawBox(lines, width) {
  const pad = (s) => s.padEnd(width);
  const top = BOX.topLeft + BOX.horizontal.repeat(width + 2) + BOX.topRight;
  const bottom = BOX.bottomLeft + BOX.horizontal.repeat(width + 2) + BOX.bottomRight;
  const rows = lines.map(l => `${BOX.vertical} ${pad(l)} ${BOX.vertical}`);
  return [top, ...rows, bottom].join('\n');
}

Next in Series: Part 5 closes the series by explaining why characters sometimes render as empty boxes (tofu), how font fallback works across operating systems, and how to build font stacks that handle the full range of Unicode gracefully. Font Fallback and Tofu: Why Characters Display as Empty Boxes

Ký hiệu liên quan

Thuật ngữ liên quan

Công cụ liên quan

Thêm hướng dẫn