Webパーツ辞典

アプリランチャーApp launcher

ヘッダーの右上にある、点が3×3に並んだボタン。押すと同じ会社のほかのアプリやサービスが、アイコンと名前のタイルで並んで開く。

表示例

アイコン: Font Awesome 7 Free(fa-solid / fa-regular)

別名・英語表記

  • ワッフルメニュー
  • アプリ切り替え
  • サービス切り替え
  • お弁当メニュー
  • アプリ一覧ボタン
  • 9つの点のメニュー
  • App launcher

実装例

<div class="x-app-launcher">
  <div class="x-app-launcher__bar">
    <span class="x-app-launcher__logo">Puchi Works</span>
    <span class="x-app-launcher__w">
      <button class="x-app-launcher__b" type="button" aria-label="アプリ一覧" aria-expanded="true" aria-controls="x-app-launcher-p">
        <svg class="x-app-launcher__dots" viewBox="0 0 18 18" width="18" height="18" aria-hidden="true" focusable="false"><circle cx="2" cy="2" r="2" fill="currentColor"/><circle cx="9" cy="2" r="2" fill="currentColor"/><circle cx="16" cy="2" r="2" fill="currentColor"/><circle cx="2" cy="9" r="2" fill="currentColor"/><circle cx="9" cy="9" r="2" fill="currentColor"/><circle cx="16" cy="9" r="2" fill="currentColor"/><circle cx="2" cy="16" r="2" fill="currentColor"/><circle cx="9" cy="16" r="2" fill="currentColor"/><circle cx="16" cy="16" r="2" fill="currentColor"/></svg>
      </button>
      <!-- 見本なので開いた状態。閉じるときは hidden 属性を付け、aria-expanded を false にする。
           中身は行き先のリンクなので role="menu" は付けない -->
      <div class="x-app-launcher__p" id="x-app-launcher-p">
        <ul class="x-app-launcher__grid" role="list">
          <li><a class="x-app-launcher__a" href="/mail" aria-current="page"><i class="fa-regular fa-envelope x-app-launcher__ic x-app-launcher__ic--1" aria-hidden="true"></i>メール</a></li>
          <li><a class="x-app-launcher__a" href="/calendar"><i class="fa-regular fa-calendar x-app-launcher__ic x-app-launcher__ic--2" aria-hidden="true"></i>カレンダー</a></li>
          <li><a class="x-app-launcher__a" href="/drive"><i class="fa-regular fa-folder x-app-launcher__ic x-app-launcher__ic--3" aria-hidden="true"></i>ドライブ</a></li>
          <li><a class="x-app-launcher__a" href="/docs"><i class="fa-regular fa-file-lines x-app-launcher__ic x-app-launcher__ic--4" aria-hidden="true"></i>文書</a></li>
          <li><a class="x-app-launcher__a" href="/chat"><i class="fa-regular fa-comment x-app-launcher__ic x-app-launcher__ic--5" aria-hidden="true"></i>チャット</a></li>
          <li><a class="x-app-launcher__a" href="/photos"><i class="fa-regular fa-image x-app-launcher__ic x-app-launcher__ic--6" aria-hidden="true"></i>写真</a></li>
        </ul>
      </div>
    </span>
  </div>
</div>

AIへの指示文

「アプリランチャー(App launcher)」を実装してください。

## これは何か
ヘッダーの右上にある、点が3×3に並んだボタン。押すと同じ会社のほかのアプリやサービスが、アイコンと名前のタイルで並んで開く。

## 使いどころ
- メール・カレンダー・ドライブのように、1つのアカウントで使う複数のサービスを行き来させたいとき
- どのサービスの画面にいても、同じ場所から他のサービスへ移れるようにしたいとき

## 実装の要件
- button[aria-label][aria-expanded][aria-controls] + div[id][hidden] > ul > li > a の構成にし、パネルはボタンの直後に置く(Tab の順がボタン → タイルになる)
- パネルはボタンの入れ物を position: relative; にし、position: absolute; top: 100%; right: 0; で右端をそろえて下へ重ねる。タイルは display: grid; grid-template-columns: repeat(3, 1fr); で並べる
- パネルに display を指定したら、パネルのクラスに絞って .パネル[hidden] { display: none; } も書く。書かないと hidden 属性を付けても閉じない([hidden] 全体に書くと hidden="until-found" が検索で開かなくなる)
- 3×3 の点は Font Awesome の無料版に無いので、SVG か、1つの点を box-shadow で9つに増やして描く
- ハンバーガーメニュー(navigation/hamburger)はいまのサイトの中の行き先をしまうもの、アプリランチャーは別のアプリやサービスへ移るためのもの。1つのヘッダーに両方置くときは、アイコンの形を変えて取り違えないようにする
- メガメニュー(navigation/mega-menu)はグローバルナビの項目から同じサイトの下の階層を並べるもの。アプリランチャーは中身がサービスの一覧で、階層を持たない
- サービスが3つ以下なら、パネルにしまわずヘッダーにリンクを並べたほうが早い
- 並び順を利用状況で勝手に入れ替えない。場所で覚えている人が迷う。いま使っているサービスは見て分かる印を付ける

