建立分割按鈕元件

說明如何建構無障礙分割按鈕元件的基礎總覽。

在這篇文章中,我想分享有關建立分割按鈕的方法。 立即試用

示範

如果您喜歡看影片,請參考這篇文章的 YouTube 版本:

總覽

分割按鈕是按鈕 隱藏主要按鈕和一系列其他按鈕。很實用 在次要巢狀結構中呈現一般動作、較不常使用 如果想讓設計變得既繁瑣,就一定要有分割按鈕 感覺很少進階分割按鈕甚至可能會記住上次的使用者動作 然後把這些元素提升為主要位置

電子郵件應用程式提供常見的分割按鈕。主要動作 ,但您可以稍後再傳送,也可以改為儲存草稿:

電子郵件應用程式中顯示的分割按鈕範例。

共用動作區域很實用,因為使用者不需要四處看看。他們 但請注意,分割按鈕內含重要電子郵件動作

零件

在探討分割按鈕之前 提供更完整的使用者體驗 VisBug 的無障礙功能 這裡會使用檢查工具顯示元件的微距檢視 定義各主要部分的 HTML、樣式和無障礙功能

分割按鈕的 HTML 元素。

頂層分割按鈕容器

最高等級的元件為內嵌 Flexbox,類別為 gui-split-button,包含主要動作.gui-popup-button

已檢查 gui-split-button 類別,並顯示此類別中使用的 CSS 屬性。

主要動作按鈕

初始顯示和可聚焦的 <button> 需符合 兩個相應邊角形狀 對焦懸停有效互動的對象 都包含在 .gui-split-button 內。

這個檢查器顯示按鈕元素的 CSS 規則。

彈出式切換鈕

「彈出式按鈕」支援元素用於啟用及分配至 次要按鈕請注意,這不是 <button>,且無法聚焦。不過 是 .gui-popup 的定位錨定標記,用於 :focus-within 的主機 顯示彈出式視窗

這個檢查器顯示 gui-popup-button 類別的 CSS 規則。

彈出式資訊卡

此為浮動資訊卡子項 .gui-popup-button,定位絕對和 在語意上納入按鈕清單

顯示類別 gui-popup 的 CSS 規則的檢查器

次要動作

可聚焦的 <button>,字型大小比主要字型小一些 動作按鈕:包含圖示和隨附的 設為主要按鈕樣式

這個檢查器顯示按鈕元素的 CSS 規則。

自訂屬性

下列變數有助於建立色彩協調,並集中存放於 修改元件中使用的值

@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --light (prefers-color-scheme: light);

.gui-split-button {
  --theme:             hsl(220 75% 50%);
  --theme-hover:  hsl(220 75% 45%);
  --theme-active:  hsl(220 75% 40%);
  --theme-text:      hsl(220 75% 25%);
  --theme-border: hsl(220 50% 75%);
  --ontheme:         hsl(220 90% 98%);
  --popupbg:         hsl(220 0% 100%);

  --border: 1px solid var(--theme-border);
  --radius: 6px;
  --in-speed: 50ms;
  --out-speed: 300ms;

  @media (--dark) {
    --theme:             hsl(220 50% 60%);
    --theme-hover:  hsl(220 50% 65%);
    --theme-active:  hsl(220 75% 70%);
    --theme-text:      hsl(220 10% 85%);
    --theme-border: hsl(220 20% 70%);
    --ontheme:         hsl(220 90% 5%);
    --popupbg:         hsl(220 10% 30%);
  }
}
敬上

版面配置和顏色

標記

元素一開始會以含有自訂類別名稱的 <div> 開頭。

<div class="gui-split-button"></div>

新增主要按鈕和 .gui-popup-button 元素。

<div class="gui-split-button">
  <button>Send</button>
  <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions"></span>
</div>

請注意 ARI 屬性 aria-haspopuparia-expanded。這些信號 讓螢幕閱讀器必須瞭解分割的功能和狀態 按鈕體驗。title 屬性對所有人都有幫助。

新增 <svg> 圖示和 .gui-popup 容器元素。

<div class="gui-split-button">
  <button>Send</button>
  <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
    <svg aria-hidden="true" viewBox="0 0 20 20">
      <path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
    </svg>
    <ul class="gui-popup"></ul>
  </span>
</div>

如果是簡單的彈出式刊登位置,.gui-popup 是指按鈕的子項 。這項策略唯一的攔截行動是.gui-split-button 容器無法使用 overflow: hidden,因為該容器會 因為視覺的呈現。

包含 <li><button> 內容的 <ul> 會宣告自身為「按鈕」 清單」也就是呈現的介面

<div class="gui-split-button">
  <button>Send</button>
  <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
    <svg aria-hidden="true" viewBox="0 0 20 20">
      <path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
    </svg>
    <ul class="gui-popup">
      <li>
        <button>Schedule for later</button>
      </li>
      <li>
        <button>Delete</button>
      </li>
      <li>
        <button>Save draft</button>
      </li>
    </ul>
  </span>
</div>

我們為次要按鈕加上了圖示,希望您能更賞心悅目,同時增添趣味色彩。 來自 https://heroicons.com。圖示為兩者皆可選用 例如主要和次要按鈕

<div class="gui-split-button">
  <button>Send</button>
  <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
    <svg aria-hidden="true" viewBox="0 0 20 20">
      <path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
    </svg>
    <ul class="gui-popup">
      <li><button>
        <svg aria-hidden="true" viewBox="0 0 24 24">
          <path d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
        </svg>
        Schedule for later
      </button></li>
      <li><button>
        <svg aria-hidden="true" viewBox="0 0 24 24">
          <path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
        </svg>
        Delete
      </button></li>
      <li><button>
        <svg aria-hidden="true" viewBox="0 0 24 24">
          <path d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" />
        </svg>
        Save draft
      </button></li>
    </ul>
  </span>
</div>

樣式

設定好 HTML 和內容後,樣式即可開始提供顏色和版面配置。

設定分割按鈕容器的樣式

inline-flex 顯示類型適用於這個包裝元件 應配合其他分割按鈕、動作或元素。

.gui-split-button {
  display: inline-flex;
  border-radius: var(--radius);
  background: var(--theme);
  color: var(--ontheme);
  fill: var(--ontheme);

  touch-action: manipulation;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}

分割按鈕。

<button> 樣式

按鈕很適合用來掩蓋所需的程式碼數量。您可能需要 復原或取代瀏覽器預設樣式,但您也需強制執行 沿用、新增互動狀態,並根據不同的使用者偏好加以調整 輸入類型按鈕樣式可以快速累積。

這些按鈕與一般按鈕不同,因為兩者共用同一個背景 宣告物件通常按鈕會有其背景和文字顏色。 不過,使用者可以分享這些圖片,而且只在互動時套用自己的背景。

.gui-split-button button {
  cursor: pointer;
  appearance: none;
  background: none;
  border: none;

  display: inline-flex;
  align-items: center;
  gap: 1ch;
  white-space: nowrap;

  font-family: inherit;
  font-size: inherit;
  font-weight: 500;

  padding-block: 1.25ch;
  padding-inline: 2.5ch;

  color: var(--ontheme);
  outline-color: var(--theme);
  outline-offset: -5px;
}

透過少數 CSS 新增互動狀態 虛擬類別及使用比對功能 狀態的自訂屬性:

.gui-split-button button {
  

  &:is(:hover, :focus-visible) {
    background: var(--theme-hover);
    color: var(--ontheme);

    & > svg {
      stroke: currentColor;
      fill: none;
    }
  }

  &:active {
    background: var(--theme-active);
  }
}

主要按鈕需要幾種特殊的樣式,才能完成設計效果:

.gui-split-button > button {
  border-end-start-radius: var(--radius);
  border-start-start-radius: var(--radius);

  & > svg {
    fill: none;
    stroke: var(--ontheme);
  }
}

最後,在風格上,淺色主題按鈕和圖示 shadow

.gui-split-button {
  @media (--light) {
    & > button,
    & button:is(:focus-visible, :hover) {
      text-shadow: 0 1px 0 var(--theme-active);
    }
    & > .gui-popup-button > svg,
    & button:is(:focus-visible, :hover) > svg {
      filter: drop-shadow(0 1px 0 var(--theme-active));
    }
  }
}

理想的按鈕應著重於微互動和極細的細節。

關於「:focus-visible」的附註

請注意,按鈕樣式如何使用 :focus-visible,而非 :focus:focus 是提供無障礙功能不可或缺的一項功能 因此無論使用者是否需要看見 但這不會套用至任何焦點

下方的影片試圖細分這種微互動,如下所示 :focus-visible 是智慧替代選項。

設定彈出式按鈕的樣式

用於將圖示置中及錨定彈出式按鈕清單的 4ch Flexbox。喜歡 主要按鈕會顯示為透明,直到使用者停留在主要按鈕或進行互動 並延伸至填滿

分割按鈕的箭頭部分,用來觸發彈出式視窗。

.gui-popup-button {
  inline-size: 4ch;
  cursor: pointer;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-inline-start: var(--border);
  border-start-end-radius: var(--radius);
  border-end-end-radius: var(--radius);
}

運用 CSS 將遊標懸停、焦點和有效狀態圖層 Nesting:is() 功能選取器:

.gui-popup-button {
  

  &:is(:hover,:focus-within) {
    background: var(--theme-hover);
  }

  /* fixes iOS trying to be helpful */
  &:focus {
    outline: none;
  }

  &:active {
    background: var(--theme-active);
  }
}

這些樣式是顯示及隱藏彈出式視窗的主要誘因。.gui-popup-button 已對其任何子項套用 focus,設定 opacity,位置 圖示和彈出式視窗上都要有 pointer-events

.gui-popup-button {
  

  &:focus-within {
    & > svg {
      transition-duration: var(--in-speed);
      transform: rotateZ(.5turn);
    }
    & > .gui-popup {
      transition-duration: var(--in-speed);
      opacity: 1;
      transform: translateY(0);
      pointer-events: auto;
    }
  }
}

完成「內切」和「整理」風格後,最後一項決定是有條件限制的 轉場效果 (視使用者的動作偏好設定而定):

.gui-popup-button {
  

  @media (--motionOK) {
    & > svg {
      transition: transform var(--out-speed) ease;
    }
    & > .gui-popup {
      transform: translateY(5px);

      transition:
        opacity var(--out-speed) ease,
        transform var(--out-speed) ease;
    }
  }
}

如果著重在程式碼上,您會發現對使用者的不透明度仍存在轉換 偏好減少動態影像的人

設定彈出式視窗樣式

.gui-popup 元素是使用自訂屬性的浮動資訊卡按鈕清單 配合主要單元 按鈕和品牌元素請注意,各圖示的對比度較低 看起來最薄,陰影則帶有品牌藍色提示。就像按鈕一樣 這些細節都要歸功於這些細微的細節,才是健全的 UI 和使用者體驗。

浮動資訊卡元素。

.gui-popup {
  --shadow: 220 70% 15%;
  --shadow-strength: 1%;

  opacity: 0;
  pointer-events: none;

  position: absolute;
  bottom: 80%;
  left: -1.5ch;

  list-style-type: none;
  background: var(--popupbg);
  color: var(--theme-text);
  padding-inline: 0;
  padding-block: .5ch;
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  font-size: .9em;
  transition: opacity var(--out-speed) ease;

  box-shadow:
    0 -2px 5px 0 hsl(var(--shadow) / calc(var(--shadow-strength) + 5%)),
    0 1px 1px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 10%)),
    0 2px 2px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 12%)),
    0 5px 5px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 13%)),
    0 9px 9px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 14%)),
    0 16px 16px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 20%))
  ;
}

這些圖示和按鈕都有加上品牌顏色,讓各種暗色的風格煥然一新 和淺色主題卡:

結帳、快速付款和儲存供稍後購買的連結與圖示。

.gui-popup {
  

  & svg {
    fill: var(--popupbg);
    stroke: var(--theme);

    @media (prefers-color-scheme: dark) {
      stroke: var(--theme-border);
    }
  }

  & button {
    color: var(--theme-text);
    width: 100%;
  }
}

深色主題彈出式視窗有文字和圖示陰影,並額外增添一點 強烈方塊陰影:

採用深色主題的彈出式視窗。

.gui-popup {
  

  @media (--dark) {
    --shadow-strength: 5%;
    --shadow: 220 3% 2%;

    & button:not(:focus-visible, :hover) {
      text-shadow: 0 1px 0 var(--ontheme);
    }

    & button:not(:focus-visible, :hover) > svg {
      filter: drop-shadow(0 1px 0 var(--ontheme));
    }
  }
}

一般 <svg> 圖示樣式

所有圖示的大小都與先前用於 font-size 按鈕的按鈕類似 使用 ch 做為 inline-size。每種方法也提供一些樣式,讓圖示的外框變得柔軟且 流暢。

.gui-split-button svg {
  inline-size: 2ch;
  box-sizing: content-box;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 2px;
}

由右至左版面配置

邏輯屬性會處理所有複雜工作。 以下是使用的邏輯屬性清單: - display: inline-flex 可建立內嵌 Flex 元素。 - padding-blockpadding-inline 為配對,而非 padding 簡而言之,您可以取得填充邏輯面的好處。 - border-end-start-radius朋友將 圓角化。 - 使用 inline-size 而非 width,可確保尺寸不會與實際尺寸相關聯。 - border-inline-start 會在起點加上框線,可能位於右側或左側,視指令碼方向而定。

JavaScript

下列 JavaScript 幾乎全都是強化無障礙功能。我兩個 輔助程式庫可簡化工作 BlingBlingJS 精簡基本用法 DOM 查詢和簡易的事件監聽器設定, roving-ux 改善無障礙環境 為彈出式視窗的鍵盤和遊戲手把互動操作。

import $ from 'blingblingjs'
import {rovingIndex} from 'roving-ux'

const splitButtons = $('.gui-split-button')
const popupButtons = $('.gui-popup-button')

匯入上述程式庫後,選取並儲存至 變數,升級體驗時,幾項功能尚未完成。

旋轉索引

當鍵盤或螢幕閱讀器聚焦 .gui-popup-button 時,我們希望 將焦點移至第一個 (或最近聚焦) 按鈕 .gui-popup。我們可以利用程式庫利用 elementtarget 達成此目的 參數。

popupButtons.forEach(element =>
  rovingIndex({
    element,
    target: 'button',
  }))

元素現在會將焦點傳遞至目標 <button> 子項,並啟用 標準方向鍵瀏覽選項。

正在切換「aria-expanded

儘管在視覺上可以看出彈出式視窗顯示及隱藏,但螢幕閱讀器的不只是視覺提示。這裡使用 JavaScript 可切換螢幕閱讀器適用屬性,藉此強化以 CSS 驅動的 :focus-within 互動。

popupButtons.on('focusin', e => {
  e.currentTarget.setAttribute('aria-expanded', true)
})

popupButtons.on('focusout', e => {
  e.currentTarget.setAttribute('aria-expanded', false)
})

啟用 Escape

使用者的注意力會刻意傳送到陷阱,也就是說,我們需要 提供出發前往的選項最常見的方法是允許使用 Escape 鍵。 做法是留意是否按下了彈出式按鈕 子項會向上顯示在這個父項上方。

popupButtons.on('keyup', e => {
  if (e.code === 'Escape')
    e.target.blur()
})

如果彈出式按鈕偵測到任何按下 Escape 鍵,焦點會從本身移除 同時 blur()

分割按鈕點擊次數

最後,如果使用者點選、輕觸或鍵盤與按鈕互動, 應用程式需要執行適當的動作使用事件啟動功能 但這次在 .gui-split-button 容器上擷取按鈕 來自子項彈出式視窗或主要動作的點擊。

splitButtons.on('click', event => {
  if (event.target.nodeName !== 'BUTTON') return
  console.info(event.target.innerText)
})

結論

現在你知道我怎麼了,這樣會如何 🙂?

讓我們來體驗多元的方法,瞭解透過網路建立內容的所有方式。 建立示範、將 Twitter 推文連結,我們就會為您新增 前往下方的社群重混專區!

社群重混作品