Uredi

Deli z drugimi prek


Keyboard accessibility

Keyboard accessibility should be treated as a primary interaction model, not a secondary fallback. A robust keyboard experience supports users who have various disabilities and limitations (including vision, learning, dexterity/mobility, and language/communication disabilities). It also improves productivity for users who prefer keyboard-first interaction for speed and precision.

If keyboard access is incomplete or inconsistent, users can lose access to core app functionality even when pointer interaction appears fully implemented.

Keyboard navigation among UI elements

To interact with a control using the keyboard, controls must be focusable and reachable through focus traversal. To receive focus (without using a pointer), the control must be accessible through tab navigation. By default, the tab order of controls is the same as the order in which they are added to a design surface, declared in XAML, or programmatically added to a container.

In many UIs, this default behavior is acceptable and aligns with reading flow. However, visual order and keyboard order can diverge depending on container layout and child positioning. This divergence should be intentional and tested.

Validate tab behavior explicitly. Grid, table, and similar layouts are common sources of mismatch between perceived reading order and focus order. Test both keyboard-only and touch interaction paths to ensure traversal remains efficient and predictable.

To align traversal with visual flow, you can restructure XAML or explicitly assign TabIndex. The following example uses a Grid with column-first tab sequencing.

<Grid>
  <Grid.RowDefinitions>...</Grid.RowDefinitions>
  <Grid.ColumnDefinitions>...</Grid.ColumnDefinitions>

  <TextBlock Grid.Column="1" HorizontalAlignment="Center">Groom</TextBlock>
  <TextBlock Grid.Column="2" HorizontalAlignment="Center">Bride</TextBlock>

  <TextBlock Grid.Row="1">First name</TextBlock>
  <TextBox x:Name="GroomFirstName" Grid.Row="1" Grid.Column="1" TabIndex="1"/>
  <TextBox x:Name="BrideFirstName" Grid.Row="1" Grid.Column="2" TabIndex="3"/>

  <TextBlock Grid.Row="2">Last name</TextBlock>
  <TextBox x:Name="GroomLastName" Grid.Row="2" Grid.Column="1" TabIndex="2"/>
  <TextBox x:Name="BrideLastName" Grid.Row="2" Grid.Column="2" TabIndex="4"/>
</Grid>

In some scenarios, an element should be excluded from tab traversal. The standard approach is to set IsEnabled to false, which also disables interaction. Disabled controls are automatically removed from tab order.

If an element remains interactive through other mechanisms but should not be reached with Tab, set IsTabStop to false.

Most focus-capable controls are included in tab order by default. A common exception is text-display controls such as RichTextBlock, which can support focus for text selection and clipboard operations but are typically not tab stops because they are not command-invokable controls. These controls can still be discovered by assistive technologies through automation semantics such as the Text control pattern.

Whether you use default traversal or explicit TabIndex, the following rules apply:

  • If TabIndex is not set on an element, the default value is Int32.MaxValue, and order is based on declaration/insertion order.
  • If TabIndex is set on an element:
    • Elements with TabIndex equal to 0 are added based on declaration/insertion order.
    • Elements with TabIndex greater than 0 are added in ascending TabIndex order.
    • Elements with TabIndex less than 0 are added before elements with zero values.

The following snippet shows mixed TabIndex settings (B uses Int32.MaxValue, or 2,147,483,647).

<StackPanel Background="#333">
  <StackPanel Background="#FF33FF">
    <Button>A</Button>
    <Button TabIndex="2147483647">B</Button>
    <Button>C</Button>
  </StackPanel>
  <StackPanel Background="#33FFFF">
    <Button TabIndex="1">D</Button>
    <Button TabIndex="1">E</Button>
    <Button TabIndex="0">F</Button>
  </StackPanel>
</StackPanel>

This produces the following tab order:

  1. F
  2. D
  3. E
  4. A
  5. B
  6. C

Keyboard navigation between application panes with F6

An application pane is a prominent task region inside a window. In Microsoft Edge, for example, panes include the address bar, bookmark bar, tab strip, and content area. F6 is commonly used to move focus between these panes with child elements accessible using standard keyboard navigation.

While a compliant keyboard navigation model is the baseline, a usable keyboard navigation model also typically includes:

  • Listening for F6 to move between major UI regions.
  • Providing keyboard shortcuts for high-frequency commands.
  • Providing access keys for important controls.

See Keyboard shortcuts and Access keys for implementation guidance.

Optimize for F6

F6 significantly reduces traversal cost by letting users jump between major regions rather than tab through every child control.

