Skip to content
Twenty One Media
webSeptember 30, 2026

The Short Chip, the Full Question

The Prism demo has three question buttons at the bottom of the query dock: "Lead quality," "This week's spend," and "What to fix first." Those labels are short by design. They fit in a chip. They scan quickly. But a screen reader user hearing "Lead quality, toggle button, not pressed" has no idea what pressing that button will do.

The fix is aria-label. Each chip carries the full question as its accessible name:

<button
  aria-label={question.question}
  aria-pressed={active === index}
  aria-controls="prism-answer-region"
  onClick={() => ask(index)}
>
  {question.label}
  <ArrowUpRight size={12} />
</button>

The visible text, "Lead quality," is what sighted users see. The icon has no alt text and carries no meaning on its own. aria-label on the <button> replaces the entire accessible name: screen readers announce "Why aren't these leads turning into jobs?, toggle button, not pressed" instead. The chip text and the icon are both invisible to assistive technology.

This matters because the chip labels were chosen for scan speed, not completeness. "Lead quality" works on screen because the context is a demo about ad performance: the user already knows these are questions about their campaigns. A screen reader user landing on an unfamiliar page does not have that visual scaffolding. The chip label is too thin to stand alone.

We use aria-label rather than a visually hidden <span> for two reasons. First, aria-label on a button completely replaces its accessible name, so there is no risk of the reader announcing both the chip label and the full question. Second, the intent is clear: the chip is a control, and aria-label is the right property for overriding a control's name.

The ArrowUpRight icon is already silent. Lucide icons render as <svg> with no title or aria-label, and the parent button's aria-label covers the whole element. No aria-hidden is needed on the icon because the button label already absorbs it.

The main question button does not need this treatment. It shows the full question text visibly:

<button className="prism-question-main" onClick={() => ask(active ?? 0)}>
  <span>{currentQuestion.question}</span>
  <span className="prism-question-send">
    <ArrowUp size={25} aria-hidden="true" />
    <span className="sr-only">Ask Prism</span>
  </span>
</button>

The full question is already in the DOM as visible content, so the accessible name is correct without any override. The send icon gets aria-hidden="true" to prevent "up arrow" from being read, and a separate sr-only span adds "Ask Prism" as context. Two techniques used together: hiding the icon, supplementing with a short phrase.

Two buttons, two different label strategies: aria-label override when the visible chip is intentionally thin, and sr-only supplement when the visible text is complete but an icon needs context stripped or added.

The rule we apply: if the visible label fully describes the action, leave it alone and add only what the icon omits. If the visible label is shorter than what a user needs to act, override it entirely with aria-label.