Overview
Flowery.Uno controls are built with accessibility in mind, providing keyboard navigation, screen reader support, and ARIA properties out of the box. This guide covers accessibility features and best practices for building inclusive applications.
Built-In Accessibility
AutomationProperties Support
All Flowery.Uno controls support standard WinUI AutomationProperties for screen readers:
< daisy:DaisyButton Content = "Save Document"
AutomationProperties.Name = "Save Document"
AutomationProperties.HelpText = "Saves the current document to disk" />
AccessibleText Attached Property
Flowery.Uno provides a simplified AccessibleText attached property:
xmlns:services="using:Flowery.Services"
< daisy:DaisyButton Content = "💾"
services:DaisyAccessibility.AccessibleText = "Save Document" />
This automatically sets AutomationProperties.Name for screen readers.
Keyboard Navigation
Tab Order
All interactive controls support TabIndex for custom tab order:
< StackPanel >
< daisy:DaisyInput PlaceholderText = "First Name" TabIndex = "1" />
< daisy:DaisyInput PlaceholderText = "Last Name" TabIndex = "2" />
< daisy:DaisyInput PlaceholderText = "Email" TabIndex = "3" />
< daisy:DaisyButton Content = "Submit" TabIndex = "4" />
</ StackPanel >
Controls with TabIndex="0" are included in natural tab order. Negative values exclude elements from tab navigation.
Keyboard Shortcuts
Use AccessKey for keyboard shortcuts:
< daisy:DaisyButton Content = "_Save"
AccessKey = "S"
Click = "SaveButton_Click" />
< daisy:DaisyButton Content = "_Cancel"
AccessKey = "C"
Click = "CancelButton_Click" />
Users can activate these with Alt + S and Alt + C on Windows.
Enter and Space Key Handling
Flowery.Uno controls support Enter/Space key activation:
private void OnKeyDown ( object sender , KeyRoutedEventArgs e )
{
// Flowery helper handles Enter/Space activation
DaisyAccessibility . TryHandleEnterOrSpace ( e , () =>
{
// Perform action
PerformAction ();
});
}
Screen Reader Support
Automatic Name Resolution
Controls automatically derive accessible names from their content:
<!-- AutomationProperties.Name = "Submit Form" -->
< daisy:DaisyButton Content = "Submit Form" />
<!-- AutomationProperties.Name = "Enable Notifications" -->
< daisy:DaisyToggle Header = "Enable Notifications" />
<!-- AutomationProperties.Name = "User Email" -->
< daisy:DaisyInput PlaceholderText = "User Email" />
Custom Automation Names
Provide explicit names when content is not descriptive:
<!-- Icon-only button needs accessible name -->
< daisy:DaisyButton IconSymbol = "Save"
AutomationProperties.Name = "Save Document" />
<!-- Complex content needs clear name -->
< daisy:DaisyCard AutomationProperties.Name = "User Profile Card" >
< StackPanel >
< Image Source = "avatar.png" />
< TextBlock Text = "John Doe" />
< TextBlock Text = "Software Engineer" />
</ StackPanel >
</ daisy:DaisyCard >
Live Regions
Use AutomationProperties.LiveSetting for dynamic content:
< daisy:DaisyAlert Variant = "Success"
AutomationProperties.LiveSetting = "Polite" >
< TextBlock Text = "Document saved successfully" />
</ daisy:DaisyAlert >
< daisy:DaisyAlert Variant = "Error"
AutomationProperties.LiveSetting = "Assertive" >
< TextBlock Text = "Failed to save document" />
</ daisy:DaisyAlert >
Live Setting Values:
Off – No announcements (default)
Polite – Announces when screen reader is idle
Assertive – Interrupts screen reader immediately
Control-Specific Accessibility
< daisy:DaisyButton Content = "Delete"
Variant = "Error"
AutomationProperties.Name = "Delete Item"
AutomationProperties.HelpText = "Permanently deletes the selected item" />
< StackPanel >
< TextBlock Text = "Email Address"
x:Name = "EmailLabel" />
< daisy:DaisyInput PlaceholderText = "[email protected] "
AutomationProperties.LabeledBy = "{x:Bind EmailLabel}" />
</ StackPanel >
Toggles
< daisy:DaisyToggle Header = "Dark Mode"
IsOn = "{x:Bind ViewModel.IsDarkMode, Mode=TwoWay}"
AutomationProperties.HelpText = "Switches between light and dark themes" />
Screen readers announce: “Dark Mode, toggle switch, on/off”.
Checkboxes
< daisy:DaisyCheckBox Content = "I agree to the terms and conditions"
IsChecked = "{x:Bind ViewModel.AgreeToTerms, Mode=TwoWay}"
AutomationProperties.HelpText = "You must agree to continue" />
< StackPanel AutomationProperties.Name = "Payment Method" >
< daisy:DaisyRadio Content = "Credit Card"
GroupName = "PaymentMethod"
IsChecked = "True" />
< daisy:DaisyRadio Content = "PayPal"
GroupName = "PaymentMethod" />
< daisy:DaisyRadio Content = "Bank Transfer"
GroupName = "PaymentMethod" />
</ StackPanel >
Progress Indicators
< daisy:DaisyProgress Value = "{x:Bind ViewModel.UploadProgress, Mode=OneWay}"
Maximum = "100"
AutomationProperties.Name = "Upload Progress"
AutomationProperties.LiveSetting = "Polite" />
Modals and Dialogs
Flowery.Uno dialogs automatically manage focus:
// DaisyModal automatically:
// - Traps focus within dialog
// - Returns focus on close
// - Announces dialog title to screen readers
var result = await DaisyModal . ShowAsync (
title : "Confirm Delete" ,
message : "Are you sure you want to delete this item?" ,
primaryButtonText : "Delete" ,
secondaryButtonText : "Cancel"
);
Focus Management
Focus Visible
All interactive controls show focus indicators:
<!-- Focus ring automatically appears on keyboard navigation -->
< daisy:DaisyButton Content = "Focused Button" />
Programmatic Focus
Set focus explicitly when needed:
// Set focus to first input on page load
private void Page_Loaded ( object sender , RoutedEventArgs e )
{
FirstNameInput . Focus ( FocusState . Programmatic );
}
// Use helper for pointer-based focus
DaisyAccessibility . FocusOnPointer ( MyButton );
Focus Scope
Use XYFocusKeyboardNavigation for custom focus regions:
< Grid XYFocusKeyboardNavigation = "Enabled" >
< Grid.RowDefinitions >
< RowDefinition Height = "Auto" />
< RowDefinition Height = "*" />
</ Grid.RowDefinitions >
<!-- Toolbar: Separate focus scope -->
< StackPanel Grid.Row = "0"
Orientation = "Horizontal"
XYFocusKeyboardNavigation = "Enabled" >
< daisy:DaisyButton Content = "New" />
< daisy:DaisyButton Content = "Open" />
< daisy:DaisyButton Content = "Save" />
</ StackPanel >
<!-- Content: Separate focus scope -->
< ScrollViewer Grid.Row = "1"
XYFocusKeyboardNavigation = "Enabled" >
<!-- Main content -->
</ ScrollViewer >
</ Grid >
Color Contrast
Theme Compliance
Flowery.Uno themes are designed to meet WCAG AA contrast requirements (4.5:1 for normal text).
Verifying Contrast
Test your custom colors:
<!-- Good contrast: #FFFFFF on #1E293B = 12.6:1 -->
< Border Background = "#1E293B" >
< TextBlock Foreground = "#FFFFFF" Text = "High Contrast Text" />
</ Border >
<!-- Poor contrast: #A0AEC0 on #CBD5E0 = 1.8:1 ❌ -->
< Border Background = "#CBD5E0" >
< TextBlock Foreground = "#A0AEC0" Text = "Low Contrast Text" />
</ Border >
Touch Targets
Minimum Touch Target Size
Flowery.Uno enforces 44px minimum touch targets on mobile platforms:
<!-- On mobile, automatically enforces 44px height -->
< daisy:DaisyButton Content = "Tap Me" Size = "Small" />
Spacing Between Targets
Provide adequate spacing between interactive elements:
< StackPanel Spacing = "16" >
< daisy:DaisyButton Content = "Action 1" Size = "Large" />
< daisy:DaisyButton Content = "Action 2" Size = "Large" />
< daisy:DaisyButton Content = "Action 3" Size = "Large" />
</ StackPanel >
WCAG Guideline : Touch targets should be at least 44×44 CSS pixels with adequate spacing (typically 8px minimum).
ARIA Properties
Common ARIA Properties
Property Purpose Example AutomationProperties.NameAccessible label ”Submit Form” AutomationProperties.HelpTextAdditional context ”Saves changes to the server” AutomationProperties.LabeledByAssociates label Reference to label element AutomationProperties.DescribedByDetailed description Reference to description element AutomationProperties.LiveSettingAnnounces changes Polite or Assertive
< StackPanel >
<!-- Explicit label association -->
< TextBlock x:Name = "UsernameLabel" Text = "Username" />
< daisy:DaisyInput AutomationProperties.LabeledBy = "{x:Bind UsernameLabel}" />
<!-- Error message association -->
< TextBlock x:Name = "UsernameError"
Text = "Username must be at least 3 characters"
Foreground = "Red"
Visibility = "Collapsed" />
< daisy:DaisyInput AutomationProperties.DescribedBy = "{x:Bind UsernameError}" />
</ StackPanel >
Best Practices
Provide text alternatives for non-text content
Use AutomationProperties.Name for icons, images, and visual-only controls.
Ensure keyboard accessibility
All interactive controls must be keyboard navigable with visible focus indicators.
Use semantic HTML-like structure
Group related controls with proper headings and regions.
Test with screen readers
Verify your app with Narrator (Windows), NVDA, or JAWS.
Maintain color contrast
Ensure 4.5:1 contrast for normal text, 3:1 for large text.
Support high contrast mode
Test your app in Windows High Contrast mode.
Testing Accessibility
Windows Narrator
Enable Narrator on Windows:
Press Windows + Ctrl + Enter
Navigate your app with Tab and Arrow keys
Verify all controls are announced correctly
Keyboard-Only Navigation
Unplug your mouse
Navigate using only Tab , Shift+Tab , Arrow keys , Enter , and Space
Verify all functionality is accessible
High Contrast Mode
Open Windows Settings → Accessibility → Contrast themes
Select a high contrast theme
Verify your app remains usable
Common Patterns
< StackPanel Spacing = "16" >
<!-- Name field with label -->
< TextBlock x:Name = "NameLabel" Text = "Full Name" />
< daisy:DaisyInput PlaceholderText = "Enter your name"
AutomationProperties.LabeledBy = "{x:Bind NameLabel}"
TabIndex = "1" />
<!-- Email field with validation -->
< TextBlock x:Name = "EmailLabel" Text = "Email" />
< daisy:DaisyInput PlaceholderText = "[email protected] "
AutomationProperties.LabeledBy = "{x:Bind EmailLabel}"
TabIndex = "2" />
< TextBlock x:Name = "EmailError"
Text = "Please enter a valid email"
Foreground = "Red"
Visibility = "{x:Bind ViewModel.ShowEmailError, Mode=OneWay}"
AutomationProperties.LiveSetting = "Polite" />
<!-- Submit button -->
< daisy:DaisyButton Content = "Submit"
Variant = "Primary"
TabIndex = "3"
AutomationProperties.Name = "Submit Form"
Click = "SubmitButton_Click" />
</ StackPanel >
Pattern: Accessible List
< ListView AutomationProperties.Name = "Task List"
SelectionMode = "Single" >
< ListView.ItemTemplate >
< DataTemplate >
< Grid AutomationProperties.Name = "{x:Bind Title}" >
< daisy:DaisyCheckBox Content = "{x:Bind Title}"
IsChecked = "{x:Bind IsCompleted, Mode=TwoWay}"
AutomationProperties.HelpText = "{x:Bind Description}" />
</ Grid >
</ DataTemplate >
</ ListView.ItemTemplate >
</ ListView >
Pattern: Accessible Modal
public async Task ShowDeleteConfirmationAsync ()
{
var result = await DaisyModal . ShowAsync (
title : "Confirm Deletion" ,
message : "Are you sure you want to delete this item? This action cannot be undone." ,
primaryButtonText : "Delete" ,
secondaryButtonText : "Cancel"
);
if ( result == ContentDialogResult . Primary )
{
await DeleteItemAsync ();
// Announce result to screen reader
AnnounceToScreenReader ( "Item deleted successfully" );
}
}
private void AnnounceToScreenReader ( string message )
{
// Create invisible live region for announcement
var announcement = new TextBlock
{
Text = message ,
Visibility = Visibility . Collapsed
};
AutomationProperties . SetLiveSetting ( announcement , AutomationLiveSetting . Polite );
// Add to visual tree temporarily
RootPanel . Children . Add ( announcement );
// Remove after announcement
_ = Task . Delay ( 2000 ). ContinueWith ( _ =>
{
DispatcherQueue . TryEnqueue (() => RootPanel . Children . Remove ( announcement ));
});
}
Resources
WCAG Guidelines Web Content Accessibility Guidelines (WCAG) 2.1
WinUI Accessibility Microsoft’s WinUI accessibility documentation
Contrast Checker Verify color contrast ratios for WCAG compliance
Screen Reader Testing Guide to testing with screen readers
Next Steps
Styling Guide Learn about control customization
Responsive Design Build adaptive UIs with size variants
Component Reference Explore all available controls
Migration Guide Upgrade from previous versions