For example, F6 in Microsoft Edge cycles between the address bar, bookmark bar, tab strip, and content. Because a page can contain many tab stops, this makes common navigation tasks much more efficient.

The F6 sequence can align with landmarks or headings, but it does not need to match exactly. Use F6 for broad region-level movement and landmarks/headings for semantic structure within and across regions.

Important

You must implement F6 navigation explicitly in your app; it is not provided automatically.

Where possible, each F6 target region should have a clear accessible name, either from landmark semantics or by setting AutomationProperties.Name on the region root.

Shift+F6 should traverse the same cycle in reverse order.

Keyboard navigation within a UI element

Composite controls should provide predictable inner navigation among child elements. A common pattern is to keep the composite root in tab order and manage active descendants internally, rather than exposing every child as a separate tab stop.

Many built-in controls already implement this behavior. For example, arrow-key traversal is available by default for ListView, GridView, ListBox, and FlipView.

Keyboard alternatives to pointer actions and events for specific control elements

Any UI that can be activated by pointer should also be invokable by keyboard. Activation requires the element have focus (only classes that derive from Control support focus and tab navigation).

For controls that can be invoked, implement keyboard event handlers for the Spacebar and Enter keys. This provides basic keyboard parity with pointer interactions.

If an element is not focus-capable by default, either use a focusable control type or implement a custom control with explicit focus behavior. In that case, set IsTabStop to true and provide a visible focus indicator.

In many cases, composition is simpler and more robust than custom pointer-only behavior. For example, instead of handling pointer input directly on an Image, place it inside a Button to inherit keyboard activation, focus handling, and automation behavior.

<!--Don't do this.-->
<Image Source="sample.jpg" PointerPressed="Image_PointerPressed"/>

<!--Do this instead.-->
<Button Click="Button_Click"
        AutomationProperties.Name="Open profile photo">
  <Image Source="Assets/profile-photo.png"/>
</Button>

Keyboard shortcuts

In addition to navigation and activation, implement a shortcut (a key combination that provides efficient access to app functionality) for important or frequently used commands with keyboard accelerators and access keys.

Two common types of shortcut are:

  • Accelerators: invoke commands directly, with or without a corresponding visible control.
  • Access keys: move focus to specific controls in your UI.

Always make shortcuts discoverable for users of assistive technology. Communicate them through tooltips, automation metadata, visible UI affordances, and help documentation.

To expose shortcut metadata to assistive technologies, use AutomationProperties.AccessKey for mnemonic shortcuts and AutomationProperties.AcceleratorKey for command shortcuts. Because screen readers may present these similarly, document shortcuts in multiple channels.

The following example demonstrates how to document shortcut keys for media play, pause, and stop buttons.

<Grid>

  <Grid.RowDefinitions>
    <RowDefinition Height="Auto" />
    <RowDefinition Height="Auto" />
  </Grid.RowDefinitions>

  <MediaPlayerElement x:Name="DemoPlayer"
    Width="500" Height="300" Margin="20"
    HorizontalAlignment="Center"
    AutoPlay="False"
    AreTransportControlsEnabled="True" />

  <StackPanel Grid.Row="1" Margin="10"
    Orientation="Horizontal" HorizontalAlignment="Center">

    <Button x:Name="PlayButton" Click="MediaButton_Click"
      ToolTipService.ToolTip="Shortcut key: Ctrl+P"
      AutomationProperties.AcceleratorKey="Ctrl+P"
      AutomationProperties.AccessKey="Alt+P">
      <Button.KeyboardAccelerators>
        <KeyboardAccelerator Modifiers="Control" Key="P"/>
      </Button.KeyboardAccelerators>
      <TextBlock>Play</TextBlock>
    </Button>

    <Button x:Name="PauseButton" Click="MediaButton_Click"
      ToolTipService.ToolTip="Shortcut key: Ctrl+A"
      AutomationProperties.AcceleratorKey="Ctrl+A"
      AutomationProperties.AccessKey="Alt+A">
      <Button.KeyboardAccelerators>
        <KeyboardAccelerator Modifiers="Control" Key="A"/>
      </Button.KeyboardAccelerators>
      <TextBlock>Pause</TextBlock>
    </Button>

    <Button x:Name="StopButton" Click="MediaButton_Click"
      ToolTipService.ToolTip="Shortcut key: Ctrl+S"
      AutomationProperties.AcceleratorKey="Ctrl+S"
      AutomationProperties.AccessKey="Alt+S">
      <Button.KeyboardAccelerators>
        <KeyboardAccelerator Modifiers="Control" Key="S"/>
      </Button.KeyboardAccelerators>
      <TextBlock>Stop</TextBlock>
    </Button>
  </StackPanel>
