[iOS][FKA] Full Keyboard Access does not scroll to offscreen Semantics nodes in SingleChildScrollView and does not trigger focus callbacks
- Dominant language
- Dart
- Stars
- 179k
- Forks
- 31.1k
- PR merge metrics
- PR metrics pending
Description
### Steps to reproduce
1. Create a Flutter app with a `SingleChildScrollView` containing enough focusable controls to require scrolling.
2. Add visible controls at the top and additional controls below the initial viewport.
3. Wrap some controls in `Semantics(
button: true,
focusable: true,
onTap: ...,
onFocus: ...,
onDidGainAccessibilityFocus: ...,
)`.
4. Run the app on iOS with a physical/external keyboard.
5. Enable iOS Full Keyboard Access.
6. Navigate using `Tab` / `Shift+Tab`.
7. Try to reach controls that are below the visible part of the `SingleChildScrollView`.
8. Try activating a visible Semantics node with Space/Enter.
### Expected results
Full Keyboard Access should be able to navigate to all focusable/semantic controls in the scroll view.
When focus moves to an offscreen control, Flutter should either:
- automatically scroll the `SingleChildScrollView` to reveal the focused semantic node, or
- dispatch a focus/accessibility callback such as `Semantics.onFocus` or `Semantics.onDidGainAccessibilityFocus`, so the app can call `Scrollable.ensureVisible`.
Keyboard activation should continue to invoke `Semantics.onTap`.
### Actual results
Visible semantic controls can be activated: `Semantics.onTap` is called.
However, while navigating with Full Keyboard Access:
- `Semantics.onFocus` is not called.
- `Semantics.onDidGainAccessibilityFocus` is not called.
- Flutter `FocusNode` / `FocusManager.primaryFocus` does not move to the focused semantic controls.
- `HardwareKeyboard` does not receive `Tab` / `Shift+Tab` while FKA is enabled.
- `Scrollable.ensureVisible` cannot be triggered because Dart receives no focus-change callback.
- Offscreen controls inside `SingleChildScrollView` are not scrolled into view and cannot be reached by keyboard navigation.
This was reproduced both with custom form widgets and with plain `OutlinedButton` widgets wrapped in `Semantics(button: true, focusable: true, onTap, onFocus, onDidGainAccessibilityFocus)`.
### Code sample
Code sample
```dart
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('FKA repro')),
body: SingleChildScrollView(
child: Column(
children: [
for (var index = 1; index <= 20; index++)
Padding(
padding: const EdgeInsets.all(16),
child: Semantics(
container: true,
button: true,
focusable: true,
label: 'FKA probe button $index',
onTap: () => debugPrint('button $index onTap'),
onFocus: () => debugPrint('button $index onFocus'),
onDidGainAccessibilityFocus: () {
debugPrint('button $index onDidGainAccessibilityFocus');
},
child: ExcludeSemantics(
child: OutlinedButton(
onPressed: () =>
debugPrint('button $index onPressed'),
child: Text('FKA probe button $index'),
),
),
),
),
],
),
),
),
);
}
}
```
### Screenshots or Video
Screenshots / Video demonstration
[Upload media here]
### Logs
Logs
```console
[Paste your logs here]
```
### Flutter Doctor output
Doctor output
```console
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.38.10, on macOS 15.3.1 24D70 darwin-arm64, locale pl-PL)
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 16.4)
[✓] Chrome - develop for the web
[✓] Connected device (4 available)
! Error: Browsing on the local area network for iPad. Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac.
The device must be opted into Developer Mode to connect wirelessly. (code -27)
[✓] Network resources
• No issues found!
```
Contributor guide
Assessment
This issue has not been assessed yet.