Semantic HTML and a well-labeled form: the interface every agent already reads
Before any tool for agents, there is an interface they all already read: HTML. A label tied to its field, a real button, a main region, a heading, a language, and alt text. It is stable, it works for screen readers and for agents, and it does not depend on an experimental browser. In this lesson you fix the sample page until the check comes back at zero.
Goal: fix the labels, button, regions, and alt text until arvore.mjs finds no problems in your copy of the page.
01 · Six fixes, from the old page to the new one
Click each one. All of them show up in the arvore.mjs check.
3Regionsmain, h1Regions. marks the main content and an
says what the page is about. The agent separates the task from the menu and the footer.
4LanguagelangLanguage. says what language the text is in. Screen readers pronounce it correctly; reading tools know what they are reading.5ImagealtImage. Every with content gets alt text that describes what it shows. A decorative image gets alt="" and disappears from the tree.6Resultrole=statusResult. The form's response goes in an
Click each block in the diagram to see what it does.
Native element first: button, label, main. ARIA only where HTML doesn't have the right element.
02 · The fixed form
The snippet that changed from antes.html to depois.html. The form works on its own, for people and for any agent that operates the screen.
Snippet · kit/site/depois.htmlCopy
<html lang="pt-BR">
…
<main>
<h1>Agendar revisão</h1>
<img src="bike.svg" width="60" height="40" alt="Bicicleta na bancada da oficina">
<form id="agendar" aria-describedby="ajuda">
<p id="ajuda">Escolha o serviço e o dia. A oficina confirma por e-mail.</p>
<label for="nome">Seu nome</label>
<input id="nome" name="nome" autocomplete="name" required>
<label for="servico">Serviço</label>
<select id="servico" name="servico" required>
<option value="">Escolha</option>
<option value="basica">Revisão básica</option>
<option value="completa">Revisão completa</option>
</select>
<label for="data">Data</label>
<input id="data" name="data" type="date" required>
<button type="submit">Agendar</button>
<output id="resultado" role="status" aria-live="polite"></output>
</form>
</main>
Terminal · check depois.htmlCopy
cd ~/projetos/formacao-webmcp-base-v72/kit
node arvore.mjs depois; echo "saída: $?"
Captured output (10/19/2026)Common mistake: fixing it with ARIA on a div
Example from the repository
## árvore de acessibilidade · 15 linhas · 415 bytes
- main:
- heading "Agendar revisão" [level=1]
- img "Bicicleta na bancada da oficina"
- paragraph: Escolha o serviço e o dia. A oficina confirma por e-mail.
- text: Seu nome
- textbox "Seu nome"
- text: Serviço
- combobox "Serviço":
- option "Escolha" [selected]
- option "Revisão básica"
- option "Revisão completa"
- text: Data
- textbox "Data"
- button "Agendar"
- status
## conferência · http://127.0.0.1:PORTA/depois.html
problemas: 0
saída: 0
Now everything has a role and a name: heading, img with a description, the three fields, the button "Agendar", and the status for the response. The tree grew from 182 to 415 bytes, and the check comes back at zero.
Putting role="button" and aria-label on the clickable div makes the tree show a button, but it doesn't get keyboard focus or fire on Enter without more JavaScript, and it doesn't submit the form. arvore.mjs keeps flagging <div> clicável que não é botão, on purpose: swap it for the button.
03 · Your copy to fix
Copy antes.html to a folder of your own and run the check: 10 problems. Then edit the copy until it hits zero. arvore.mjs accepts the path to any .html.
- campo #servico sem <label>
- campo #data sem <label>
problemas: 10
saída: 1
The last three lines of the copy check, before the fix. The full list is in lesson 2.
04 · Practice
Reset the copy and bring the fix to your own site.
E1 · Reset the copy
20 min
Edit /tmp/oficina/antes.html with the six fixes, without looking at depois.html. Run arvore.mjs after each change and watch the list shrink.
Done when:node arvore.mjs /tmp/oficina/antes.html → problemas: 0 and saída: 0.
E2 · The form still works
5 min
Run tres-jeitos.mjs on your fixed copy: the agent has to find the three fields and the button by name and read the result.
Done when:node tres-jeitos.mjs /tmp/oficina/antes.html → line 2 · operar with controles na árvore · 0 sem nome and saída: 0.
E3 · One fix on your own site
30 min
On the page of your site from lesson 2, apply the fixes (start with the labels and the button) on a local copy or in a test environment. Publish when the check drops.
Done when:node arvore.mjs on the page (copy or published) shows problemas: lower than the number you noted in lesson 2.
05 · Lesson quiz
Question 1 of 4
What is the right way to give a form text field a name?skill
AA label element tied to the field with forBA placeholder with the field name inside itCA title attribute on the input with the nameDBold text right above the field
Correct
The label tied with for becomes the accessible name and stays visible. A placeholder disappears when you type, and loose text above the field isn't tied to it.
Not this one
The label tied with for becomes the accessible name and stays visible. A placeholder disappears when you type, and loose text above the field isn't tied to it.Review: Six fixes, from the old page to the new one
Question 2 of 4
Why swap the clickable div for a button, instead of putting role="button" on the div?
ABecause role="button" is forbidden by the WebMCP specBBecause the div makes the page much slower on a phoneCBecause the agent only reads elements that have their own idDBecause button already comes with focus, keyboard, and submit
Correct
Native element first: button gets focus, fires with Enter, and submits the form. The role only changes what the tree shows; you'd have to program the rest.
Not this one
Native element first: button gets focus, fires with Enter, and submits the form. The role only changes what the tree shows; you'd have to program the rest.Review: The fixed form
Question 3 of 4
The form's response appears in some random div. What changes when you use
ANothing for the agent; it only changes the text color on screenBThe agent finds the result by the status roleCThe form becomes a registered WebMCP toolDThe result goes to the site's MCP server
Correct
status is an accessibility tree role: the agent reads there what happened after the click. It has nothing to do with WebMCP; it's stable HTML.
Does this HTML fix depend on an origin trial or a flag in Chrome?skill
AYes, it only works with the published origin trial tokenBYes, it needs the enable-webmcp-testing flag turned onCNo: it's stable HTML, read by agentsDOnly in Edge 150, which has the origin trial open
Correct
Label, button, regions, and alt are plain old HTML. That's the stable base; WebMCP is the experimental part.