</Grid>

Important

Setting AutomationProperties.AcceleratorKey or AutomationProperties.AccessKey does not implement keyboard behavior. These properties expose metadata to UI Automation so assistive technologies can announce the expected shortcuts.

Keyboard behavior must still be implemented in code. Use declarative KeyboardAccelerator definitions when possible, and use KeyDown or KeyUp handlers where you need custom routing logic. Also note that access-key underline styling is not automatic. If you want visible mnemonic underlines, render them explicitly (for example, with Underline).

For brevity, the sample omits string resources such as "Ctrl+A". In production, localize shortcut text and validate mnemonic choices per locale, since key selection often depends on translated labels.

For additional guidance, see Shortcut keys in the Windows User Experience Interaction Guidelines.

Implementing a key event handler

Key input uses routed events. Routed events can bubble from children to a parent container, which allows the parent to process shortcuts for multiple descendant elements. This event model is convenient for defining shortcut key actions for a control that contains several child elements, none of which can have focus or be part of the tab order.

For code examples that include modifier-key checks (for example, Ctrl), see Keyboard interactions.

Keyboard navigation for custom controls

For custom controls, use arrow keys when child elements are spatially related. In tree scenarios where expand/collapse and activation are separate interactions, map left and right arrows to expand/collapse behavior. For oriented controls, map directional keys to the control's visual orientation.

Custom key behavior is commonly implemented by overriding OnKeyDown and OnKeyUp.

An example of a visual state for a focus indicator

Any focusable custom control should expose a clear visual focus indicator. A common template pattern uses a Rectangle overlay that starts hidden via Visibility and appears when focus enters.

Built-in XAML controls already provide focus indicators. The exact appearance can vary with theme settings, including high contrast mode. If you retemplate controls, preserve equivalent focus visibility behavior.

The following example is adapted from the default Button template.

<ControlTemplate TargetType="Button">
...
    <Rectangle
      x:Name="FocusVisualWhite"
      IsHitTestVisible="False"
      Stroke="{ThemeResource FocusVisualWhiteStrokeThemeBrush}"
      StrokeEndLineCap="Square"
      StrokeDashArray="1,1"
      Opacity="0"
      StrokeDashOffset="1.5"/>
    <Rectangle
      x:Name="FocusVisualBlack"
      IsHitTestVisible="False"
      Stroke="{ThemeResource FocusVisualBlackStrokeThemeBrush}"
      StrokeEndLineCap="Square"
      StrokeDashArray="1,1"
      Opacity="0"
      StrokeDashOffset="0.5"/>
...
</ControlTemplate>

To toggle focus-indicator visibility, use VisualStateManager and VisualStateManager.VisualStateGroups on the template root.

<ControlTemplate TargetType="Button">
  <Grid>
    <VisualStateManager.VisualStateGroups>
      <!--other visual state groups here-->
      <VisualStateGroup x:Name="FocusStates">
        <VisualState x:Name="Focused">
          <Storyboard>
            <DoubleAnimation
              Storyboard.TargetName="FocusVisualWhite"
              Storyboard.TargetProperty="Opacity"
              To="1" Duration="0"/>
            <DoubleAnimation
              Storyboard.TargetName="FocusVisualBlack"
              Storyboard.TargetProperty="Opacity"
              To="1" Duration="0"/>
          </Storyboard>
        </VisualState>
        <VisualState x:Name="Unfocused" />
        <VisualState x:Name="PointerFocused" />
      </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>

    <!--composition is here-->
  </Grid>
</ControlTemplate>

Only one state in this group explicitly modifies the focus visual. The other states can remain empty because transitions inside the same VisualStateGroup cancel prior state animations. Focus events such as GotFocus, combined with GoToState, typically drive these transitions.

Keyboard accessibility and devices without a hardware keyboard

Some devices rely on a Soft Input Panel (SIP) instead of a hardware keyboard. Screen readers can detect that the user is scanning keys and announce a user's SIP key exploration, and many keyboard accessibility concepts still apply through gesture equivalents.

For example, even without a physical Tab key, Narrator supports gestures that map to Tab-like traversal. That means coherent tab order is still critical. Narrator also provides gesture equivalents for directional navigation in complex controls (see Narrator keyboard commands and touch gestures).

Examples

WinUI 3 Gallery icon The WinUI 3 Gallery app includes interactive examples of WinUI controls and features. Get the app from the Microsoft Store or browse the source code on GitHub.