Skip to content

fix(a2ui_flutter): name the slider by its label - #3411

Open
diegolopezrm wants to merge 5 commits into
a2ui-project:mainfrom
diegolopezrm:fix/a2ui-flutter-slider-label
Open

diegolopezrm wants to merge 5 commits into
a2ui-project:mainfrom
diegolopezrm:fix/a2ui-flutter-slider-label

Conversation

@diegolopezrm

Copy link
Copy Markdown
Contributor

Follow-up to #3088, @andrewkolos.

Flutter's Slider uses its label argument, the value bubble's text, as the slider's name, so a screen reader said "40, 40%" and read "Volume" as a separate line. The caption now names the slider, and the value is read as it's shown. The trade-off is the bubble: Flutter reads its text as the name, so keeping it would undo the fix. It's gone, and the value stays on screen above the track. Two semantics tests cover it, and they fail without the change.

Flutter's Slider reads its label argument, the value bubble's text, as the
slider's name, so the slider announced "4" with a value of "40%", and the
caption above it was a separate node. The caption now names the node that
carries the value and the actions, the value is read as it is shown, and
the caption and the value text above the track are left out of semantics so
nothing is said twice.
@github-actions github-actions Bot added the status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs label Oct 22, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request improves the accessibility of the Slider component by wrapping the visual label and value in ExcludeSemantics to prevent screen readers from announcing them twice, and wrapping the Slider in MergeSemantics and Semantics with the label and a semantic formatter callback. It also adds comprehensive semantic tests. The review feedback suggests making the label variable nullable (String?) instead of defaulting to an empty string, ensuring that null is passed to Semantics when a label is omitted while still defaulting to an empty string for the visual Text widget.

: null;
final double raw = props.number('value') ?? min;
final double value = raw.isFinite ? raw.clamp(min, max) : min;
final String label = props.string('label') ?? '';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using a nullable String? for label is preferred here. If the label is omitted or empty, passing null to Semantics is better than passing an empty string '', as some screen readers might announce an empty string or treat the widget as unlabelled. We can default to '' only when rendering the Text widget.

Suggested change
final String label = props.string('label') ?? '';
final String? label = props.string('label');

