Jetpack Compose

Popisek čistě grafického tlačítka pomocí contentDescription (Jetpack Compose)

Tlačítko s jedinou ikonou a bez viditelného textu nemá žádný výchozí přístupný název. Icon sám o sobě žádný text nenese a jeho contentDescription nemá žádnou rozumnou výchozí hodnotu — pokud ho necháte null a nic jiného v tlačítku popisek nedodá, TalkBack ohlásí jen holé "tlačítko", bez jakéhokoli náznaku, k čemu slouží.

1. Špatně — ikona bez popisku

IconButton(onClick = { onSync() }) {
    Icon(Icons.Default.Refresh, contentDescription = null)
}

Vizuálně jasné (ikona aktualizace), ale contentDescription = null tu ikonu explicitně označuje jako dekorativní a nic jiného v tomhle tlačítku popisek nedodá. Výsledek: tlačítko bez přístupného jména.

2. Správně — popisek přímo na Icon

IconButton(onClick = { view.clickSound(); showOverflowMenu = true }) {
    Icon(Icons.Default.MoreVert, contentDescription = "Další možnosti")
}

Modifier.clickable, na kterém IconButton stojí, sám sloučí sémantiku celého podstromu do jednoho uzlu (mergeDescendants = true) — popisek nastavený přímo na vnořeném Icon se tak stane přístupným jménem celého tlačítka. Nic dalšího není potřeba: roli tlačítka i akci kliknutí IconButton zajišťuje sám.

3. Kde se to používá

V mé aplikaci pro čtení RSS kanálů takhle vypadá naprostá většina ikonových tlačítek v horní liště — tlačítko zpět, tlačítko s dalšími možnostmi, tlačítko pro otevření odkazu v prohlížeči:

IconButton(onClick = { view.clickSound(); onBack() }) {
    Icon(Icons.AutoMirrored.Filled.ArrowBack, "Zpět na domovskou obrazovku")
}

Tlačítko pro synchronizaci je trochu jiný případ — ikona se za běhu mění na indikátor průběhu, takže popisek nejde pověsit na jeden konkrétní Icon. Místo toho sedí na Modifier.semantics celého tlačítka a mění se podle stavu:

IconButton(
    onClick = { view.clickSound(); onSync() },
    enabled = !isRefreshing,
    modifier = Modifier.semantics {
        contentDescription = if (isRefreshing) "Synchronizace probíhá" else "Synchronizovat"
    }
) {
    if (isRefreshing) {
        CircularProgressIndicator(modifier = Modifier.size(24.dp))
    } else {
        Icon(Icons.Default.Refresh, contentDescription = null)
    }
}

Icon uvnitř má tady contentDescription = null záměrně a správně — popisek už dodává obalující IconButton, takže nastavit ho ještě jednou i na ikonu by bylo jen zbytečné zdvojení.

Proč to takhle je

  • Modifier.clickable slučuje sémantiku podstromu automaticky — proto stačí popisek nastavit na Icon, nic se nemusí ručně slučovat ani přenášet výš do rodičovského uzlu.
  • IconButton sám nastavuje roli tlačítka — na rozdíl od vlastního contentDescription u textového tlačítka, kde se muselo použít Modifier.clearAndSetSemantics a roli i akci kliknutí ručně doplnit zpátky, tady se nic nezahazuje — IconButton roli i akci zajišťuje sám, popisek je jediná věc, kterou je potřeba přidat.
  • contentDescription = null na ikoně není vždycky chyba — je v pořádku všude tam, kde popisek tlačítka dodává něco jiného (obalující IconButton přes Modifier.semantics, nebo sousední viditelný text u položek menu). Chybou je to jen tehdy, když je ikona jediným obsahem tlačítka a přesto zůstane bez popisku.
  • Mění-li se popisek podle stavu a mění se přitom i samotná ikona (jako u synchronizace — ikona se nahradí indikátorem průběhu), je jednodušší dát popisek na Modifier.semantics celého tlačítka než ho řešit na obsahu, který se sám průběžně mění.

Rozdíl oproti vlastnímu contentDescription u textového tlačítka je v tom, že tam existoval viditelný text, který zněl čtečce nepřirozeně nebo neuváděl celou potřebnou informaci — proto bylo nutné jeho sémantiku zahodit pomocí clearAndSetSemantics a nahradit ji ručně, včetně role a akce kliknutí, protože zahazování smazalo i je. U čistě grafického tlačítka žádný viditelný text není, takže není co zahazovat — stačí popisek přidat tam, kam přirozeně patří.

Pravidla

  • WCAG 4.1.2 (Název, role, hodnota) — i čistě grafické tlačítko musí mít programově zjistitelné jméno, ne jen vizuální ikonu, kterou vidí pouze uživatel bez zrakového postižení.

Shrnutí: čistě grafické tlačítko (IconButton s Icon a bez viditelného textu) potřebuje popisek buď přímo na Icon, nebo — mění-li se podle stavu spolu s ikonou — na Modifier.semantics celého tlačítka. IconButton už sám slučuje sémantiku podstromu a nastavuje roli, takže na rozdíl od nahrazování popisku u textového tlačítka tu není potřeba clearAndSetSemantics.

Štítky

Komentáře

Zatím zde nejsou žádné komentáře. Buďte první, kdo napíše svůj názor.

Přidat komentář k „Popisek čistě grafického tlačítka pomocí contentDescription (Jetpack Compose)“