Dropdown yang Menyesuaikan dengan Area Pandang

Panel tindakan yang ditampilkan atau grup tombol popover adalah pola yang berguna bagi pengguna untuk mengakses fungsi tambahan sekaligus menghemat ruang. Pola overlay ini memiliki kompleksitas tata letak, karena panel harus tetap ditambatkan ke elemen pemicu sambil beradaptasi dengan batasan area pandang. Biasanya, hal ini memerlukan library JavaScript yang kompleks (seperti Popper.js atau Floating UI) untuk menghitung posisi dan menangani tumpang-tindih.

CSS Anchor Positioning menyediakan cara deklaratif dan dioptimalkan performanya untuk menangani hubungan ini sepenuhnya di CSS, sehingga browser dapat mengelola logika pemosisian dan overflow secara native.

1. Menentukan Hubungan Tombol dan Panel

Langkah pertama adalah membuat tombol pemicu yang membuka penampung overlay menggunakan Popover API.

Perbedaan Aksesibilitas WAJIB: Pola ini secara eksplisit memodelkan grup tombol yang ditampilkan di dalam popover, bukan menu ARIA yang sebenarnya. Jangan terapkan role="menu" atau role="menuitem" kecuali jika Anda sepenuhnya menerapkan kontrak navigasi keyboard yang sesuai (seperti menangani navigasi tombol panah spasial antar-item). Untuk alasan yang sama, jangan tambahkan aria-haspopup ke pemicu — nilainya (menu, listbox, tree, grid, atau true lama) adalah jaminan bahwa target mengekspos peran yang cocok, yang tidak dilakukan oleh pola ini.

<button popovertarget="action-panel">
  Open Actions
</button>
<!-- Use the Popover API (`popover="auto"`) for the overlay to ensure it is placed in the top layer and handled accessibly by the browser. -->
<div id="action-panel" popover="auto" class="panel">
  <button class="action-item" type="button">Edit</button>
</div>

Tindakan ini akan membuat pengaitan anchor implisit antara tombol dan panel, sehingga panel dapat diposisikan secara relatif terhadap tombol secara native tanpa memerlukan anchor-name pada tombol atau position-anchor pada panel.

2. Penentuan posisi dengan position-area

Daripada offset top/left manual, gunakan position-area untuk menempatkan target pada petak 3x3 yang relatif terhadap anchor.

.panel {
  /* 
     Position the panel below the anchor (block-end), 
     aligned to the start of the anchor and spanning to its end (span-inline-end).
  */
  position-area: block-end span-inline-end;
  
  /* Reset insets to allow the grid to take control */
  inset: auto;
}

Pilih kata kunci logis (span-inline-end, block-start) daripada kata kunci fisik (left, top) untuk mendukung RTL dan berbagai mode penulisan secara otomatis.

WAJIB: Jangan mencampur kata kunci fisik dan logis di position-area.

3. Menerapkan Ketahanan Edge (Penggantian)

Untuk mencegah panel terpotong di tepi layar, tentukan "taktik percobaan" yang harus dicoba browser jika posisi default meluap.

.panel {
  /* 
     If the panel overflows the bottom, flip it to the top (flip-block).
     If it overflows the inline edges, flip it horizontally (flip-inline).
     If it overflows both (e.g., in a corner), flip on both axes (flip-block flip-inline).
  */
  position-try-fallbacks: flip-block, flip-inline, flip-block flip-inline;
}

Jika dropdown atau flyout Anda menyertakan panah atau batas arah yang perlu diubah gayanya bergantung pada posisi penggantian yang dipilih, lihat Tooltip yang Mengetahui Posisi. Untuk pemanggilan deklaratif dialog dan popover tanpa JavaScript, lihat Kontrol Dialog dan Popover Deklaratif.

Strategi penggantian

Penggantian & dukungan browser untuk Popover

Popover API sebagian besar merupakan progressive enhancement, tetapi perilaku penentuannya — promosi lapisan atas, penutupan ringan, dan pemanggilan popovertarget — tidak memiliki padanan khusus CSS. Browser lama memerlukan polyfill, atau penggantian manual jika Anda tidak ingin mengirimkannya.

Polyfill: Untuk mendukung atribut popover di browser lama, muat @oddbird/popover-polyfill secara bersyarat. WAJIB: Deteksi fitur dengan memeriksa properti popover di HTMLElement.prototype, dan muat polyfill hanya jika dukungan native tidak ada — JANGAN memuatnya tanpa syarat.

Dengan bundler atau peta impor:

// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
  import("@oddbird/popover-polyfill");
}

Tanpa bundler, impor dari CDN di dalam <script type="module">:

<script type="module">
  if (!("popover" in HTMLElement.prototype)) {
    import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
  }
</script>

Peringatan gaya: Polyfill tidak dapat menentukan pseudo-class :popover-open yang sebenarnya, sehingga menerapkan class .\:popover-open. WAJIB: Gabungkan keduanya dengan :is() atau :where(). Jika tidak, browser yang tidak memiliki :popover-open akan menghapus seluruh aturan:

[popover]:is(:popover-open, .\:popover-open) {
  display: block;
}

Atau, untuk penggantian lama tanpa polyfill, gunakan position: fixed dan hitung koordinat secara manual melalui getBoundingClientRect() atau andalkan pemosisian default dengan inset: auto jika hal itu dapat diterima untuk kasus penggunaan.

Untuk mendukung browser tanpa pemosisian anchor, Anda harus menetapkan posisi yang wajar. Secara default, popover diposisikan di tengah layar, yang mungkin sesuai untuk kasus penggunaan Anda.

Untuk beberapa kasus penggunaan, Anda mungkin dapat menggunakan polyfill @oddbird/css-anchor-positioning, yang menambahkan dukungan untuk beberapa kasus penggunaan pemosisian anchor. Atribut ini tidak mendukung anchor implisit, jadi Anda HARUS menambahkan nama anchor ke pemicu. Selain itu, position-area tidak didukung di popover oleh polyfill, jadi Anda HARUS menggunakan anchor() pada inset yang diinginkan.

<!-- MANDATORY: Conditionally install the anchor positioning polyfill -->
<script type="module">
  if (!("anchorName" in document.documentElement.style)) {
    await import("https://unpkg.com/@oddbird/css-anchor-positioning");
  }
</script>
.panel {
  /* Mandatory: use explicit anchor name */
  position-anchor: --kebab-anchor;
  /* Mandatory: use insets rather than position-area for positioning */
  bottom: auto;
  right: auto;
  top: anchor(bottom);
  left: anchor(left);
  margin: 0;
}