[web] TabIndex and Focus styles
keywords: tabindex
, a11y
, focus
最近碰到一個情況是在電腦上點擊某個 HTML 元素是,該元素不會出現 outline,但在手機上卻會,後來研究了一下原來是和 tabindex
還有 focus
屬性有關。希望做到的效果:
- 在不同瀏覽器上不會出現 outline
- 鍵盤操作時會有 outline
TL;DR;
- 由於這個元素有加上
tabindex
屬性,所以它變成是可以被 focus 的。 - 針對可以被 focus 的元素來說,不論是手機 tap 或桌機 click 時,都會套用
:focus
的 CSS 樣式(通常是 outline)。如果在手機上和電腦上看的效果不同,通常是因為瀏覽器預設的樣式不同,和裝置本身無關。 - 為了要解決元素被套用了
:focus
屬性,最快的作 法就是使用outline: none;
,但這麼做忽略了使用 a11y 的問題,使用 keyboard 瀏覽的人將不知道自己的位置。 - 使用
:focus-visible
讓使用鍵盤的閱覽者一樣可以用鍵盤瀏覽頁面。 - Safari 在寫文章的當下還不支援
:focus-visible
(2021-07-27
)。
/* 未來 Safari 支援 :focus-visible 後,可以這樣寫 */
button:focus:not(:focus-visible) {
outline: none;
}
/*
目前還是只能這樣
有支援 :focus-visible 的將可以用鍵盤瀏覽,沒有的(Safari)則不行
*/
&:focus {
outline: none;
}
&:focus-visible {
outline: 2px solid tomato;
}
TabIndex
tabindex
最主要的目的就是讓鍵盤可以無障礙的操作到改元素,一般來說 HTML 內建的元素像是 <button>
、<input>
、<a>
預設就會帶有 keyboard accessibility 的功能,不需要額外添加,但如果是針對特定的元素想要讓使用者可以被鍵盤觸擊到的話,就可以用 tabindex 這個屬性。
一旦加了 tabindex
之後:
- 鍵盤就可以選到該元素,
- 這個元素會變成是可以透過
.focus()
選到的
通常可行的話。會建議使用 HTML 原生就有支援的 element 來達到想做的 a11y 的效果,例如使用 button
或 select
,因為瀏覽器已經針對這些 element 處理過了,而不用針對 HTML 元素手動的去操作 a11y 的設定。
tabindex="0"
tabindex 會改變使用者點擊 tab 後該元素的 tab order,一般來說,如果希望讓該元素可以點擊 tab 後被選取 ,只需要在原本的 HTML element 上加上 tabindex="0"
即可,這樣瀏覽器就會用預設的方式,去排列 tab order:
<h1 tabindex="0">This is a title with keyboard accessibilty</h1>
tabindex="-1"
由於預設 button 和 input 元素就有 tabindex 的效果在,如果因為某些情況你不想讓鍵盤能夠選到這個 button,但仍然可以被 focus 時,就可以加上 tabindex="-1"
,如此,使用者就沒有辦法透過鍵盤的 tab 選到該元素:
<button type="button" tabindex="-1">This is a button without keyboard accessibility</button>
tabindex="-1"
通常用在你希望某個元素可以被 focus,但不能被鍵盤的 tab 觸擊到時。
tabindex > 0
這是一種非常不建議的作法,一般閱讀器是根據 DOM 的循序,而不是 tab 的訊息。如果你真的希望能夠讓使用者比較早用 tab 選到該元素,就應該把這個元素拉往頁面的前面。
tabindex 的概念和 zindex 有點像,都會改變某種順序,但 tabindex 一般不會設超過 0,而是讓瀏覽器根據 DOM 自己排列。
Style Focus
只要這個 HTML 元素是可以被 focus 的,不論是原本的 button、a 或是加上了 tabindex
的元素,它們都將會帶有 :focus
的屬性,但不同瀏覽器的實作可能不同(safari 在點擊 button 時不會觸發 focus)。
傳統上為了避免使用者點擊按鈕時出現討人厭的 outline,我們會這樣做:
/* anti pattern */
button:focus {
outline: none;
}
雖然大家都這麼做,但這麼做被視為一種 anti-pattern,因為對於使用鍵盤來瀏覽頁面的人來說,他將不是到自己現在 focus 的位置在哪。
要解決這個問題可以使用下面提到的 CSS 屬性。
使用 :focus-visible
當這個元素被 focus 時,瀏覽器會自行判斷要不要顯示 focus indicator,特別是當使用者使用了鍵盤在操作時,就會 match 到這個屬性:
button:focus-visible {
outline: 2px solid tomato;
}
感覺起來非常好用,邏輯上我們可以寫成這樣,如果這個 element 不是被 :focus-visible
的話(也就是不是被鍵盤觸擊到的話),就不要有 outline:
/* https://web.dev/style-focus/ */
button:focus:not(:focus-visible) {
outline: none;
}
難過的是,在 Safari 上還不支援...(Can I Use :focus-visible):
所以最後還是換回這樣的寫法:
- 把 focus 的 outline 都關掉
- 針對支援 focus-visible 的瀏覽器,讓他有 focus indicator 出現
&:focus {
outline: none;
}
&:focus-visible {
outline: 2px solid tomato;
}
這種寫法的話,在 Safari 上用鍵盤瀏覽會不知道自己的位置在哪。
:focus-within
在讀文件時另外發現的一個好用屬性,而且瀏覽器支援度還不錯,有些時候我們會想要某個內層元素被 focus 時,外層元素可以套用一些樣式,這時候就可以用到 :focus-within
這個屬性:
<div class="outter"><input type="checkbox" /> check me</div>
<style>
.outter {
&:focus-within {
border: 1px solid steelblue;
}
}
</style>
Reference
- Control focus with tabindex @ web.dev
- Style focus @ web.dev