SnippetUI

Accordion (FAQ) Pure CSS

Component Accordion (dành cho mục FAQ) tối ưu trợ năng, được xây dựng hoàn toàn bằng cặp thẻ HTML details/summary kết hợp CSS, không sử dụng JavaScript.

HTMLCSS

Tận dụng sức mạnh của cặp thẻ <details><summary> được ra mắt từ HTML5, chúng ta hoàn toàn có thể xây dựng một UI component Accordion có khả năng tự đóng/mở mà không tốn một dòng JavaScript nào. Phương pháp này mang lại điểm SEO tuyệt vời và hỗ trợ sẵn các thao tác điều hướng bằng bàn phím (Accessibility/a11y) theo tiêu chuẩn mặc định của trình duyệt.

Xem trước (Preview)

Đặc điểm của accordion này là gì?

Nó được xây dựng chỉ bằng CSS và các tính năng tiêu chuẩn của HTML, không sử dụng JavaScript. Rất nhẹ và tối ưu cho SEO cũng như trợ năng (chẳng hạn như hỗ trợ thao tác bằng bàn phím).

Có thể mở nhiều mục cùng lúc không?

Bằng cách thiết lập thuộc tính name có cùng giá trị (trong ví dụ này là name=“faq”), bạn có thể kiểm soát loại trừ, khi một mục được mở thì các mục khác sẽ tự động đóng lại. Nếu bạn muốn chúng có thể mở cùng lúc, hãy xóa thuộc tính name.

Hiệu ứng animation được triển khai như thế nào?

Animation xoay của icon và hiệu ứng fade-in của nội dung được triển khai bằng transition@keyframes trong CSS.

Mã nguồn (Source Code)

<div class="accordion-container">
  <details class="accordion" name="faq">
    <summary class="accordion-summary">
      Đặc điểm của accordion này là gì?
      <span class="icon"></span>
    </summary>
    <div class="accordion-content">
      <p>Nó được xây dựng chỉ bằng CSS và các tính năng tiêu chuẩn của HTML, không sử dụng JavaScript. Rất nhẹ và tối ưu cho SEO cũng như trợ năng (chẳng hạn như hỗ trợ thao tác bằng bàn phím).</p>
    </div>
  </details>
  
  <details class="accordion" name="faq">
    <summary class="accordion-summary">
      Có thể mở nhiều mục cùng lúc không?
      <span class="icon"></span>
    </summary>
    <div class="accordion-content">
      <p>Bằng cách thiết lập cùng một giá trị cho thuộc tính name, bạn có thể kiểm soát loại trừ (mở một mục thì mục kia sẽ đóng).</p>
    </div>
  </details>
</div>
/* Styling cho container */
.accordion-container {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  width: 100%;
  max-width: 600px;
  margin: 0 auto;
}

/* Phần thân accordion */
.accordion {
  border: 1px solid #e2e8f0;
  border-radius: 8px;
  background-color: #ffffff;
  overflow: hidden;
  box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}

/* Styling cho phần summary (tiêu đề) */
.accordion-summary {
  padding: 1.25rem 1.5rem;
  font-weight: 600;
  font-size: 1.05rem;
  cursor: pointer;
  display: flex;
  justify-content: space-between;
  align-items: center;
  list-style: none; /* Ẩn dấu tam giác mặc định */
  color: #1e293b;
  transition: background-color 0.2s ease;
  user-select: none;
}

/* Ẩn dấu tam giác mặc định cho trình duyệt Safari */
.accordion-summary::-webkit-details-marker {
  display: none;
}

.accordion-summary:hover {
  background-color: #f8fafc;
}

/* Đường gạch dưới của summary khi ở trạng thái mở */
.accordion[open] .accordion-summary {
  border-bottom: 1px solid #e2e8f0;
}

/* Styling cho icon đóng mở */
.icon {
  position: relative;
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  margin-left: 1rem;
}

/* Đường ngang và đường dọc của icon */
.icon::before,
.icon::after {
  content: '';
  position: absolute;
  background-color: #64748b;
  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  border-radius: 2px;
}

.icon::before {
  width: 16px;
  height: 2px;
}

.icon::after {
  width: 2px;
  height: 16px;
}

/* Animation khi accordion mở (dấu + chuyển thành dấu -) */
.accordion[open] .icon::after {
  transform: rotate(90deg) scaleY(0);
}

.accordion[open] .icon::before {
  background-color: #3b82f6;
}

/* Styling cho phần nội dung */
.accordion-content {
  padding: 1.5rem;
  color: #475569;
  line-height: 1.6;
  animation: fadeDown 0.3s ease-out forwards;
}

.accordion-content p {
  margin: 0;
}

/* Animation xuất hiện một cách mềm mại */
@keyframes fadeDown {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

Điểm nhấn triển khai

  1. Sức mạnh của <details><summary>: Bạn có thể sở hữu trọn vẹn logic điều khiển thao tác đóng/mở (toggle state) mà không cần phải cắm mặt vào viết các hàm event listener (onclick) lồng ghép phức tạp bằng JS.
  2. Loại bỏ mũi tên mặc định: Với list-style: none;::-webkit-details-marker { display: none; }, biểu tượng mũi tên tam giác mặc định (và có phần lỗi thời) của trình duyệt sẽ bị gỡ bỏ, nhường không gian cho chúng ta thiết kế custom icon đẹp mắt hơn.
  3. Behavior độc quyền (Exclusive Toggle) nhờ thuộc tính name: Bằng cách gán cho các thẻ <details> cùng một giá trị cho thuộc tính name, chúng sẽ tự động có tính năng “accordion chuẩn” (mở tab này thì tab kia sẽ tự gập lại) chỉ thông qua duy nhất một tính năng HTML rất mới và xịn sò này.
  4. CSS Animations hiện đại: Hiệu ứng nội dung từ từ rớt xuống (@keyframes fadeDown) cùng icon dấu cộng (+) biến đổi mượt mà thành dấu trừ (-) thông qua transition, tất cả đều xử lý bằng CSS. Khá là mượt mà và thân thiện với UX.