## アクセシビリティの要件
- WAI-ARIA APG の Disclosure の形にする。button に aria-expanded と aria-controls を付け、パネルは hidden 属性で出し入れする
- 中身は別のサービスへの行き先なので、ul > li > a のリンクの一覧にし、role="menu" は付けない。Tab でタイルを順に移れる
- 点のアイコンだけのボタンなので、aria-label で「アプリ一覧」のように名前を付ける
- いまのサービスのタイルには aria-current="page" を付け、色だけでなく枠でも示す(WCAG 1.4.1 色の用途)
- Esc でパネルを閉じてボタンへフォーカスを戻す。フォーカスがパネルの外へ出たときにも閉じる

## 参考にする実装
この形を出発点にして、プロジェクトの命名規則・色・余白に合わせて調整してください。

```html
<div class="x-app-launcher">
  <div class="x-app-launcher__bar">
    <span class="x-app-launcher__logo">Puchi Works</span>
    <span class="x-app-launcher__w">
      <button class="x-app-launcher__b" type="button" aria-label="アプリ一覧" aria-expanded="true" aria-controls="x-app-launcher-p">
        <svg class="x-app-launcher__dots" viewBox="0 0 18 18" width="18" height="18" aria-hidden="true" focusable="false"><circle cx="2" cy="2" r="2" fill="currentColor"/><circle cx="9" cy="2" r="2" fill="currentColor"/><circle cx="16" cy="2" r="2" fill="currentColor"/><circle cx="2" cy="9" r="2" fill="currentColor"/><circle cx="9" cy="9" r="2" fill="currentColor"/><circle cx="16" cy="9" r="2" fill="currentColor"/><circle cx="2" cy="16" r="2" fill="currentColor"/><circle cx="9" cy="16" r="2" fill="currentColor"/><circle cx="16" cy="16" r="2" fill="currentColor"/></svg>
      </button>
      <!-- 見本なので開いた状態。閉じるときは hidden 属性を付け、aria-expanded を false にする。
           中身は行き先のリンクなので role="menu" は付けない -->
      <div class="x-app-launcher__p" id="x-app-launcher-p">
        <ul class="x-app-launcher__grid" role="list">
          <li><a class="x-app-launcher__a" href="/mail" aria-current="page"><i class="fa-regular fa-envelope x-app-launcher__ic x-app-launcher__ic--1" aria-hidden="true"></i>メール</a></li>
          <li><a class="x-app-launcher__a" href="/calendar"><i class="fa-regular fa-calendar x-app-launcher__ic x-app-launcher__ic--2" aria-hidden="true"></i>カレンダー</a></li>
          <li><a class="x-app-launcher__a" href="/drive"><i class="fa-regular fa-folder x-app-launcher__ic x-app-launcher__ic--3" aria-hidden="true"></i>ドライブ</a></li>
          <li><a class="x-app-launcher__a" href="/docs"><i class="fa-regular fa-file-lines x-app-launcher__ic x-app-launcher__ic--4" aria-hidden="true"></i>文書</a></li>
          <li><a class="x-app-launcher__a" href="/chat"><i class="fa-regular fa-comment x-app-launcher__ic x-app-launcher__ic--5" aria-hidden="true"></i>チャット</a></li>
          <li><a class="x-app-launcher__a" href="/photos"><i class="fa-regular fa-image x-app-launcher__ic x-app-launcher__ic--6" aria-hidden="true"></i>写真</a></li>
        </ul>
      </div>
    </span>
  </div>
</div>
```

