ボタンクリックで開くDrop Down Menuコンポーネントです。
- Web Component (
<ui-dropdown>) として提供しています。
- WAI-ARIA Authoring Practices の Menu Button Pattern に準拠しています。
Source Code
<button type="button" data-ui-dropdown-trigger="basic-menu">操作</button>
<ui-dropdown class="ui:dropdown" id="basic-menu" aria-label="操作メニュー">
<button class="ui:dropdown__item" type="button">編集</button>
<button class="ui:dropdown__item" type="button">複製</button>
<button class="ui:dropdown__item" type="button" disabled>Disabled</button>
リンク (<a href>) も項目として使えます。
Source Code
<button type="button" data-ui-dropdown-trigger="nav-menu">メニュー</button>
<ui-dropdown class="ui:dropdown" id="nav-menu" aria-label="ナビゲーション">
<a class="ui:dropdown__item" href="/">ホーム</a>
<a class="ui:dropdown__item" href="/about">概要</a>
<hr class="ui:separator" />
<a class="ui:dropdown__item" href="https://example.com" target="_blank">
外部リンク<ui-icon data-icon="external-link" aria-label="別ウィンドウで開きます"></ui-icon>
| 属性 / CSS変数 | 要素 | 値 | 効果 |
|---|
disabled | button.ui:dropdown__item | - | 項目を無効化(キーボード移動からスキップ) |
aria-disabled | .ui:dropdown__item | "true" | 項目を無効化(button / a 両対応) |
data-value | .ui:dropdown__item | string | ui-dropdown:select カスタムイベントの detail.value で取得可能 |
--ui-dropdown-min-width | root | CSS <length> | メニュー最小幅。既定 12rem |
| 属性 | 要素 | 値 | 効果 |
|---|
data-ui-dropdown-trigger | 任意 | 対象 dropdown の "id" | 自動でA11y属性が付与され、イベントハンドラが登録されます |
| キー | 動作 |
|---|
| Enter Enter Enter Enter / Space Space Space Space | メニューを開く + 最初の項目へフォーカス |
| Down Arrow ↓ Down Arrow ↓ | メニューを開く + 最初の項目へフォーカス |
| Up Arrow ↑ Up Arrow ↑ | メニューを開く + 最後の項目へフォーカス |
| キー | 動作 |
|---|
| Down Arrow ↓ Down Arrow ↓ / Up Arrow ↑ Up Arrow ↑ | 次 / 前の有効な項目へフォーカス(ループ) |
| Home Home Home Home | 最初の項目へ |
| End End End End | 最後の項目へ |
| Enter Enter Enter Enter / Space Space Space Space | フォーカス中の項目を activate(= click)して close |
| Tab Tab Tab Tab | メニューを閉じる(focus はそのまま自然遷移) |
| Escape Esc Escape Esc | メニューを閉じる(popover ネイティブ) |
| backdrop クリック | メニューを閉じる(popover ネイティブ) |
JS が trigger 要素の getBoundingClientRect() を見て、メニューの位置を計算します:
- 既定: trigger の 直下、左端揃え
- viewport の 下に入らない 場合は 上に flip (上に入る場合のみ)
- viewport の 右にはみ出す 場合は左方向に shift
- popover は top layer (= viewport 座標) なので
position: fixed で配置
<ui-dropdown> は Light DOM の Web Component ですが、ユーザーアクションによって表示される性質上、JS upgrade 前は 完全に非表示 にしています。
<!-- トリガー (どこに置いても OK) -->
<button type="button" data-ui-dropdown-trigger="my-menu">操作</button>
<ui-dropdown class="ui:dropdown" id="my-menu" aria-label="操作メニュー">
<button class="ui:dropdown__item" type="button">編集</button>
<a class="ui:dropdown__item" href="/settings">設定</a>
<hr class="ui:dropdown__separator" />
<button class="ui:dropdown__item" type="button" disabled>共有 (準備中)</button>
<button class="ui:dropdown__item" type="button" data-variant="danger">削除</button>
| 要素 | 役割 |
|---|
<ui-dropdown id> | メニュー本体。role="menu" が自動付与される |
.ui:dropdown__item (<button> / <a>) | メニュー項目。role="menuitem" が自動付与される |
.ui:dropdown__separator (<hr>) | 区切り線。ネイティブ role="separator" が効く |
import type { UiDropdownElement } from "@packages/ui/dropdown";
const $menu = document.querySelector<UiDropdownElement>("#my-menu");
$menu?.open(); // プログラム open
$menu?.open(triggerEl); // 特定 trigger 起点で open (位置計算で使われる)
console.log($menu?.isOpen);
| イベント | 発火元 | cancelable | bubbles | detail |
|---|
ui-dropdown:beforeopen | <ui-dropdown> | ✅ | ✅ | { $trigger: HTMLElement | null } |
ui-dropdown:open | <ui-dropdown> | ❌ | ✅ | { $trigger: HTMLElement | null } |
ui-dropdown:close | <ui-dropdown> | ❌ | ✅ | {} |
ui-dropdown:select | <ui-dropdown> | ❌ | ✅ | { $item: HTMLElement; value: string | null } |
import type { DropdownOpenDetail, DropdownSelectDetail } from "@packages/ui/dropdown";
document.addEventListener("ui-dropdown:select", (e) => {
const { $item, value } = e.detail;
console.log("selected:", value, $item);
// open をキャンセル (例: 未保存変更時)
document.addEventListener("ui-dropdown:beforeopen", (e) => {