Build ASCII Architecture Diagrams in React

Render Unicode box-drawing diagrams as styled React components in Next.js.

Introduction

30 Second Summary

Architecture diagrams are everywhere in software engineering, but most tools produce bloated SVGs or require heavy diagramming apps just to show how a few boxes connect.

In this project, you will build a reusable system for rendering minimal, dark-mode ASCII architecture diagrams as React components, using Unicode box-drawing characters and a monochromatic shading palette to create visual depth without color.

What You'll Build

A pair of ASCII architecture diagrams rendered as React components in a Next.js app, styled with CSS Modules and a monochromatic dark-mode palette where visual depth comes from shading hierarchy, not color.

By the end of this project, you'll have:

  • A set of reusable token helper functions (B, O, P, Q) and a Pre wrapper component that render color-coded ASCII diagrams in the browser.
  • A pixel-perfect recreation of a simple "Pipeline / Ingest" data flow diagram using Unicode box-drawing characters.
  • A pixel-perfect recreation of the complex "LINEAR" nested architecture diagram with multiple layers, external actors, and bidirectional arrows.
  • Your own custom ASCII architecture diagram for a system you know well.

Are there any prerequisites?

You should be comfortable with React and TypeScript. Familiarity with the Next.js App Router and CSS Modules is helpful but not required.

Set Up the ASCII Diagram File Structure

Before you can draw any diagrams, you need a small toolkit of reusable building blocks. In this step, you'll create the file structure for your ASCII diagram system and build the core pieces: a CSS Module with a monochromatic dark-mode palette and a set of token helper functions that apply the right shading to each part of a diagram.

In This Step, Get Ready To:
  • Create the ascii/ directory and starter files.
  • Define a monochromatic color palette using CSS Modules.
  • Build token helper functions and a Pre wrapper component for rendering diagrams.
Create the Directory and Files

All of your ASCII diagram code will live in a single directory. This keeps the token functions, styles, and diagram components organized together.

  • Create a new directory called ascii/ inside your app/ directory (or components/ if that fits your project structure better).
  • Inside ascii/, create three empty files:
Add the CSS Module With the Monochromatic Palette

The visual depth in these diagrams comes entirely from shading hierarchy, not color. Lighter shades represent outer frames, and darker shades represent the most important labels and text. This creates a sense of nesting without using any colors at all.

  • Open styles.module.css and add the following styles:
.root {
  display: flex;
  justify-content: center;
  overflow: hidden;
}

.diagram {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
    "Liberation Mono", "Courier New", monospace;
  line-height: 1.3;
  font-size: 12px;
  white-space: pre;
  user-select: none;
}

/* Lightest — outermost frame */
.border {
  color: #dcdbdd;
}

/* Mid-gray — external actors and secondary labels */
.outer {
  color: #6f6e77;
}

/* Darkest — main labels and important text */
.primary {
  color: #282a30;
}

/* Mid-gray — nested box borders */
.quaternary {
  color: #86848d;
}

What do these styles do?

The .root class centers the diagram on the page using flexbox. The .diagram class sets up the monospace font stack, which is critical for alignment. Every character in a monospace font occupies exactly one column width, so spaces and box-drawing characters line up perfectly.

The white-space: pre setting preserves exact spacing (just like a <pre> element). The user-select: none prevents accidental text selection, making the diagram feel like a visual element rather than selectable text.

💡 Why This Specific Font Stack?

The generic monospace keyword can map to different fonts on different browsers, which may cause alignment issues with box-drawing characters. Some monospace fonts lack glyphs for these characters entirely, causing the browser to substitute from another font with different metrics. Specifying concrete fonts like SFMono-Regular and Consolas prevents this problem.

Build the Token Helpers and Pre Component

Token helpers are small functions that wrap a string in a <span> with the correct CSS class. Instead of manually adding class names throughout your diagram, you call B('┌──┐') to apply the lightest border shade, or P('Pipeline') to apply the darkest primary shade.

  • Open tokens.tsx and add the following code:
import styles from './styles.module.css';

