feat: introduce ClickableRegion component to improve keyboard accessibility for interaction editors - #6094
Conversation
|
@AlexVelezLl, Should we add this to other interactions as well? |
|
Hi @Abhishek-Punhani! Yes, let's use this for the textEntry prompt, and I just merged the ordering interaction PR; we can refactor that editor as well! |
…bility for interaction editors Signed-off-by: Abhishek-Punhani <punhani.manavabhi@gmail.com>
ae28f15 to
ba7e840
Compare
AlexVelezLl
left a comment
There was a problem hiding this comment.
Thanks @Abhishek-Punhani! It's really nice these button are finally accessible! I've found couple of things we might be able to simplify on the code.
|
|
||
| <div | ||
| class="clickable-area" | ||
| @click="onClick" |
There was a problem hiding this comment.
The @click should be on the button element.
| class="overlay-button" | ||
| :aria-label="ariaLabel" | ||
| @keydown.enter.prevent="onClick" | ||
| @keydown.space.prevent="onClick" |
There was a problem hiding this comment.
If we add @click, we don't need to specify these two, as for button elements, the space and enter keystrokes also fire the onClick handler on a button element.
| class="clickable-area" | ||
| @click="onClick" | ||
| > | ||
| <button |
There was a problem hiding this comment.
This is working great for keyboard navigation, but now for click interactions on prompts (this does not happen on options), we should have a slight background color change on hover, and we should have a cursor: pointer.
Grabacion.de.pantalla.2026-08-19.a.la.s.4.33.08.a.m.mov
| <button | ||
| v-if="!suppressed" | ||
| type="button" | ||
| class="overlay-button" |
There was a problem hiding this comment.
Oh, another small thing: the focus outline for this is blue-ish, but it should be the same color we have on $coreOutline
Outline color now:
Correct outline color
| :minHeight="'80px'" | ||
| :autofocus="mode === 'edit' && isQuestionOpen" | ||
| :imageProcessor="EditorImageProcessor" | ||
| :tabindex="isQuestionOpen ? 0 : -1" |
There was a problem hiding this comment.
I don't think we need these tab indexes; if it's open, tabindex should also be -1 because we can already navigate through the editor to the input. (i.e., it should always be -1, as we are already handling the tab index in the outer container).
| function handlePromptClick(event) { | ||
| if (props.mode !== 'edit') return; | ||
| if (event.target.closest('button') || event.target.closest('input')) return; | ||
| const closestBtn = | ||
| event.target && event.target.closest ? event.target.closest('button') : null; | ||
| if (closestBtn && !closestBtn.classList.contains('overlay-button')) return; | ||
| const closestInput = | ||
| event.target && event.target.closest ? event.target.closest('input') : null; | ||
| if (closestInput) return; | ||
| if (!isQuestionOpen.value) { | ||
| event.stopPropagation(); | ||
| if (event && event.stopPropagation) event.stopPropagation(); | ||
| openQuestion(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Why do we need all of this? It's even less needed now because when the editor is already open, the Clickable region cannot fire any click event anymore, right?
| return { | ||
| borderColor: questionHasError.value ? tokens.error : tokens.fineLine, | ||
| cursor: props.mode === 'edit' ? 'pointer' : undefined, | ||
| '--clickable-region-hover-bg': palette.blue.v_100, |
There was a problem hiding this comment.
Hmm, I don't think we are using this anywhere.
| const closestBtn = | ||
| event.target && event.target.closest ? event.target.closest('button') : null; | ||
| if (closestBtn && !closestBtn.classList.contains('overlay-button')) return; | ||
| const closestInput = | ||
| event.target && event.target.closest ? event.target.closest('input') : null; | ||
| if (closestInput) return; | ||
| if (!isPromptOpen.value) { | ||
| event.stopPropagation(); | ||
| if (event && event.stopPropagation) event.stopPropagation(); | ||
| openPrompt(); | ||
| } | ||
| } | ||
|
|
||
| function handleItemClick(event, itemId) { | ||
| if (props.mode !== 'edit') return; | ||
| if (openItemId.value === itemId) return; | ||
| if (event.target.closest('button') || event.target.closest('input')) return; | ||
| event.stopPropagation(); | ||
| const closestBtn = | ||
| event.target && event.target.closest ? event.target.closest('button') : null; | ||
| if (closestBtn && !closestBtn.classList.contains('overlay-button')) return; | ||
| const closestInput = | ||
| event.target && event.target.closest ? event.target.closest('input') : null; | ||
| if (closestInput) return; | ||
| if (event && event.stopPropagation) event.stopPropagation(); |
| function handlePromptClick(event) { | ||
| if (props.mode !== 'edit') return; | ||
| if (event.target.closest('button') || event.target.closest('input')) return; | ||
| const closestBtn = | ||
| event.target && event.target.closest ? event.target.closest('button') : null; | ||
| if (closestBtn && !closestBtn.classList.contains('overlay-button')) return; | ||
| const closestInput = | ||
| event.target && event.target.closest ? event.target.closest('input') : null; | ||
| if (closestInput) return; |
Summary
Adding
ClickableRegioncomponent to improve keyboard accessibility for interaction editorsReferences
Closes #6043
Reviewer guidance
Navigate to the Qti-demo-page and and test navigating Choice Editor prompt/choice cards using keyboard (
Tab,Enter/Space) to verify accessiblearia-labelannouncements, focus outlines, and seamless interaction with nested controls (TipTap, selection inputs, action buttons) without triggering parent clicks.AI usage
Used Antigravity for final review and nitpicks.