FN-7613: fix confirm Yes/No button selected-state visibility

Fix confirm Yes/No selected-state visibility in chat questions and add aria-pressed accessibility support.

- Strengthen CSS specificity for .chat-question-response__confirm--selected so the selected style beats the global .btn/.btn:hover rules, using token-driven --cta-* colors for light/dark themes.
- Add dedicated hover and focus-visible states for the selected confirm button.
- Add aria-pressed to the Yes/No confirm buttons so assistive tech reflects the same selected state.
- Add regression tests covering the selected visual/aria state.
- Add changeset (patch) documenting the fix.

Files changed:
 .changeset/fn-7613-confirm-selected-state.md       |  7 ++++
 .../app/components/ChatQuestionResponse.css        | 40 ++++++++++++++++++++--
 .../app/components/ChatQuestionResponse.tsx        |  8 +++++
 .../__tests__/ChatQuestionResponse.test.tsx        | 26 ++++++++++++++
 4 files changed, 79 insertions(+), 2 deletions(-)

Fusion-Task-Id: FN-7613

Fusion-Task-Lineage: d648044e-ab30-4542-9402-a7bfbfe6563e

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-05 20:54:36 -07:00
parent ed136b1e76
commit 81fbb656ac
4 changed files with 79 additions and 2 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Yes/No chat question buttons now show a clear selected state after clicking.
category: fix
dev: Strengthened `.chat-question-response__confirm--selected` CSS specificity (compound selector + dedicated hover/focus-visible rules) so the CTA-token selected fill/border beats the global `.btn`/`.btn:hover` rules; added `aria-pressed` and a regression test asserting the selected class toggles correctly between Yes/No.

View File

@@ -102,12 +102,48 @@
}
.chat-question-response__option:hover,
.chat-question-response__option--selected,
.chat-question-response__confirm--selected {
.chat-question-response__option--selected {
border-color: var(--accent);
background: color-mix(in srgb, var(--accent) 12%, var(--card));
}
/*
FNXC:ChatQuestionResponse 2026-07-05-00:00:
The confirm Yes/No buttons also carry the global `.btn` class, whose base
background and (especially) `.btn:hover` rule (specificity 0,2,0) visually
swamped the old single-class `.chat-question-response__confirm--selected`
rule (0,1,0), so a clicked answer never looked selected. Use a compound
selector (`.chat-question-response__confirm.chat-question-response__confirm--selected`,
specificity 0,2,0) plus a dedicated `:hover` override so the selected state
beats `.btn`/`.btn:hover` in every interaction state, and keep everything
token-driven (`--cta-*` / `--accent` via color-mix) so light/dark themes and
the mobile column layout stay correct without hardcoded colors.
*/
.chat-question-response__confirm {
border-color: var(--border);
background: var(--card);
color: var(--text);
font-weight: 500;
}
.chat-question-response__confirm.chat-question-response__confirm--selected {
border-color: var(--cta-border);
background: color-mix(in srgb, var(--cta-bg) 55%, var(--card));
color: var(--cta-text);
font-weight: 700;
box-shadow: var(--cta-glow), inset 0 0 0 var(--btn-border-width) var(--cta-border);
}
.chat-question-response__confirm.chat-question-response__confirm--selected:hover {
background: color-mix(in srgb, var(--cta-bg-hover) 60%, var(--card));
border-color: var(--cta-border-hover);
color: var(--cta-text);
}
.chat-question-response__confirm.chat-question-response__confirm--selected:focus-visible {
box-shadow: var(--focus-ring-strong), var(--cta-glow);
}
.chat-question-response__option input {
margin: calc(var(--space-xs) / 2) 0 0;
accent-color: var(--accent);

View File

@@ -168,11 +168,18 @@ function QuestionControls({
if (question.type === "confirm") {
return (
<div className="chat-question-response__confirm-group" role="group" aria-label={question.question}>
{/*
FNXC:ChatQuestionResponse 2026-07-05-00:00:
Expose the confirm selection to assistive tech via aria-pressed so
screen reader users get the same clear selected/unselected signal
the strengthened CSS now provides visually.
*/}
<button
type="button"
className={`btn chat-question-response__confirm${value === true ? " chat-question-response__confirm--selected" : ""}`}
data-testid={`chat-question-response-option-${question.id}-yes`}
disabled={disabled}
aria-pressed={value === true}
onClick={() => setQuestionAnswer(question.id, true)}
>
{t("chat.questionConfirmYes", "Yes")}
@@ -182,6 +189,7 @@ function QuestionControls({
className={`btn chat-question-response__confirm${value === false ? " chat-question-response__confirm--selected" : ""}`}
data-testid={`chat-question-response-option-${question.id}-no`}
disabled={disabled}
aria-pressed={value === false}
onClick={() => setQuestionAnswer(question.id, false)}
>
{t("chat.questionConfirmNo", "No")}

View File

@@ -60,6 +60,32 @@ describe("ChatQuestionResponse", () => {
}
});
it("marks the clicked confirm option as visually selected and moves the marker on re-click (FN-7613)", async () => {
const user = userEvent.setup();
render(<ChatQuestionResponse parsed={parsed} onSubmit={vi.fn()} />);
const yesButton = screen.getByTestId("chat-question-response-option-confirm-yes");
const noButton = screen.getByTestId("chat-question-response-option-confirm-no");
// Neither selected before any click.
expect(yesButton).not.toHaveClass("chat-question-response__confirm--selected");
expect(noButton).not.toHaveClass("chat-question-response__confirm--selected");
expect(yesButton).toHaveAttribute("aria-pressed", "false");
expect(noButton).toHaveAttribute("aria-pressed", "false");
await user.click(yesButton);
expect(yesButton).toHaveClass("chat-question-response__confirm--selected");
expect(noButton).not.toHaveClass("chat-question-response__confirm--selected");
expect(yesButton).toHaveAttribute("aria-pressed", "true");
expect(noButton).toHaveAttribute("aria-pressed", "false");
await user.click(noButton);
expect(noButton).toHaveClass("chat-question-response__confirm--selected");
expect(yesButton).not.toHaveClass("chat-question-response__confirm--selected");
expect(noButton).toHaveAttribute("aria-pressed", "true");
expect(yesButton).toHaveAttribute("aria-pressed", "false");
});
it("supports compact mode", () => {
render(<ChatQuestionResponse parsed={{ questions: [parsed.questions[0]!] }} compact onSubmit={vi.fn()} />);
expect(screen.getByTestId("chat-question-response")).toHaveClass("chat-question-response--compact");