Skip to content

Tab navigation skips FocusScope elements outside visible viewport in Flickable/ScrollView #10321

@AlexeyMatskevich

Description

@AlexeyMatskevich

Bug Description

Description

When using keyboard Tab navigation in a Flickable or ScrollView, FocusScope elements that are outside the visible viewport are skipped entirely. The focus jumps only between visible elements, making it impossible to navigate to off-screen focusable content via keyboard.

Expected Behavior

Tab navigation should cycle through all focusable elements in document order, automatically scrolling the viewport to bring the newly focused element into view (similar to browser behavior with focusable elements in scrollable containers).

Steps to Reproduce

  1. Run the example above
  2. Click on "Button 1" to give it focus
  3. Press Tab repeatedly to navigate through buttons
  4. Observe that Tab only cycles through buttons visible in the viewport
  5. Buttons below the fold (Button 5-10) are never reached via Tab

Actual Behavior

  • Tab navigation only visits FocusScope elements within the visible viewport
  • Off-screen focusable elements are skipped entirely
  • No automatic scrolling occurs to bring focused elements into view

Workarounds Attempted (None Successful)

  1. focus-changed-event callback: Never fires for off-screen elements since they are skipped
  2. Custom Tab interception at parent level: Slint's internal focus management runs before user code
  3. Programmatic focus() calls: Element must be visible first for focus to work

Suggested Solution

When Tab navigation moves focus to the next FocusScope, automatically scroll the containing Flickable/ScrollView to ensure the focused element is visible. This could be similar to browser behavior where focusing an element via Tab automatically scrolls it into view.

Reproducible Code (if applicable)

import { VerticalBox } from "std-widgets.slint";

component FocusableButton inherits Rectangle {
    in property <string> label;
    
    width: 200px;
    height: 50px;
    background: focus-scope.has-focus ? #4f39f6 : #e5e5e5;
    border-radius: 8px;
    
    focus-scope := FocusScope {
        width: 100%;
        height: 100%;
        
        key-pressed(event) => {
            if event.text == Key.Return {
                debug("Button pressed:", root.label);
                return accept;
            }
            reject
        }
    }
    
    Text {
        text: root.label;
        color: focus-scope.has-focus ? white : black;
    }
}

export component App inherits Window {
    width: 400px;
    height: 300px;
    
    Flickable {
        width: 100%;
        height: 100%;
        viewport-height: 800px;
        
        VerticalBox {
            padding: 20px;
            spacing: 20px;
            
            FocusableButton { label: "Button 1"; }
            FocusableButton { label: "Button 2"; }
            FocusableButton { label: "Button 3"; }
            FocusableButton { label: "Button 4"; }
            FocusableButton { label: "Button 5"; }
            FocusableButton { label: "Button 6"; }
            FocusableButton { label: "Button 7"; }
            FocusableButton { label: "Button 8"; }
            FocusableButton { label: "Button 9"; }
            FocusableButton { label: "Button 10"; }
        }
    }
}

Environment Details

  • Slint Version: 1.9.2
  • Platform/OS: macOS / Linux (NixOS)
  • Programming Language: Rust
  • Backend/Renderer: Skia OpenGL

Product Impact

This significantly affects accessibility for keyboard-only users. Any scrollable content with focusable elements becomes partially inaccessible via keyboard navigation.

Metadata

Metadata

Assignees

Labels

a:builtin elementsThe runtime data structures related to the items (mO,bT)

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions