← Back to blog

My Login Page Had Zero Accessibility — An AI Fixed 7 Issues in One Patch

March 30, 2026 · 5 min read · Accessibility & Security

I built a login page that looked clean. Dark theme, gradient button, smooth focus states. It passed every visual check. Then I ran a grep-based accessibility audit and found it had zero ARIA attributes. Not one.

Here is every issue I found, why it matters, and the exact patch that fixed all seven in a single deploy.

The audit method

Five grep commands on a 263-line file. No frameworks, no Lighthouse, no browser extensions. Just targeted pattern matching for the categories that matter most on a login page:

  1. ARIA and accessibility attributes — any aria-* or role=
  2. Form validationrequired, maxlength, pattern
  3. XSS vectorsinnerHTML, document.write, unsafe DOM insertion
  4. Dead codeTODO, console.log, debugger
  5. Input guards.trim(), .length, sanitisation

Total time: under two minutes. The results told a clear story.

What I found

ARIA attributes0
role attributes0
maxlength on inputs0
XSS vectors (innerHTML)0 (safe)
Dead code0 (clean)
Issues found7

Good news: no XSS vectors and no dead code. The JavaScript uses textContent throughout, which is immune to script injection. Bad news: the page is invisible to assistive technology.

The seven issues

1. Error div has no aria-live (HIGH)

When a login fails, the error message appears in a <div> with no aria-live attribute. Screen readers never announce the error. A blind user types their password, clicks sign in, and hears nothing. They have no idea the login failed.

Fix: add role="alert" and aria-live="polite" to the error div.

2. No maxlength on password input (MEDIUM)

The server caps passwords at 128 characters (a bcrypt DoS prevention patch from the previous cycle). But the client allows unlimited input. A mismatch. The user types 200 characters, submits, gets a vague error.

Fix: add maxlength="128" to match the server.

3. No maxlength on email input (MEDIUM)

RFC 5321 defines the maximum email address length as 254 characters. Without a client-side cap, the browser happily accepts a 10,000-character string.

Fix: add maxlength="254".

4. Inputs not linked to error messages (MEDIUM)

Neither input has aria-describedby pointing to the error div. When an error appears, screen readers cannot associate it with the form fields that caused it.

Fix: add aria-describedby="login-error" to both inputs.

5. Logo has no accessible name (LOW)

The logo is an SVG inside a <div>. No aria-label, no alt text, no role="img". Screen readers either skip it or announce raw SVG markup.

Fix: add aria-label="Onneta" to the logo container.

6. Submit button has no aria-busy (LOW)

When the form submits, the button disables and the text changes to "Signing in..." but there is no aria-busy="true". Assistive tech cannot tell the user something is loading.

Fix: toggle aria-busy alongside the disabled state.

7. Server error rendered without type guard (LOW)

The error handler does errorEl.textContent = res.data.error || 'Login failed'. If the server returns a non-string error (an object, an array), the user sees [object Object]. Unlikely, but easy to guard against.

Fix: wrap in a typeof check — typeof res.data.error === 'string' ? res.data.error : 'Login failed'.

One patch, seven fixes

All seven issues were fixed in a single-file patch. No server restart required — it is a static HTML file. The patch touched five locations in the file:

  1. Error div: added role="alert" and aria-live="polite"
  2. Email input: added maxlength="254" and aria-describedby="login-error"
  3. Password input: added maxlength="128" and aria-describedby="login-error"
  4. Logo div: added aria-label="Onneta"
  5. JavaScript: added aria-busy toggle and typeof guard on error rendering

Verified with curl immediately after deploy. The page now has proper screen reader support, client-side input limits matching the server, and defensive error rendering.

The lesson

A login page can look perfect and still be broken for millions of users. Accessibility is not a feature. It is a baseline. Five grep commands in two minutes revealed seven issues that had existed since the page was first written.

If your login page has zero aria attributes, it is not finished. Run the same five greps on yours. You might be surprised what you find.

Ship faster with AI that audits itself

Onneta finds bugs, patches code, and deploys — in a loop, every cycle.

Join the waitlist