Các menu bật lên, không giống như các chú giải công cụ xuất hiện khi di chuột, mở khi nhấp chuột và được sử dụng để hiển thị danh sách các hành động (chẳng hạn như chỉnh sửa hoặc xóa) cho người dùng. Nó được thiết kế chú trọng đến khả năng truy cập, chẳng hạn như quản lý tiêu điểm và xử lý đóng không nhấp chuột.

Xem trước

Trường hợp sử dụng & Tính năng

Những điểm chính của thiết kế và UX (Design & UX)

Cách tùy chỉnh (Hướng dẫn tùy chỉnh)

Mã triển khai (Implementation)

Bạn có thể đưa menu bật lên vào dự án của mình bằng mã bên dưới.

<div class="popover-wrapper">

<button class="popover-trigger" aria-haspopup="true" aria-expanded="false" id="popover-btn">

  <span>hành động</span>

  <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">

    <path d="m6 9 6 6 6-6"/>

  </svg>

</button>

<div class="popover-menu" role="menu" aria-labelledby="popover-btn">

  <div class="popover-content">

    <button class="popover-item" role="menuitem">

      <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>

      Chỉnh sửa

    </button>

    <button class="popover-item" role="menuitem">

      <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2" ry="2"/><path d="M21 9H3"/><path d="M9 21V9"/></svg>

      trùng lặp

    </button>

    <div class="popover-divider"></div>

    <button class="popover-item text-danger" role="menuitem">

      <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/></svg>

      xóa

    </button>

  </div>

</div>

</div>



<style>
/* Đặt CSS vào đây */

.popover-wrapper {

position: relative;

display: inline-block;

}



.popover-trigger {

display: flex;

align-items: center;

gap: 6px;

padding: 8px 16px;

font-size: 14px;

font-weight: 500;

color: #334155;

background-color: #ffffff;

border: 1px solid #cbd5e1;

border-radius: 8px;

cursor: pointer;

transition: all 0.2s ease;

box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);

}



.popover-trigger:hover {

background-color: #f1f5f9;

border-color: #94a3b8;

}



.popover-trigger:focus-visible {

outline: 2px solid #3b82f6;

outline-offset: 2px;

}



.popover-trigger svg {

transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);

}



.popover-trigger.is-open svg {

transform: rotate(180deg);

}



.popover-menu {

position: absolute;

top: calc(100% + 8px);

left: 50%;

transform: translateX(-50%) translateY(-10px);

width: 200px;

background: #ffffff;

border-radius: 12px;

box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);

border: 1px solid #e2e8f0;

opacity: 0;

visibility: hidden;

transition: all 0.2s cubic-bezier(0.16, 1, 0.3, 1);

z-index: 50;

pointer-events: none;

}



.popover-wrapper.is-active .popover-menu {

opacity: 1;

visibility: visible;

transform: translateX(-50%) translateY(0);

pointer-events: auto;

}



.popover-content {

padding: 6px;

display: flex;

flex-direction: column;

gap: 2px;

}



.popover-item {

display: flex;

align-items: center;

gap: 10px;

width: 100%;

padding: 8px 12px;

font-size: 14px;

color: #475569;

background: transparent;

border: none;

border-radius: 6px;

cursor: pointer;

text-align: left;

transition: background-color 0.15s ease, color 0.15s ease;

}



.popover-item:hover,

.popover-item:focus-visible {

background-color: #f1f5f9;

color: #0f172a;

outline: none;

}



.popover-item.text-danger {

color: #ef4444;

}



.popover-item.text-danger:hover,

.popover-item.text-danger:focus-visible {

background-color: #fef2f2;

color: #dc2626;

}



.popover-divider {

height: 1px;

background-color: #e2e8f0;

margin: 4px 0;

}
</style>



<script>

// Đặt JavaScript vào đây

document.addEventListener('DOMContentLoaded', () => {

const wrappers = document.querySelectorAll('.popover-wrapper');



wrappers.forEach(wrapper => {

  const trigger = wrapper.querySelector('.popover-trigger');

  const menu = wrapper.querySelector('.popover-menu');

  

  if (!trigger || !menu) return;



  const togglePopover = () => {

    const isActive = wrapper.classList.contains('is-active');

    if (isActive) {

      closePopover();

    } else {

      openPopover();

    }

  };



  const openPopover = () => {

    wrapper.classList.add('is-active');

    trigger.classList.add('is-open');

    trigger.setAttribute('aria-expanded', 'true');

  };



  const closePopover = () => {

    wrapper.classList.remove('is-active');

    trigger.classList.remove('is-open');

    trigger.setAttribute('aria-expanded', 'false');

  };



  trigger.addEventListener('click', togglePopover);



  //Đóng bên ngoài vùng click

  document.addEventListener('click', (e) => {

    if (!wrapper.contains(e.target)) {

      closePopover();

    }

  });



  // Đóng bằng phím Escape

  document.addEventListener('keydown', (e) => {

    if (e.key === 'Escape' && wrapper.classList.contains('is-active')) {

      closePopover();

      trigger.focus();

    }

  });

});

});

</script>