Comment on lines +62 to +76
ExcludeSemantics(
child: Row(
children: [
Expanded(child: Text(label, style: theme.textTheme.titleSmall)),
Text(valueText, style: theme.textTheme.bodySmall),
],
),
),
Slider(
value: value,
min: min,
max: max,
divisions: divisions,
label: valueText,
onChanged: write == null || !range.isFinite
? null
: (moved) {
final double snapped = divisions == null
? moved
: math.min(max, min + (moved - min).round());
write(
snapped == snapped.roundToDouble() &&
snapped.abs() < _twoTo53
? snapped.toInt()
: snapped,
);
},
// The label names the node that carries the value and the actions.
// Flutter's own `label` is the value bubble, and it would be
// announced as the slider's name.
MergeSemantics(
child: Semantics(
label: label,
child: Slider(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Update the Text widget to use label ?? '' since label is now nullable, and pass the nullable label directly to Semantics.

Suggested change
ExcludeSemantics(
child: Row(
children: [
Expanded(child: Text(label, style: theme.textTheme.titleSmall)),
Text(valueText, style: theme.textTheme.bodySmall),
],
),
),
Slider(
value: value,
min: min,
max: max,
divisions: divisions,
label: valueText,
onChanged: write == null || !range.isFinite
? null
: (moved) {
final double snapped = divisions == null
? moved
: math.min(max, min + (moved - min).round());
write(
snapped == snapped.roundToDouble() &&
snapped.abs() < _twoTo53
? snapped.toInt()
: snapped,
);
},
// The label names the node that carries the value and the actions.
// Flutter's own `label` is the value bubble, and it would be
// announced as the slider's name.
MergeSemantics(
child: Semantics(
label: label,
child: Slider(
ExcludeSemantics(
child: Row(
children: [
Expanded(child: Text(label ?? '', style: theme.textTheme.titleSmall)),
Text(valueText, style: theme.textTheme.bodySmall),
],
),
),
// The label names the node that carries the value and the actions.
// Flutter's own 'label' is the value bubble, and it would be
// announced as the slider's name.
MergeSemantics(
child: Semantics(
label: label,
child: Slider(

@github-project-automation github-project-automation Bot moved this to Todo in A2UI Oct 22, 2026
Keeps the semantics wrapper from this branch around main's Slider, which
now moves in its v1.0 `steps`, and moves the CHANGELOG line from the
released 0.0.1-wip003 section to Unreleased.

⚠️ Agent-generated
min: min,
max: max,
divisions: divisions,
semanticFormatterCallback: (v) => ComponentProps.asString(v)!,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that v is floating point number, ComponentProps.asString is going to produce values like 0.3000000004 or whatnot instead of 0.3. Would it be best to run the value through the same snapping logic that onChanged uses (after it's extracted out into a snap function or something similar).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, done in 5b3e027. The snapping in onChanged is now a snap function and the semantics formatter uses it too, so the slider announces what a move would write. It was more than cosmetic: on a 0 to 100 slider the value below 30 came out as 28.999999999999996. Added a test for that and one for fractional steps on v1.0. I also merged main and moved the changelog line under a new Unreleased heading, since 0.2.0 shipped without it.

@github-actions github-actions Bot removed the status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs label Nov 2, 2026
@github-actions github-actions Bot added the status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs label Nov 10, 2026
…ider-label

# Conflicts:
#	dart/a2ui_flutter/CHANGELOG.md
@polina-c

Copy link
Copy Markdown
Collaborator

From triage bot:

Triage suggestion:

For humans

Summary:
The PR makes the Slider in a2ui_flutter announce its label as its name and the value it shows, instead of reading the value bubble as its name. It removes the value bubble, since Flutter reads the bubble's text as the slider's name, and adds semantics tests.

Why flagged for triage:
This external PR has no assignee.

Suggestion:
Review the PR and assign andrewkolos. They wrote #3088, which added the v0.9 basic catalog with this slider, and left a review comment that the author addressed on 2026-11-10. The PR meets the review criteria: it is a small accessibility fix (143 lines added) with semantics tests, and the change stays inside the slider. The only overlap is #4108 by the same author, which also changes slider.dart and adds the same ## Unreleased heading to the changelog, so the second one to land needs a rebase. GitHub already reports a merge conflict with main.

  1. Add labels: type: bug, component: flutter adapter, lang: dart
  2. Assign: andrewkolos
  3. Related PRs: test(a2ui_flutter): cover the properties an agent gets wrong #4108

To adjust and apply this suggestion, run in your a2ui checkout:

tools/internal/triage_new/triage_ui.sh https://github.com/a2ui-project/a2ui/pull/3411
For software
<a2ui>
root = TriageSuggestion(
  summary="""The PR makes the `Slider` in `a2ui_flutter` announce its `label` as its name and the value it shows, instead of reading the value bubble as its name. It removes the value bubble, since Flutter reads the bubble's text as the slider's name, and adds semantics tests.""",
  reason="""This external PR has no assignee.""",
  suggestion="""Review the PR and assign `andrewkolos`. They wrote #3088, which added the v0.9 basic catalog with this [slider](https://github.com/a2ui-project/a2ui/blob/8bf644a37d680a227296b8c0849a7f4b328b8b73/dart/a2ui_flutter/lib/src/catalog/basic/slider.dart), and left a [review comment](https://github.com/a2ui-project/a2ui/pull/3411#discussion_r4439093310) that the author addressed on 2026-11-10. The PR meets the review criteria: it is a small accessibility fix (143 lines added) with semantics tests, and the change stays inside the slider. The only overlap is #4108 by the same author, which also changes `slider.dart` and adds the same `## Unreleased` heading to the [changelog](https://github.com/a2ui-project/a2ui/blob/8bf644a37d680a227296b8c0849a7f4b328b8b73/dart/a2ui_flutter/CHANGELOG.md), so the second one to land needs a rebase. GitHub already reports a merge conflict with `main`.""",
  added_labels=["type: bug", "component: flutter adapter", "lang: dart"],
  new_assignees=["andrewkolos"],
  related_prs=[4108]
)
</a2ui>

🤖 Agent-generated

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: needs-triage auto-managed: https://github.com/a2ui-project/a2ui/blob/main/scripts/triage.mjs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants