分割ボタン コンポーネントを作成する

アクセス可能な分割ボタン コンポーネントを作成する方法の基本概要。

この投稿では、分割ボタンを作成する方法についての考え方を共有します。 デモをお試しください

<ph type="x-smartling-placeholder">
</ph> <ph type="x-smartling-placeholder">
</ph> デモ

動画で視聴したい場合は、この投稿の YouTube バージョンをご利用ください。

概要

分割ボタンはボタン メインボタンとその他のボタンのリストを隠します。役に立つ 一般的なアクションを公開し、使用頻度の低いセカンダリをネストします。 アクションを実行できます。煩雑なデザインには分割ボタンが不可欠 感じます高度な分割ボタンでは最後のユーザー アクションも記憶される メインの掲載位置に昇格させることができます

メール アプリケーションには、一般的な分割ボタンがあります。メイン アクション が、後で送信したり、下書きとして保存したりすることもできます。

メール アプリケーションの分割ボタンの例。

共有アクション領域は、ユーザーが見回す必要がないので便利です。。 重要なメール アクションが分割ボタンに含まれていることを理解しておいてください。

部品

分割ボタンについて説明する前に、分割ボタンの重要な部分を詳しく見ていきましょう。 全体的なオーケストレーションと 最終的なユーザーエクスペリエンスです VisBug のユーザー補助機能 この検査ツールを使用すると、コンポーネントのマクロビューが表示され、 主要な要素について、HTML、スタイル、アクセシビリティの要素を理解します。

分割ボタンを構成する HTML 要素。

最上位の分割ボタンコンテナ

最上位のコンポーネントはインラインのフレックスボックスで、 gui-split-buttonプライマリ アクションを含む) および .gui-popup-button

gui-split-button クラスを調べ、このクラスで使用されている CSS プロパティを表示している。

メイン アクション ボタン

最初に表示され、フォーカス可能な <button> は、次のコンテナ内に収まります。 2 つの角の形が一致しているため、 Focusカーソルを合わせて、 有効なインタラクションを .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>

ARIA の属性 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> が自身を「ボタン」としてアナウンスします listスクリーン リーダーです。これはまさに提示されるインターフェースです。

<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 からどちらでもアイコンは任意 2 つめのボタンがあります

<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 ではなく :focus-visible が使用されていることに注目してください。:focus 使いやすいユーザーインターフェースを実現する 重要な手段ですが ユーザーがそれを見る必要があるか、それとも すべてのフォーカスに適用されます

以下の動画は、このマイクロインタラクションを細分化して、 :focus-visible はインテリジェントな代替手段です。

ポップアップ ボタンのスタイルを設定する

アイコンを中央に配置し、ポップアップ ボタンのリストを固定するための 4ch フレックスボックス。高評価 カーソルを合わせるか操作するまで透明です 引き伸ばして埋めます

ポップアップをトリガーするために使用される分割ボタンの矢印部分。

.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 でレイヤ化 ネスト: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);
  }
}

これらのスタイルは、ポップアップを表示および非表示にするための主要なフックです。Google .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 と UX は、こうした細かい積み重ねによって生まれたものです。

フローティング カード要素。

.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 ではなく、padding-blockpadding-inline をペアとして使用 論理面をパディングするメリットを享受できます。 - border-end-start-radius友達の行動は ドキュメントの向きに応じて角を丸くします。 - width ではなく inline-size を使用すると、サイズが物理的な寸法に結び付かなくなります。 - border-inline-start は先頭に枠線を追加します。枠線は、スクリプトの向きに応じて右揃えまたは左揃えになります。

JavaScript

以下の JavaScript のほとんどは、ユーザー補助機能を強化するためのものです。私の 2 ヘルパー ライブラリを使用して、タスクを少し簡単にします。 簡潔な表現には BlingBlingJS を使用します。 DOM クエリと簡単なイベントリスナーの設定に加え roving-ux は、ユーザーが Google Cloud ポップアップのキーボードとゲームパッドの操作。

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)
})

まとめ

どのようにやり方をしたかわかったので、どのように感じますか? ‽ 🙂?

アプローチを多様化して、ウェブで構築するすべての方法を学びましょう。 デモを作成し、ツイートしてリンクを送ってください 下の [コミュニティリミックス]セクションに アクセスしてください

コミュニティ リミックス