跳至主要内容

[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-visible2021-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 之後:

  1. 鍵盤就可以選到該元素,
  2. 這個元素會變成是可以透過 .focus() 選到的
提示

通常可行的話。會建議使用 HTML 原生就有支援的 element 來達到想做的 a11y 的效果,例如使用 buttonselect,因為瀏覽器已經針對這些 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):

Screen Shot 2021-08-27 at 1.58.27 PM

所以最後還是換回這樣的寫法:

  • 把 focus 的 outline 都關掉
  • 針對支援 focus-visible 的瀏覽器,讓他有 focus indicator 出現
&:focus {
outline: none;
}
&:focus-visible {
outline: 2px solid tomato;
}
warning

這種寫法的話,在 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