```css
/* 貼った先のリセットに頼らないよう、この部品の中だけ border-box にする */
.x-app-launcher,
.x-app-launcher *:not([class*="fa-"]),
.x-app-launcher *:not([class*="fa-"])::before,
.x-app-launcher *:not([class*="fa-"])::after {
  box-sizing: border-box;
  /* 貼った先の既定の書体に引きずられないようにする。書体が変わると
     文字の幅と高さが変わり、枠のサイズまでずれる */
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Hiragino Sans", "Noto Sans JP", "Yu Gothic UI", Meiryo, sans-serif;
}

/* 外枠はヘッダーの右上を切り取った見本。パネルが下へ重なって開く */
.x-app-launcher {
  width: 300px;
  max-width: 100%;
  min-height: 210px;
  color: #1b2028;
}
.x-app-launcher__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 48px;
  padding: 0 8px 0 14px;
  border: 1px solid #7f8a99;
  border-radius: 8px;
  background: #fff;
}
.x-app-launcher__logo {
  font-size: 14px;
  font-weight: 700;
}
.x-app-launcher__w {
  position: relative;
  display: inline-flex;
}
.x-app-launcher__b {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: #3c4452;
  cursor: pointer;
}
.x-app-launcher__b:hover,
.x-app-launcher__b[aria-expanded="true"] {
  background: #e3e6ea;
}
.x-app-launcher__b:focus-visible,
.x-app-launcher__a:focus-visible {
  outline: 2px solid #1d4ed8;
  outline-offset: 1px;
}
/* 3×3 の点(ワッフル)。1つの点を box-shadow で9つに増やして描く。
   左上の点を中央からずらし、影で残りの8つを置く */
.x-app-launcher__dots {
  display: block;
}
.x-app-launcher__p {
  position: absolute;
  top: calc(100% + 10px);
  right: -4px;
  z-index: 10;
  width: 252px;
  padding: 10px;
  border: 1px solid #7f8a99;
  border-radius: 12px;
  background: #fff;
  box-shadow: 0 8px 20px rgba(27, 32, 40, 0.16);
}
.x-app-launcher__p[hidden] {
  display: none;
}
.x-app-launcher__grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 4px;
  margin: 0;
  padding: 0;
  list-style: none;
}
.x-app-launcher__a {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 10px 2px 8px;
  border-radius: 8px;
  color: #1b2028;
  font-size: 11.5px;
  text-decoration: none;
}
.x-app-launcher__a:hover {
  background: #f1f3f6;
}
/* いま使っているアプリ。色だけでなく枠でも示す */
.x-app-launcher__a[aria-current="page"] {
  background: #eef2fb;
  box-shadow: inset 0 0 0 1px #1d4ed8;
}
.x-app-launcher__ic {
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 10px;
  color: #fff;
  font-size: 17px;
}
/* アイコンの地は白い絵柄と 4.5:1 以上になる濃さにそろえる */
.x-app-launcher__ic--1 { background: #b91c1c; }
.x-app-launcher__ic--2 { background: #1d4ed8; }
.x-app-launcher__ic--3 { background: #a16207; }
.x-app-launcher__ic--4 { background: #0369a1; }
.x-app-launcher__ic--5 { background: #15803d; }
.x-app-launcher__ic--6 { background: #7e22ce; }
```

## 出力の約束
- HTML と CSS を分けて出す。JavaScript が要るときは素の JavaScript で書き、UIフレームワークは使わない。
- 参考実装がアイコンに Font Awesome のクラスを使っている場合は、プロジェクトで使っているアイコンかインライン SVG に置き換えてよい。
- 上のアクセシビリティ要件を満たす。満たせない箇所があるときは、理由を添えて先に知らせる。
- 色・角丸・余白は既存のデザイントークンがあればそれを使う。無ければ CSS 変数として切り出す。

使いどころ

  • メール・カレンダー・ドライブのように、1つのアカウントで使う複数のサービスを行き来させたいとき
  • どのサービスの画面にいても、同じ場所から他のサービスへ移れるようにしたいとき

注意点

  • ハンバーガーメニュー(navigation/hamburger)はいまのサイトの中の行き先をしまうもの、アプリランチャーは別のアプリやサービスへ移るためのもの。1つのヘッダーに両方置くときは、アイコンの形を変えて取り違えないようにする
  • メガメニュー(navigation/mega-menu)はグローバルナビの項目から同じサイトの下の階層を並べるもの。アプリランチャーは中身がサービスの一覧で、階層を持たない
  • サービスが3つ以下なら、パネルにしまわずヘッダーにリンクを並べたほうが早い
  • 並び順を利用状況で勝手に入れ替えない。場所で覚えている人が迷う。いま使っているサービスは見て分かる印を付ける

アクセシビリティの要点

  • WAI-ARIA APG の Disclosure の形にする。button に aria-expanded と aria-controls を付け、パネルは hidden 属性で出し入れする
  • 中身は別のサービスへの行き先なので、ul > li > a のリンクの一覧にし、role="menu" は付けない。Tab でタイルを順に移れる
  • 点のアイコンだけのボタンなので、aria-label で「アプリ一覧」のように名前を付ける
  • いまのサービスのタイルには aria-current="page" を付け、色だけでなく枠でも示す(WCAG 1.4.1 色の用途)
  • Esc でパネルを閉じてボタンへフォーカスを戻す。フォーカスがパネルの外へ出たときにも閉じる

作るときに使えるツール

見分け方

関連するパーツ