Varför tillgänglighet i mobilappar inte längre är valfritt
Sedan den 28 juni 2025 gäller EU:s tillgänglighetsdirektiv (European Accessibility Act, EAA) i hela unionen — inklusive Sverige. I praktiken innebär det att mobilappar riktade mot konsumenter inom e-handel, finanstjänster, telekom och en rad andra sektorer måste uppfylla specifika tillgänglighetskrav. Bryter du mot reglerna riskerar du böter, tillsynsåtgärder och i värsta fall att bli utestängd från marknaden.
Men ärligt talat — tillgänglighet handlar om så mycket mer än lagkrav.
Över en miljard människor världen över lever med någon form av funktionsnedsättning, enligt WHO. En tillgänglig app når helt enkelt fler användare, ger bättre UX för alla (ja, även för dem utan funktionsnedsättning) och signalerar att ditt team tar kvalitet på allvar.
I den här guiden går vi igenom allt du behöver för att bygga tillgängliga .NET MAUI-appar: juridiska ramar, WCAG-standarden, praktiska kodexempel med SemanticProperties, testning med skärmläsare och automatiserade kontroller. Vi kör .NET 10 och .NET MAUI genomgående.
Juridiska krav: EAA, EN 301 549 och svensk lag
European Accessibility Act (EAA)
EU-direktivet 2019/882, ofta kallat European Accessibility Act, trädde i kraft den 28 juni 2025. Direktivet kräver att digitala produkter och tjänster — inklusive mobilappar — ska vara tillgängliga för personer med funktionsnedsättning.
EAA gäller för alla företag som säljer produkter eller tjänster inom EU, oavsett var huvudkontoret ligger. Mikroföretag med färre än 10 anställda och under 2 miljoner euro i omsättning kan vara undantagna, men det varierar mellan medlemsländerna (så kolla vad som gäller just er).
EN 301 549 — den tekniska standarden
Den europeiska standarden EN 301 549 (version 3.2.1) är det tekniska ramverk som används för att visa överensstämmelse med EAA. Standarden inkorporerar WCAG 2.1 nivå AA i sin helhet och utökar kraven till hårdvara, telekom och andra ICT-produkter. En uppdatering som inkluderar WCAG 2.2 är på gång.
WCAG 2.1 och 2.2 — vad som krävs nu
Det juridiska minimikravet är WCAG 2.1 nivå AA. WCAG 2.2 ses som bästa praxis och rekommenderas starkt. De nya kriterierna i 2.2 fokuserar bland annat på:
- Tydligare fokusmarkörer — det ska alltid framgå var fokus befinner sig vid tangentbordsnavigering
- Större klickytor — minst 24×24 CSS-pixlar (rekommenderat 44×44 på iOS, 48×48 dp på Android)
- Konsekvent hjälp — hjälpfunktioner ska finnas på samma plats genom hela appen
- Tillgänglig autentisering — inloggningsflöden ska inte kräva kognitiva funktionstest
POUR-principerna: Grundpelarna för tillgänglig design
WCAG bygger på fyra grundprinciper, kända som POUR. Att ha koll på dem ger dig ett bra ramverk för att tänka kring tillgänglighet i varje designbeslut:
- Perceivable (Möjlig att uppfatta) — information och UI-element måste kunna uppfattas av alla. Bilder behöver alt-text, videor behöver textning, och text måste ha tillräcklig kontrast.
- Operable (Hanterbar) — alla funktioner ska gå att nå via tangentbord eller anpassade inmatningsmetoder. Knappar och länkar behöver tillräckligt stora klickytor.
- Understandable (Begriplig) — gränssnitt och navigation ska vara konsekventa och förutsägbara. Felmeddelanden ska vara tydliga och faktiskt hjälpa användaren.
- Robust — innehållet måste kunna tolkas korrekt av olika hjälpmedel, inklusive skärmläsare och alternativa inmatningsenheter.
SemanticProperties i .NET MAUI: Det rekommenderade sättet
Microsoft rekommenderar SemanticProperties som det primära sättet att hantera tillgänglighet i .NET MAUI. Klassen exponerar tre nyckelattribut som du sätter som attached properties på valfritt visuellt element. Låt oss gå igenom dem.
SemanticProperties.Description
Description är en kort, beskrivande sträng som skärmläsaren läser upp. Det här är det absolut viktigaste attributet för tillgänglighet.
<!-- Bild med beskrivning -->
<Image Source="varning.png"
SemanticProperties.Description="Varningsikon som indikerar ett fel" />
<!-- Knapp med tydlig beskrivning -->
<ImageButton Source="delete.png"
SemanticProperties.Description="Radera uppgift"
Clicked="OnDeleteClicked" />
<!-- Bindning till en labels text -->
<Label x:Name="nameLabel" Text="Ange ditt namn:" />
<Entry SemanticProperties.Description="{Binding Source={x:Reference nameLabel}, Path=Text}"
Placeholder="Förnamn Efternamn" />
Du kan också sätta Description programmatiskt i C#:
var toggleLabel = new Label { Text = "Aktivera mörkt läge:" };
var darkModeSwitch = new Switch();
SemanticProperties.SetDescription(darkModeSwitch, toggleLabel.Text);
Viktiga varningar för Description:
- Sätt aldrig Description på en Label — det blockerar skärmläsaren från att läsa Label-komponentens Text-egenskap
- Undvik Description på Entry och Editor i Android — det bryter TalkBacks funktionalitet. Använd Placeholder eller Hint istället
- På iOS kan Description på en förälderkontroll göra att skärmläsaren inte når barnelementen (detta har bitit oss mer än en gång)
SemanticProperties.Hint
Hint ger extra kontext om syftet med en kontroll — exempelvis vad som förväntas av användaren i ett inmatningsfält.
<Entry SemanticProperties.Description="Telefonnummer"
SemanticProperties.Hint="Ange ditt 10-siffriga telefonnummer"
Keyboard="Telephone" />
<Button Text="Spara"
SemanticProperties.Hint="Tryck för att spara dina ändringar och gå tillbaka"
Clicked="OnSaveClicked" />
<Switch SemanticProperties.Description="Push-notiser"
SemanticProperties.Hint="Slå på eller av push-notiser för den här appen" />
Obs: På Android kan Hint krocka med Entry.Placeholder eftersom de mappar till samma plattformsegenskap. Använd en av dem — inte båda samtidigt.
SemanticProperties.HeadingLevel
HeadingLevel markerar element som rubriker, så att skärmläsaranvändare snabbt kan hoppa mellan sektioner — precis som med h1–h6 på webben.
<Label Text="Inställningar"
SemanticProperties.HeadingLevel="Level1"
Style="{StaticResource TitleStyle}" />
<Label Text="Utseende"
SemanticProperties.HeadingLevel="Level2"
Style="{StaticResource SubtitleStyle}" />
<Label Text="Välj tema, textstorlek och kontrastläge" />
<Label Text="Notifikationer"
SemanticProperties.HeadingLevel="Level2"
Style="{StaticResource SubtitleStyle}" />
<Label Text="Hantera vilka notifikationer du vill ta emot" />
Det finns stöd för nivå 1 till 9, men i praktiken räcker 1–3 gott och väl för de allra flesta mobilappar.
Hantera bilder och dekorativa element
Inte alla bilder förmedlar information. Dekorativa element — skiljelinjer, bakgrundsmönster, rena utfyllnadsbilder — ska exkluderas från tillgänglighetsträdet så att skärmläsaren hoppar över dem. Det här är en sån detalj som gör stor skillnad i praktiken:
<!-- Dekorativ bild — dölj från skärmläsaren -->
<Image Source="separator_line.png"
AutomationProperties.IsInAccessibleTree="False" />
<!-- Informativ bild — beskriv vad den visar -->
<Image Source="status_online.png"
SemanticProperties.Description="Status: online" />
<!-- Komplex bild med detaljerad beskrivning -->
<Image Source="sales_chart.png"
SemanticProperties.Description="Stapeldiagram som visar försäljning per månad. Mars hade högst försäljning med 45 000 kronor." />
Fokushantering och läsordning
SetSemanticFocus
Ibland behöver du styra vart skärmläsarens fokus hamnar — till exempel efter att en operation slutförts eller när nytt innehåll dyker upp. Det gör du så här:
// I din ViewModel eller code-behind
private void OnItemSaved()
{
// Visa bekräftelsemeddelande
ConfirmationLabel.Text = "Uppgiften har sparats!";
ConfirmationLabel.IsVisible = true;
// Flytta skärmläsarens fokus till bekräftelsen
ConfirmationLabel.SetSemanticFocus();
}
SemanticOrderView från Community Toolkit
Skärmläsaren följer normalt den visuella layoutordningen, men det är inte alltid optimalt. Med SemanticOrderView från .NET MAUI Community Toolkit kan du definiera en specifik läsordning:
<toolkit:SemanticOrderView>
<toolkit:SemanticOrderView.ViewOrder>
<x:Array Type="{x:Type View}">
<x:Reference Name="PageTitle" />
<x:Reference Name="ImportantAlert" />
<x:Reference Name="MainContent" />
<x:Reference Name="ActionButton" />
</x:Array>
</toolkit:SemanticOrderView.ViewOrder>
<VerticalStackLayout>
<Label x:Name="PageTitle" Text="Mina uppgifter"
SemanticProperties.HeadingLevel="Level1" />
<Frame x:Name="ImportantAlert" BackgroundColor="LightYellow">
<Label Text="Du har 3 försenade uppgifter" />
</Frame>
<CollectionView x:Name="MainContent" />
<Button x:Name="ActionButton" Text="Lägg till uppgift" />
</VerticalStackLayout>
</toolkit:SemanticOrderView>
Installera paketet via NuGet:
dotnet add package CommunityToolkit.Maui
Praktiska mönster för vanliga UI-komponenter
Tillgänglig inloggningssida
Inloggning är ofta det första en användare möter — så det är extra viktigt att det fungerar bra med skärmläsare. Här är ett fullständigt exempel:
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MinApp.Views.LoginPage"
Title="Logga in">
<ScrollView>
<VerticalStackLayout Padding="20" Spacing="16">
<Label Text="Logga in"
SemanticProperties.HeadingLevel="Level1"
FontSize="28"
FontAttributes="Bold" />
<Label Text="E-postadress"
FontSize="16" />
<Entry x:Name="EmailEntry"
Placeholder="[email protected]"
Keyboard="Email"
SemanticProperties.Description="E-postadress"
SemanticProperties.Hint="Ange din registrerade e-postadress"
ReturnType="Next" />
<Label Text="Lösenord"
FontSize="16" />
<Entry x:Name="PasswordEntry"
Placeholder="Minst 8 tecken"
IsPassword="True"
SemanticProperties.Description="Lösenord"
SemanticProperties.Hint="Ange ditt lösenord, minst 8 tecken"
ReturnType="Done" />
<Label x:Name="ErrorLabel"
TextColor="Red"
IsVisible="False"
SemanticProperties.Description="Felmeddelande" />
<Button Text="Logga in"
SemanticProperties.Hint="Tryck för att logga in med angiven e-post och lösenord"
Clicked="OnLoginClicked"
MinimumHeightRequest="48"
MinimumWidthRequest="48" />
<Button Text="Glömt lösenord?"
SemanticProperties.Hint="Tryck för att återställa ditt lösenord via e-post"
Clicked="OnForgotPasswordClicked"
MinimumHeightRequest="48" />
</VerticalStackLayout>
</ScrollView>
</ContentPage>
Tillgänglig lista med statusindikator
Listor med interaktiva element kräver lite extra omsorg. Varje element behöver en meningsfull beskrivning, och statusikoner måste ha textbeskrivningar:
<CollectionView ItemsSource="{Binding Tasks}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:TaskItem">
<SwipeView>
<Grid Padding="16" ColumnDefinitions="Auto,*,Auto"
MinimumHeightRequest="48">
<CheckBox IsChecked="{Binding IsCompleted}"
SemanticProperties.Description="{Binding Title,
StringFormat='Markera {0} som slutförd'}"
VerticalOptions="Center" />
<VerticalStackLayout Grid.Column="1" Padding="12,0">
<Label Text="{Binding Title}"
FontSize="16"
FontAttributes="Bold" />
<Label Text="{Binding DueDate, StringFormat='Förfaller: {0:d}'}"
FontSize="12"
TextColor="Gray" />
</VerticalStackLayout>
<Image Grid.Column="2"
Source="{Binding PriorityIcon}"
SemanticProperties.Description="{Binding PriorityText}"
WidthRequest="24"
HeightRequest="24"
VerticalOptions="Center" />
</Grid>
</SwipeView>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
Kontrasthantering och dynamisk textstorlek
Säkerställ tillräcklig kontrast
WCAG 2.1 kräver ett kontrastförhållande på minst 4.5:1 för normal text och 3:1 för stor text (18pt eller 14pt fetstil). I .NET MAUI kan du bygga teman som respekterar detta. Det behöver inte vara komplicerat:
<!-- I App.xaml eller en ResourceDictionary -->
<ResourceDictionary>
<!-- Standardtema med god kontrast -->
<Color x:Key="PrimaryText">#1A1A1A</Color>
<Color x:Key="SecondaryText">#4A4A4A</Color>
<Color x:Key="Background">#FFFFFF</Color>
<Color x:Key="ErrorText">#B00020</Color>
<!-- Högkontrasttema -->
<Color x:Key="HighContrastPrimaryText">#000000</Color>
<Color x:Key="HighContrastBackground">#FFFFFF</Color>
<Color x:Key="HighContrastErrorText">#8B0000</Color>
</ResourceDictionary>
Stöd för dynamisk textstorlek
Användare som behöver större text ställer in det i enhetens inställningar. Din app måste respektera dessa val — det finns inga genvägar här. Undvik att hårdkoda fasta höjder och bredder, och använd istället layouts som anpassar sig:
<!-- Bra: Flexibel layout som anpassas efter textstorlek -->
<VerticalStackLayout>
<Label Text="Rubrik"
FontSize="Title"
LineBreakMode="WordWrap" />
<Label Text="Beskrivning som kan bli lång"
FontSize="Body"
LineBreakMode="WordWrap"
MaxLines="0" />
</VerticalStackLayout>
<!-- Dåligt: Fast höjd som klipper text vid större storlek -->
<!-- <Label HeightRequest="20" Text="Denna text klipps" /> -->
Använd namngivna fontstorlekar (Micro, Small, Body, Title, Header) istället för explicita pixelvärden. De skalas automatiskt med enhetens textinställningar, och det sparar dig en hel del huvudvärk.
Testa tillgänglighet: Skärmläsare och verktyg
Manuell testning med skärmläsare
Inget automatiserat verktyg kan ersätta manuell testning med en riktig skärmläsare. Det kan kännas ovant i början, men det ger insikter som inget annat verktyg ger. Så här aktiverar du skärmläsare per plattform:
- Android (TalkBack): Inställningar → Tillgänglighet → TalkBack → Aktivera
- iOS (VoiceOver): Inställningar → Tillgänglighet → VoiceOver → Aktivera
- Windows (Skärmläsaren): Inställningar → Hjälpmedel → Skärmläsaren → Aktivera
- macOS (VoiceOver): Systeminställningar → Tillgänglighet → VoiceOver → Aktivera
Checklista för manuell testning
Gå igenom varje skärm och kontrollera:
- Kan alla interaktiva element nås med skärmläsaren?
- Läser skärmläsaren upp meningsfulla beskrivningar för varje element?
- Är läsordningen logisk och följer det visuella flödet?
- Kan formulär fyllas i och skickas helt utan att se skärmen?
- Får användaren feedback efter åtgärder (spara, radera, fel)?
- Är knappar och länkar tillräckligt stora (minst 48×48 dp)?
- Fungerar appen i högkontrastläge?
- Fungerar appen med förstorad text?
Automatiserade tillgänglighetsverktyg
Använd dessa verktyg som komplement — de ersätter inte manuell testning, men de fångar lågt hängande frukt:
- Accessibility Insights — för Android och Windows, identifierar saknade beskrivningar och kontrastfel
- Accessibility Scanner (Google) — skannar Android-appar och föreslår konkreta förbättringar
- Accessibility Inspector (Xcode) — inspekterar tillgänglighetsegenskaper på iOS och macOS
Automatiserade tester med Appium
Du kan integrera tillgänglighetskontroller i din befintliga testsvit med Appium. Sätt AutomationId på element du vill nå från tester:
<!-- I XAML -->
<Button Text="Spara"
AutomationId="SaveButton"
SemanticProperties.Hint="Tryck för att spara"
MinimumHeightRequest="48" />
// I ditt Appium-testprojekt (xUnit)
[Fact]
public void SaveButton_ShouldHaveAccessibilityLabel()
{
var saveButton = _driver.FindElement(
MobileBy.AccessibilityId("SaveButton"));
Assert.NotNull(saveButton);
// Verifiera att knappen har en tillgänglighetsbeskrivning
var label = saveButton.GetAttribute("content-desc"); // Android
Assert.False(string.IsNullOrEmpty(label),
"Spara-knappen saknar tillgänglighetsbeskrivning");
}
[Fact]
public void SaveButton_ShouldHaveMinimumTouchTarget()
{
var saveButton = _driver.FindElement(
MobileBy.AccessibilityId("SaveButton"));
var size = saveButton.Size;
Assert.True(size.Height >= 48,
$"Knappens höjd ({size.Height}dp) är under minsta klickyta (48dp)");
Assert.True(size.Width >= 48,
$"Knappens bredd ({size.Width}dp) är under minsta klickyta (48dp)");
}
Bygga ett tillgängligt tema med AppThemeBinding
.NET MAUI stöder ljust och mörkt tema via AppThemeBinding. Att implementera båda med korrekta kontrastförhållanden är en viktig pusselbit för tillgängligheten:
<Style TargetType="Label" x:Key="AccessibleBodyLabel">
<Setter Property="FontSize" Value="Body" />
<Setter Property="TextColor"
Value="{AppThemeBinding
Light={StaticResource PrimaryText},
Dark=#E0E0E0}" />
<Setter Property="LineBreakMode" Value="WordWrap" />
</Style>
<Style TargetType="Button" x:Key="AccessibleButton">
<Setter Property="FontSize" Value="Body" />
<Setter Property="MinimumHeightRequest" Value="48" />
<Setter Property="MinimumWidthRequest" Value="48" />
<Setter Property="Padding" Value="16,12" />
<Setter Property="BackgroundColor"
Value="{AppThemeBinding
Light=#1565C0,
Dark=#64B5F6}" />
<Setter Property="TextColor"
Value="{AppThemeBinding
Light=#FFFFFF,
Dark=#000000}" />
</Style>
<Style TargetType="Entry" x:Key="AccessibleEntry">
<Setter Property="FontSize" Value="Body" />
<Setter Property="MinimumHeightRequest" Value="48" />
<Setter Property="TextColor"
Value="{AppThemeBinding
Light={StaticResource PrimaryText},
Dark=#E0E0E0}" />
<Setter Property="PlaceholderColor"
Value="{AppThemeBinding
Light=#757575,
Dark=#9E9E9E}" />
</Style>
Checklista: Tillgänglighet före lansering
Innan du publicerar din app — ta en stund och gå igenom det här:
- Alla bilder har SemanticProperties.Description eller är exkluderade från tillgänglighetsträdet
- Alla knappar och interaktiva element har minst 48×48 dp klickyta
- Alla formulärfält har tydlig Description och/eller Hint
- Rubriker är markerade med HeadingLevel för snabb navigering
- Kontrasten uppfyller WCAG AA (4.5:1 för normal text, 3:1 för stor text)
- Dynamisk textstorlek respekteras — inga fasta höjder som klipper text
- Mörkt och ljust tema har testats med korrekt kontrast i båda lägena
- Manuell testning har genomförts med TalkBack (Android) och VoiceOver (iOS)
- Felmeddelanden kommuniceras till skärmläsaren via SemanticFocus
- Läsordningen är logisk och verifierad med skärmläsare
Vanliga misstag att undvika
Vi har sett samma tillgänglighetsmisstag dyka upp gång på gång i .NET MAUI-projekt. Här är de vanligaste — och ja, vi har begått några av dem själva:
- Sätta Description på Label — detta överskuggar Text-egenskapen och skärmläsaren läser bara Description, inte den text användaren faktiskt ser
- Glömma dekorativa bilder — varje bild utan IsInAccessibleTree=False läses upp, vilket skapar onödigt brus för skärmläsaranvändare
- Hårdkoda dimensioner — fasta höjder och bredder gör att text klipps vid förstorade inställningar
- Ignorera plattformsskillnader — TalkBack och VoiceOver beter sig olika. Testa alltid på båda plattformarna, inte bara den du råkar ha till hands
- Otillräckliga felmeddelanden — att bara visa rött på skärmen hjälper inte användare som inte ser färgen. Kombinera visuella signaler med text och semantisk fokus
- Testa bara med verktyg — automatiserade verktyg hittar kanske 30–40% av tillgänglighetsproblemen. Manuell testning med skärmläsare är helt enkelt oumbärlig
Vanliga frågor
Är min .NET MAUI-app skyldig att uppfylla EU:s tillgänglighetsdirektiv?
Ja, om din app erbjuder tjänster inom e-handel, bank, telekom eller andra sektorer som omfattas av EAA och du säljer till konsumenter inom EU. Undantag kan gälla för mikroföretag med färre än 10 anställda och under 2 miljoner euro i omsättning. Sedan juni 2025 pågår aktiv tillsyn med risk för böter och marknadsbegränsningar.
Vad är skillnaden mellan SemanticProperties och AutomationProperties?
SemanticProperties är Microsofts rekommenderade sätt i .NET MAUI och ersätter de äldre AutomationProperties från Xamarin.Forms. SemanticProperties erbjuder bättre plattformsintegration och inkluderar funktioner som HeadingLevel. AutomationProperties fungerar fortfarande men betraktas som föråldrade — använd SemanticProperties för alla nya projekt.
Hur testar jag tillgänglighet utan fysisk enhet?
Du kan använda Android Emulator med TalkBack (aktiveras via Inställningar → Tillgänglighet) och iOS Simulator med VoiceOver (aktiveras via Inställningar → Tillgänglighet). Komplettera med Accessibility Insights för Android/Windows och Accessibility Inspector i Xcode för iOS/macOS. Automatiserade tester med Appium kan verifiera att element har korrekta tillgänglighetsattribut. Men inget slår testning på fysiska enheter med riktiga skärmläsare — det är bara verkligheten.
Vilken version av WCAG måste jag följa — 2.1 eller 2.2?
Det juridiska minimikravet via EN 301 549 v3.2.1 är WCAG 2.1 nivå AA. WCAG 2.2 betraktas som bästa praxis och en uppdatering av standarden som inkluderar 2.2 är under arbete. Vi rekommenderar att sikta på WCAG 2.2 redan nu — det finns inga brytande ändringar jämfört med 2.1, och du framtidssäkrar din app.
Kan jag använda .NET MAUI Community Toolkit för tillgänglighet?
Absolut. Community Toolkit erbjuder bland annat SemanticOrderView för att styra läsordningen, SemanticProperties-extensions för C# Markup, och StatusBarBehavior för att anpassa statusfältet. Installera paketet CommunityToolkit.Maui via NuGet och registrera det i MauiProgram.cs med UseMauiCommunityToolkit().