cantclickthis.dev

You can't click this

Five ways websites lock people out, each one live and broken on this page — then fixed, in the same breath, with the markup that did it.

Read the transcript

Intro

[record scratch]
Yeah… Check the DOM.
Check the tree.
You can't tab through that.

Chorus

You can't click this.
You can't click this.
You can't click this.
Stop! Semantic time!

Verse

My, my, my, my markup hits so bad,
Makes the screen reader user straight-up mad.
Thank you for blessing me
With a clickable div that I just can't see!
No role, no focus state,
Hit the Tab key once, now I gotta wait.
Buttons look real, but they have no tags,
Accessibility backlog drags.

Chorus

Can't tab this.
Look, man, you can't click this.
No aria-label, can't click this.
Stop! Semantic time!

Outro

Just use a button.
CantClickThis.dev.
Stop! Semantic time!

Greg Miller, Shrinkray Interactive Track: “Can't Click This” by toledogeeks

01 of 05

The clickable div

It looks like a button. It is styled like a button. The mouse agrees. Nothing else does.

  • WCAG 4.1.2 Name, Role, Value
  • WCAG 2.1.1 Keyboard

example.com

Put focus in the demo and press Tab. In the broken state there is nothing to land on.

What breaks it

 (flagged, see note 3) <div class="btn" onclick="addToCart()">
  Add to cart
</div>
  1. No role, so a screen reader announces this as plain text — or skips it entirely.
  2. Not focusable: a div has no tabindex, so Tab walks straight past it.
  3. A click handler alone ignores Enter and Space, which is how buttons are operated without a mouse.

What fixes it

 (flagged, see note 2) <button type="button" class="btn">
  Add to cart
</button>
  1. Role, focusability, Enter and Space handling, and the disabled state all arrive free with the element.
  2. type="button" stops it submitting a surrounding form by accident.

If it looks like a button, it has to be a button. Screen readers and keyboards only know what the code says, never what it looks like.

Si parece un botón, tiene que ser un botón. El lector de pantalla y el teclado solo saben lo que dice el código, nunca lo que se ve.

02 of 05

Alt text that says nothing

Every image gets an alt attribute. What goes inside it depends on whether the image carries information or just decorates.

  • WCAG 1.1.1 Non-text Content

example.com

The filename version is not a missing alt — it is worse. It is noise a screen reader has to read out loud.

What breaks it

 (flagged, see note 1) <img src="hero.png" alt="IMG_2847_final_v3.png">
 
 (flagged, see note 2) <img src="swirl.svg"
     alt="decorative swirl divider graphic image">
  1. A filename describes your export settings, not the picture. It tells the listener nothing.
  2. Decoration announced out loud is clutter. "Image" is also redundant — the screen reader already said "graphic".

What fixes it

<img src="hero.png"
 (flagged, see note 1)      alt="Blue high-top sneaker with a white sole, side view">
 
 (flagged, see note 2) <img src="swirl.svg" alt="">
  1. Describes what a sighted person gets from the image, in the length of a caption.
  2. An empty alt is a decision, not an omission: it tells assistive tech to skip this one on purpose.

If you deleted the image, what sentence would you write in its place? That sentence is your alt text. If you would write nothing, use alt="".

Si borraras la imagen, ¿qué frase escribirías en su lugar? Esa frase es tu texto alternativo. Si no escribirías nada, usa alt="".

03 of 05

The invisible cursor

Someone removed the focus outline because it looked ugly on one button. Now nobody navigating by keyboard can tell where they are.

  • WCAG 2.4.7 Focus Visible
  • WCAG 2.4.11 Focus Not Obscured

example.com

Tab through this row in both states. Same tab order, same elements — only one of them tells you anything.

What breaks it

a,
button {
 (flagged, see note 2)   outline: none;
}
  1. Nothing replaces it, so keyboard focus becomes invisible across the entire page.
  2. This is usually copied in to hide the ring on mouse click — :focus-visible already does that for you.

What fixes it

 (flagged, see note 1) a:focus-visible,
button:focus-visible {
  outline: 3px solid #ffffff;
  outline-offset: 2px;
 (flagged, see note 2)   box-shadow: 0 0 0 6px #0e1420;
}
  1. :focus-visible fires for keyboard focus and stays quiet for mouse clicks — the behaviour people actually wanted.
  2. A second ring in the opposite tone keeps the indicator visible on light and dark backgrounds alike.

Never remove a focus outline without replacing it with something at least as visible. If you cannot see where you are, neither can the person using only a keyboard.

Nunca quites el indicador de foco sin poner algo igual de visible en su lugar. Si tú no ves dónde estás, quien navega solo con teclado tampoco.

05 of 05

A form with no labels

Placeholder text looks like a label until you start typing and it vanishes — taking the only instruction with it.

  • WCAG 1.3.1 Info and Relationships
  • WCAG 3.3.2 Labels or Instructions

example.com

Tab into each field. The broken version announces "edit text" three times with no way to tell them apart.

What breaks it

 (flagged, see note 1) <div class="label">Email</div>
 (flagged, see note 2) <input type="text" placeholder="you@example.com">
 
 (flagged, see note 3) <p class="hint">* Required</p>
  1. A styled div is not a label. Nothing connects it to the input, so it is never announced with the field.
  2. Placeholder-only fields announce as "edit text" and the hint disappears as soon as typing starts.
  3. Required is signalled by a red asterisk alone — colour carrying meaning on its own fails 1.4.1.

What fixes it

 (flagged, see note 1) <label for="email">Email</label>
<input type="email" id="email" name="email"
 (flagged, see note 2)        autocomplete="email"
 (flagged, see note 3)        aria-describedby="email-hint" required>
 
<p id="email-hint">We send one confirmation
   and nothing else.</p>
  1. for and id pair them up, so the label is announced with the field and clicking the label focuses it.
  2. autocomplete lets the browser fill it in — a genuine accessibility win under 1.3.5, not just convenience.
  3. required is announced as a state, and aria-describedby attaches the hint without crowding the label.

Every input needs a real label a screen reader can announce. Placeholder text is a hint, not a label, and it disappears the moment it is needed.

Cada campo necesita una etiqueta real que el lector de pantalla pueda anunciar. El placeholder es una pista, no una etiqueta, y desaparece justo cuando hace falta.