// Token helper functions
// Each wraps a string in a <span> with the corresponding CSS class

// B = Border (lightest shade, for outermost frames)
export const B = (text: string) => (
  <span className={styles.border}>{text}</span>
);

// O = Outer (mid-gray, for external actors and secondary labels)
export const O = (text: string) => (
  <span className={styles.outer}>{text}</span>
);

// P = Primary (darkest shade, for main labels and important text)
export const P = (text: string) => (
  <span className={styles.primary}>{text}</span>
);

// Q = Quaternary (mid-gray, for nested box borders)
export const Q = (text: string) => (
  <span className={styles.quaternary}>{text}</span>
);

// Pre wrapper component for rendering diagrams
export const Pre = ({
  children,
  'aria-label': ariaLabel,
}: {
  children: React.ReactNode;
  'aria-label'?: string;
}) => (
  <div className={styles.root}>
    <pre className={styles.diagram} aria-label={ariaLabel}>
      {children}
    </pre>
  </div>
);

What is the shading hierarchy?

The four tokens map to four levels of visual depth. B (Border) is the lightest at #dcdbdd, used for the outermost frame. Q (Quaternary) and O (Outer) are mid-grays for nested boxes and external actors. P (Primary) is the darkest at #282a30, used for the most important labels. Lighter means further out, darker means more central and important.

💡 Why does the Pre component accept an aria-label?

Screen readers cannot interpret box-drawing characters as visual layouts. The aria-label prop lets you provide a text description of what the diagram shows, making it accessible. For example, you might set it to "Data flow diagram showing a Pipeline containing an Ingest process."

Your toolkit is ready. You have a color palette, four token helpers, and a Pre wrapper component. Next up, you'll use these building blocks to compose your first ASCII diagram line by line.

Build the Simple Data Flow Diagram

Your token helpers and Pre component are ready to use. Now it is time to put them to work by building an actual diagram. You will recreate a simple "Pipeline / Ingest" data flow diagram that nests one box inside another, using the shading hierarchy to create visual depth.

In This Step, Get Ready To:
  • Understand how the token functions map to different nesting layers in a diagram.
  • Compose a two-layer nested box diagram line by line using Unicode box-drawing characters.
  • Verify that every line has the same character count so vertical edges align perfectly.
Understand the Anatomy of the Diagram

Before writing any code, you need to understand how the shading hierarchy maps to the diagram's structure. The diagram has two layers: an outer box labeled "Pipeline" and an inner box labeled "Ingest." Each layer uses a different token function to create visual depth.

Here is how the tokens map to the diagram layers:

  • B (Border) draws the outer "Pipeline" box frame. This is the lightest shade (#dcdbdd), representing the outermost layer.
  • Q (Quaternary) draws the inner "Ingest" box frame. This is a mid-gray shade (#86848d), making nested boxes visually recede behind the outer frame.
  • P (Primary) renders labels like "Pipeline" and "Ingest." This is the darkest shade (#282a30), making text the most prominent element.
  • Content text like "Kafka" and "Redis" is left unstyled. It inherits the base .diagram color.

Why Does Lighter Mean Further Out?

The shading hierarchy creates a sense of depth on a flat screen. The outermost frame is the lightest because it acts as a container boundary, not the focal point. As you move inward, elements get darker and more visually prominent. Labels use the darkest shade because they carry the most important information. The result is that your eye is drawn to the center of the diagram first.

Compose the Diagram Line by Line

Each line of the diagram is a <div> inside the Pre component. In a monospace font, every character occupies exactly one column width. Spaces count as characters. This means you need to be precise about how many characters appear on each line so that vertical edges align.

  • Create DataFlowDiagram.tsx in your ascii/ directory and add the following code:
import { B, P, Q, Pre } from './tokens';

export default function DataFlowDiagram() {
  return (
    <Pre aria-label="Data flow diagram showing a Pipeline box containing an Ingest box with Kafka and Redis">
      {/* Top border of outer "Pipeline" box */}
      <div>{B('┌───────── ')}{P('Pipeline')}{B(' ──────────────┐')}</div>

      {/* Empty row inside the outer box */}
      <div>{B('│')}{'                                  '}{B('│')}</div>

      {/* Top border of inner "Ingest" box */}
      <div>{B('│')}{' '}{Q('┌────── ')}{P('Ingest')}{Q(' ──────────┐')}{' '}{B('│')}</div>

      {/* Content row: Kafka */}
      <div>{B('│')}{' '}{Q('│')}{' Kafka                  '}{Q('│')}{' '}{B('│')}</div>

      {/* Content row: Redis */}
      <div>{B('│')}{' '}{Q('│')}{' Redis                  '}{Q('│')}{' '}{B('│')}</div>

      {/* Bottom border of inner "Ingest" box */}
      <div>{B('│')}{' '}{Q('└──────────────────────────┘')}{' '}{B('│')}</div>

      {/* Empty row inside the outer box */}
      <div>{B('│')}{'                                  '}{B('│')}</div>

      {/* Bottom border of outer "Pipeline" box */}
      <div>{B('└──────────────────────────────────┘')}</div>
    </Pre>
  );
}

What Does This Code Do?

Each <div> represents one horizontal line of the diagram. The token functions (B, Q, P) wrap characters in <span> elements with the appropriate CSS class, applying the correct shade. Plain strings like ' Kafka ' are left unstyled. Notice how spaces are used to pad each line so that the right-side vertical bars (│) all land in the same column.

💡 Pro tip: count your columns

The most common mistake when building ASCII diagrams is misaligned vertical edges. Every line must have the exact same total character count. If the outer box is 36 characters wide, every line between the top and bottom borders must also be exactly 36 characters. When something looks off, count the characters in the broken line and compare it to a line you know is correct.

Verify Alignment and Render

With the component written, you need to import it into a page and check that the diagram renders correctly in the browser.

  • Import DataFlowDiagram into your page component (for example, app/page.tsx) and add <DataFlowDiagram /> to the JSX.
  • Run npm run dev and open the page in your browser.
npm run dev
  • Check that all four corners of the outer box (┌ ┐ └ ┘) connect properly to their horizontal and vertical lines.
  • Verify that the inner "Ingest" box sits cleanly inside the outer "Pipeline" box with consistent spacing on all sides.
  • Confirm that the "Pipeline" and "Ingest" labels appear in the darkest shade, the outer frame in the lightest shade, and the inner frame in mid-gray.

What Should I See?

You should see a clean, dark-mode diagram with two nested rectangles. The outer rectangle has "Pipeline" in its top border, and the inner rectangle has "Ingest" in its top border. "Kafka" and "Redis" appear as content inside the inner box. The three shading levels should be clearly distinct.

🙋‍♀️ Lines Are Misaligned?

If the right-side vertical bars do not line up, one or more lines have a different character count. Pick a line that looks correct and count every character in it, including spaces. Then count the broken line. The difference tells you exactly how many spaces to add or remove. Help me debug my misaligned ASCII diagram.

Your first ASCII diagram is rendering in the browser. Next up, you will learn the full set of Unicode box-drawing characters and the alignment rules that make complex nested diagrams possible.

Understand the Box-Drawing Character System

You have a working diagram on screen, but you might still be copying box-drawing characters without fully understanding how they fit together. Unicode defines a dedicated block of 128 box-drawing characters specifically designed to connect in a grid. Once you learn the small subset that matters, composing new diagrams becomes a repeatable skill instead of guesswork.

In This Step, Get Ready To:
  • Learn the six core box-drawing characters and the five T-junction and cross characters.
  • Understand the equal-width alignment rule that keeps vertical edges straight.
  • Practice modifying a box in your existing diagram to build muscle memory.
Learn the Core Characters

The Unicode Box Drawing block spans U+2500 to U+257F and contains 128 characters. You only need six of them to draw most diagrams.

Here are the six essential characters:

  • ─ horizontal line (U+2500).
  • │ vertical line (U+2502).
  • ┌ top-left corner (U+250C).
  • ┐ top-right corner (U+2510).
  • └ bottom-left corner (U+2514).
  • ┘ bottom-right corner (U+2518).

Corners connect horizontal and vertical lines. A ┌ joins a line going right and a line going down. A ┘ joins a line going left and a line going up. The name tells you the direction: "Light Down And Right" for ┌, "Light Up And Left" for ┘.

For more complex diagrams, five additional junction characters let you branch lines:

  • ├ left tee (U+251C) branches right from a vertical line.
  • ┤ right tee (U+2524) branches left from a vertical line.
  • ┬ top tee (U+252C) branches down from a horizontal line.
  • ┴ bottom tee (U+2534) branches up from a horizontal line.
  • ┼ cross (U+253C) is a four-way intersection.

Pro tip: stick to light variants

Unicode also defines double-line variants like ═ (U+2550). The light variants in the U+2500 range have much better font support across browsers. Some monospace fonts lack glyphs for the double-line characters, which causes the browser to substitute from a different font with different metrics. That breaks your alignment.

Understand the Alignment Rules

In a monospace font, every character occupies exactly one column width. A horizontal line ─ fills one column. A vertical line │ fills one column. A space fills one column. This is what makes ASCII diagrams possible.

The single most important rule is this: every line in your diagram must have the same total number of characters for vertical edges to align.

Consider a simple three-line box:

┌──────┐
│ Test │
└──────┘

Each line is exactly 8 characters wide. The ┌ on line 1 sits in column 1. The │ on line 2 also sits in column 1. The └ on line 3 also sits in column 1. They stack perfectly because the character count is identical on every line.

If line 2 had one fewer space, the right │ would shift left by one column and the box would look broken. Spaces are characters and they count toward alignment.

Why does your CSS use a specific font stack?

Your styles.module.css specifies a font stack like ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace instead of just monospace. The generic monospace keyword can map to different fonts on different browsers. Specifying concrete fonts ensures consistent rendering of box-drawing characters across platforms.

Practice Connecting Boxes

The best way to internalize these rules is to modify an existing diagram. You will change the inner "Ingest" box in your DataFlowDiagram.tsx to practice alignment.

  • Open DataFlowDiagram.tsx in your editor.
  • Find the inner box (the one using the Q token for its borders).
  • Change the label from "Ingest" to a longer word like "Transform".
  • Widen the inner box by adding more ─ characters to its top and bottom borders.
  • Add an extra content line inside the inner box with a new label.
  • Pad every other line in the diagram with spaces so the total character count stays the same across all lines.
  • Save the file and check the browser.

Vertical lines not lining up?

Count the characters on each line. Every line must have the exact same total. A common mistake is forgetting to add trailing spaces after content that is shorter than the box width. Help me debug my box alignment.

If your modified box renders with all corners connected and vertical edges perfectly straight, you have the core skill of ASCII diagram craft down.

You now have a solid understanding of how box-drawing characters connect and how alignment works. Next up, you will put these skills to the test by building the full LINEAR architecture diagram with four nesting levels and external actors.

Build the Complex Architecture Diagram

Your simple data flow diagram proved that the token system works for two nesting levels. The real test is whether it scales to a complex, real-world architecture. In this step, you'll build the full "LINEAR" diagram with four nesting levels, external actors on both sides, and bidirectional arrows connecting everything together.

In This Step, Get Ready To:
  • Plan the four-layer nesting hierarchy and map each layer to a token.
  • Build the full architecture diagram from outside in, line by line.
  • Add external actors and bidirectional arrows that span all layers.
Plan the Layout on Paper

Before writing any code, you need to understand the nesting structure. The LINEAR diagram has four distinct layers, each progressively nested inside the previous one. Each layer maps to a specific token that controls its shading.

  • Sketch or visualize four nested rectangles on paper.
  • The outermost frame is labeled "LINEAR" and uses the B token (lightest shade, #dcdbdd).
  • Inside that sits a "Context" box using the Q token (mid-gray, #86848d).
  • Inside Context sits a "Rules" box, also using the Q token.
  • Inside Rules sits an "Agents" box using the Q token, containing block shade characters (░▒▓) for an abstract agent visualization.
  • External actors sit outside the outer frame on the left (Customer requests, Bug reports, Feedback) and right (Product). These use the O token (#6f6e77).
  • All labels like "LINEAR", "Context", "Rules", and "Agents" use the P token (darkest shade, #282a30).

Why Manual Tokens Instead of the Auto-Tokenizer?

The AsciiArt auto-tokenizer in tokens.tsx automatically applies the quaternary style to all box-drawing characters. That works for simple diagrams, but it cannot distinguish between nesting levels. The outer frame and inner boxes would all get the same color.

Manual B/O/P/Q tokens give you per-character control, which is essential when different nesting levels need different shading to create visual depth.

Build the Diagram From Outside In

The strategy is the same one you used for the simple diagram: build line by line, starting with the outermost frame and working inward. The difference is that this diagram is roughly 110 characters wide, so careful character counting matters even more.

  • Create a new file called ArchitectureDiagram.tsx in your ascii/ directory.
  • Import the token functions (B, O, P, Q) and the Pre component from tokens.tsx.
  • Start with the top border of the outer LINEAR frame using B('┌─── ') then P('LINEAR') then B(' ───...───┐').
  • Build each subsequent line as a <div> inside the Pre component, just like you did for the simple diagram.

Here is the structure for the component. Each <div> represents one line of the diagram:

import { B, O, P, Q, Pre } from './tokens';
export default function ArchitectureDiagram() {
  return (
    <Pre aria-label="LINEAR architecture diagram showing nested layers: Context, Rules, and Agents, with external actors for Customer requests, Bug reports, Feedback, and Product">
      {/* Outer frame top border */}
      <div>{B('┌──────────── ')}{P('LINEAR')}{B(' ─────────────────────────────────────────────────────────────────────────────────────────┐')}</div>
      {/* Spacer line inside outer frame */}
      <div>{B('│')}{' '.repeat(108)}{B('│')}</div>
      {/* Context box top border */}
      <div>{B('│')}{'  '}{Q('┌───────── ')}{P('Context')}{Q(' ──────────────────────────────────────────────────────────────────────────────────┐')}{'  '}{B('│')}</div>
      {/* Spacer inside Context */}
      <div>{B('│')}{'  '}{Q('│')}{' '.repeat(102)}{Q('│')}{'  '}{B('│')}</div>
      {/* Rules box top border */}
      <div>{B('│')}{'  '}{Q('│')}{'  '}{Q('┌──────── ')}{P('Rules')}{Q(' ──────────────────────────────────────────────────────────────────────────┐')}{'  '}{Q('│')}{'  '}{B('│')}</div>
      {/* Spacer inside Rules */}
      <div>{B('│')}{'  '}{Q('│')}{'  '}{Q('│')}{' '.repeat(96)}{Q('│')}{'  '}{Q('│')}{'  '}{B('│')}</div>
      {/* Agents box top border */}
      <div>{B('│')}{'  '}{Q('│')}{'  '}{Q('│')}{'  '}{Q('┌──────── ')}{P('Agents')}{Q(' ──────────────────────────────────────────────────────────────────┐')}{'  '}{Q('│')}{'  '}{Q('│')}{'  '}{B('│')}</div>
      {/* Agent visualization lines */}
      <div>{B('│')}{'  '}{Q('│')}{'  '}{Q('│')}{'  '}{Q('│')}{' '.repeat(90)}{Q('│')}{'  '}{Q('│')}{'  '}{Q('│')}{'  '}{B('│')}</div>
      <div>{B('│')}{'  '}{Q('│')}{'  '}{Q('│')}{'  '}{Q('│')}{'          ░░░░░░▓▒▒░                                                                  '}{Q('│')}{'  '}{Q('│')}{'  '}{Q('│')}{'  '}{B('│')}</div>
      <div>{B('│')}{'  '}{Q('│')}{'  '}{Q('│')}{'  '}{Q('│')}{' '.repeat(90)}{Q('│')}{'  '}{Q('│')}{'  '}{Q('│')}{'  '}{B('│')}</div>
      {/* Agents box bottom border */}
      <div>{B('│')}{'  '}{Q('│')}{'  '}{Q('│')}{'  '}{Q('└──────────────────────────────────────────────────────────────────────────────────────────────┘')}{'  '}{Q('│')}{'  '}{Q('│')}{'  '}{B('│')}</div>
  • Still in ArchitectureDiagram.tsx, add the closing boxes, external actors, and arrow rows below the Agents bottom border by pasting this code:
      {/* Spacer inside Rules */}
      <div>{B('│')}{'  '}{Q('│')}{'  '}{Q('│')}{' '.repeat(96)}{Q('│')}{'  '}{Q('│')}{'  '}{B('│')}</div>
      {/* Rules box bottom border */}
      <div>{B('│')}{'  '}{Q('│')}{'  '}{Q('└──────────────────────────────────────────────────────────────────────────────────────────────────┘')}{'  '}{Q('│')}{'  '}{B('│')}</div>
      {/* Spacer inside Context */}
      <div>{B('│')}{'  '}{Q('│')}{' '.repeat(102)}{Q('│')}{'  '}{B('│')}</div>
      {/* Context box bottom border */}
      <div>{B('│')}{'  '}{Q('└──────────────────────────────────────────────────────────────────────────────────────────────────────────┘')}{'  '}{B('│')}</div>
      {/* Spacer line inside outer frame */}
      <div>{B('│')}{' '.repeat(108)}{B('│')}</div>
      {/* Arrow row with external actors */}
      <div>{O('Customer requests ')}{B('│')}{'  '}{Q('│')}{'  '}{Q('│')}{'  '}{Q('│')}{'  ◀──────────────────────────────────────────────────────────────────────▶  '}{Q('│')}{'  '}{Q('│')}{'  '}{Q('│')}{'  '}{B('│')}{' '}{O('Product')}</div>
      <div>{O('Bug reports       ')}{B('│')}{'  ◀───────▶  '}{Q('│')}{'  ◀───▶  '}{Q('│')}{'                                                                          '}{Q('│')}{'  '}{Q('│')}{'  '}{Q('│')}{'  '}{B('│')}</div>
      <div>{O('Feedback          ')}{B('│')}{'  '}{Q('│')}{'  '}{Q('│')}{'  '}{Q('│')}{'  ◀────────▶                                                                     '}{Q('│')}{'  '}{Q('│')}{'  '}{Q('│')}{'  '}{B('│')}</div>
      {/* Spacer line inside outer frame */}
      <div>{B('│')}{' '.repeat(108)}{B('│')}</div>
      {/* Outer frame bottom border */}
      <div>{B('└──────────────────────────────────────────────────────────────────────────────────────────────────────────────┘')}</div>
    </Pre>
  );
}

What Is Happening in This Code?

Each <div> is one horizontal line of the diagram. The token functions (B, Q, P, O) wrap characters in <span> elements with the correct CSS class. The ' '.repeat(N) calls generate exact numbers of spaces to fill empty areas inside boxes.

The diagram builds from the outer frame inward. You open the LINEAR frame, then open Context inside it, then Rules inside that, then Agents at the deepest level. After the Agents content, you close each box in reverse order.

💡 Pro tip: count characters to debug alignment

Every line must have the same total character count for the right edge of the outer frame to align. If a vertical bar is off by even one column, count the characters in that line and compare it to a line you know is correct. The ' '.repeat(N) pattern makes this easier because you can adjust the number precisely.

Add External Actors and Verify the Full Diagram

The arrow row is the most complex part of the diagram. It spans the full width and passes through every nested box border. External actors sit outside the outer frame, connected by bidirectional arrows made from ◀ (U+25C0), ─ (U+2500), and ▶ (U+25B6).

  • Look at the three arrow lines in the code above. "Customer requests" on the left uses the O token and has a long bidirectional arrow that reaches deep into the Agents box.
  • "Bug reports" has a shorter arrow that connects to the Context layer.
  • "Feedback" has a medium arrow that connects to the Rules layer.
  • "Product" sits on the right side of the first arrow line, also using the O token.
  • Import the component into your page and run npm run dev.
// In your page file (e.g., app/page.tsx), add the import:
import ArchitectureDiagram from './ascii/ArchitectureDiagram';

// Then render it in your JSX:
<ArchitectureDiagram />
  • Check that all four nesting levels are visible with distinct shading.
  • Verify that the outer frame (B token) is the lightest shade and the labels (P token) are the darkest.
  • Confirm that the external actors appear outside the frame and the arrows connect through the nested borders.
  • Verify the block shade characters (░▒▓) render inside the Agents box.

What Should I See?

You should see a large nested diagram with the LINEAR label on the outer frame, three progressively nested boxes inside (Context, Rules, Agents), external actor labels on the left and right sides, and arrows connecting the layers. The shading should create a sense of depth, with the outermost frame being the lightest and the labels being the darkest.

🙋‍♀️ Right Edge Not Lining Up?

The most common issue is mismatched character counts between lines. Pick a line where the right edge looks correct and count its total characters (including spaces). Then count the broken line. The difference tells you exactly how many spaces to add or remove. Help me debug my diagram alignment.

💡 What Are the Block Shade Characters?

The characters ░ (U+2591 Light Shade), ▒ (U+2592 Medium Shade), and ▓ (U+2593 Dark Shade) are Unicode block elements. In this diagram, the pattern ░░░░░░▓▒▒░ creates an abstract visualization of agent activity inside the innermost box.

You just built a complex, multi-layered architecture diagram entirely from Unicode characters and React components. The shading hierarchy makes the nesting instantly readable without any color. Next up, you'll take everything you've learned and design your own diagram from scratch.

Secret mission

Design Your Own Architecture Diagram

You have the token system, the box-drawing characters, and the alignment rules. Now put them all together to diagram a system you actually know. Pick a real architecture, sketch the nesting hierarchy, and build it line by line.

Before you go

Clean Up Your Resources

This project runs entirely locally with no cloud resources, so there are no ongoing costs. Decide whether to keep your files for future reference, pause your dev server, or delete everything entirely.

Resources you used:

  • The ascii/ directory containing tokens.tsx, styles.module.css, DataFlowDiagram.tsx, and ArchitectureDiagram.tsx.
  • Your local Next.js development server (started with npm run dev).
  • Node.js and any node_modules dependencies installed for the project.

Keep everything running

No action needed. Choose this if you are still actively building or want to experiment with more ASCII diagrams.

  • Your dev server and all files stay as they are.
  • You can continue adding new diagram components to the ascii/ directory at any time.

Pause - I'll come back to this later

Shut down your development server to free up memory while keeping all your files intact.

  • Stop the Next.js dev server by pressing Ctrl + C in the terminal where it is running.
  • Your project files will remain on disk. Run npm run dev again when you are ready to resume.

Delete - I don't want to use this again

Remove all project resources and start fresh.

  • Stop the Next.js dev server by pressing Ctrl + C in the terminal.
  • Delete the ascii/ directory and all its contents (tokens.tsx, styles.module.css, DataFlowDiagram.tsx, ArchitectureDiagram.tsx).
  • Remove any imports or route references to the diagram components from your page files.
  • If this was a standalone project and you no longer need the Next.js app, delete the entire project directory.

Mission accomplished

That's a Wrap!

Nice work! You just built a complete system for rendering ASCII architecture diagrams as React components, using nothing but Unicode box-drawing characters and a monochromatic shading hierarchy.

Here's what you learned:

  • Created reusable token helper functions (B, O, P, Q) that apply visual depth through CSS Module classes, where lighter shades represent outer layers and darker shades represent inner, more important elements.
  • Mastered the Unicode box-drawing character system and alignment rules for composing clean nested layouts where every line has the same character count.
  • Built a complex multi-layered architecture diagram with four nesting levels, external actors, and bidirectional arrows, all rendered pixel-perfectly in a monospace font.
  • Designed and built your own custom ASCII architecture diagram for a real system you know.

Ready to quiz yourself?