jcyamo.dev

The Disassembled Web · 003 · The Open Door

Part of The Disassembled Web

------------------------------------------------------------------------
V3SS3L · 003 · THE OPEN DOOR
------------------------------------------------------------------------

I showed the markup to a friend. Senior engineer. Ships React
apps for a living.

“Why not just use divs?”

Because a div is a lie of omission. It says nothing. A <main>
says “this is why you came here.” An <article> says “this stands
on its own.” A <nav> says “this is how you move.”

“Screen readers figure it out.”

No. Screen readers read what we write. If we write nothing, they
read nothing. A blind user tabbing through a wall of <div> is
like a sighted user staring at a page with no headings, no
paragraphs, no whitespace -- just an unbroken river of text.

“Sounds like extra work.”

It is the same number of characters. <main> is four letters.
<div> is three. The cost of meaning is one keystroke.

He went quiet for a moment.

I thought about what I wrote in issue 001. Principle two:
“No line of code shall exist without justification.” A <div>
exists without justification. A <main> justifies itself by
its name.

I added role="region" to the <pre> block today. Now a screen
reader announces “Zine content, region” and the user can jump
straight to the text. One attribute. One word of difference.

The door was always there. We just had to open it.

-- z3r0
------------------------------------------------------------------------

z3r0 references issue 001 above, but the reader has no way to navigate there — cross-references without navigation is a limitation of our single-page design.

Our zine is currently a raw <pre><code> block inside a <main> element. It works, but it’s semantically flat. The browser sees it as a generic block of code, not as a structured publication. We need to give it meaning.

Semantic HTML is not just about accessibility (though that’s a huge benefit). It’s about giving our content structure that both humans and machines can understand. It’s about building the load-bearing walls before we paint.

The Semantic Container

The instinct is to decompose. Break z3r0’s raw text into <article>, <section>, and <p> elements—proper headings, structured paragraphs, the full semantic tree. For a blog post or news article, that decomposition would be exactly right. Prose with paragraphs, headings, and inline emphasis maps cleanly onto HTML’s semantic vocabulary.

But a zine is not a blog post. The ASCII art separators, the 72-character line wraps, the monospaced grid—all of it depends on <pre><code> preserving exact whitespace. Decomposing into <p> tags would shatter the layout. The semantic breakdown and the preformatted aesthetic are in direct conflict.

The resolution is a principle, not a compromise: when content is opaque, the container carries the meaning. The <article> wrapper tells the browser “this is a self-contained composition” without demanding that the content inside be further decomposed. The <pre><code> block remains intact. Structural meaning without aesthetic sacrifice.

The general rule: decompose when you can, contain when you must. For most web content, full semantic markup is the right default. But when the content’s format is its meaning—preformatted text, ASCII art, code listings—wrap it in the right semantic container and stop. Don’t force a structure the content resists.

Open zines/V3SS3L/001.html. We’re going to wrap the zine in an <article> and add a visually-hidden heading that gives the article a programmatic title.

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>The Void Reader | V3SS3L 001</title>
		<link rel="stylesheet" href="../../assets/css/reset.css" />
		<link rel="stylesheet" href="../../assets/css/typography.css" />
	</head>
	<body>
		<main>
			<article aria-labelledby="article-title">
				<h1 id="article-title" class="visually-hidden">
					V3SS3L · 001 · THE VOID
				</h1>
				<pre><code>
------------------------------------------------------------------------
V3SS3L · 001 · THE VOID
------------------------------------------------------------------------
...
            </code></pre>
			</article>
		</main>
	</body>
</html>

The <article> tells the browser this is a self-contained composition. The <h1> with class=”visually-hidden” is invisible on screen but announces the article’s title to screen readers. The aria-labelledby attribute connects the two, so assistive technology announces “V3SS3L · 001 · THE VOID, article” when navigating by landmark.

The title is duplicated—once in the hidden heading, once inside the <pre> block. This is intentional. The visual title lives inside the opaque preformatted content where CSS can’t extract it. The hidden heading is the bridge between the visual and the programmatic.

Opening the Door Wider

Structure is the scaffold. Now we add the wiring that makes the scaffold navigable. z3r0 wrote it plainly: “If you can’t use my site with a keyboard, I have failed you.” Accessibility is empathy encoded in markup.

Our page has a semantic <main> and an <article>, but it’s still partially silent. A screen reader user navigating by landmarks would find the <main> region, but the <pre> block inside it is opaque—just a run of preformatted text with no announced role or label. We can do better.

We need a CSS utility to hide elements visually while keeping them available to screen readers. Create the accessibility stylesheet.

touch assets/css/a11y.css

Open assets/css/a11y.css:

/* Visually hide an element, but keep it available for screen readers */
.visually-hidden {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

Now let’s enhance the markup with ARIA roles, labels, and keyboard focus. Open zines/V3SS3L/001.html:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>The Void Reader | V3SS3L 001</title>
		<link rel="stylesheet" href="../../assets/css/reset.css" />
		<link rel="stylesheet" href="../../assets/css/typography.css" />
		<link rel="stylesheet" href="../../assets/css/a11y.css" />
	</head>
	<body>
		<main>
			<article aria-labelledby="article-title">
				<h1 id="article-title" class="visually-hidden">
					V3SS3L · 001 · THE VOID
				</h1>
				<pre role="region" aria-label="Zine content" tabindex="0"><code>
--------------------------------------------------------------------------------
V3SS3L · 001 · THE VOID
--------------------------------------------------------------------------------
...
            </code></pre>
			</article>
		</main>
	</body>
</html>

Let’s break down what we added and why.

lang="en" on <html>: This tells the browser and assistive technology what language the content is in. Screen readers use it to select the correct pronunciation engine. Without it, a reader using a French screen reader would hear English text mangled through French phonetics.

<main> without a label: The <main> element is a landmark by default—screen readers announce it when navigating by region. Since there’s only one <main> on the page, a label isn’t necessary. Labels on landmarks are for differentiating multiple instances of the same landmark type (e.g., two <nav> elements: “Primary navigation” and “Footer navigation”). A single <main> is self-evident.

role="region" on <pre>: The <pre> element has a default role of generic—screen readers treat it as an unremarkable block. By adding role="region", we create a navigable landmark: a perceivable section that users will likely want to navigate to directly. Combined with aria-label="Zine content", a screen reader announces “Zine content, region”—accurate and helpful.

tabindex="0" on <pre>: This makes the <pre> element focusable via keyboard. Without it, a keyboard user can’t focus the content area to scroll through it with arrow keys. It brings the zine content into the keyboard navigation flow.

aria-labelledby="article-title" on <article>: This connects the article to its visually-hidden heading, giving the article landmark a human-readable label. A screen reader navigating by article will announce the title.

Link the new stylesheet in the <head> and reload. Press Tab. The focus outline should appear on the <pre> block. Use a screen reader (or your browser’s Accessibility pane in DevTools) to verify the landmarks and labels are announced correctly.

The page now has a properly structured main landmark, a labelled article, a navigable and labelled region for the zine content, a programmatically accessible title, and full keyboard navigation. Structure became meaning. Meaning became access.

$ git add assets/css/a11y.css zines/V3SS3L/001.html
$ git commit -m "Add semantic structure, ARIA landmarks, and keyboard navigation for accessibility"
$ git tag -a v3ss3l-003 -m "V3SS3L 003: The Open Door — semantic HTML, ARIA landmarks, keyboard navigation"