10 điểm bởi GN⁺ 2025-11-02 | 7 bình luận | Chia sẻ qua WhatsApp
  • Trong giao diện web, dùng <button> thay vì <div> là lựa chọn đúng đắn hơn về cả khả năng truy cập lẫn chức năng
  • <div> không được trình đọc màn hình nhận diện là phần tử tương tác, đồng thời cũng không phản hồi với tiêu điểm bàn phím hay thao tác nhấn Enter, Spacebar
  • Dù thêm thuộc tính [role="button"] hoặc [tabindex="0"], vẫn còn các vấn đề về thứ tự tiêu điểm và xử lý sự kiện bàn phím
  • Để khắc phục các vấn đề này, việc thêm nhiều event listener và câu lệnh điều kiện gây ra độ phức tạp không cần thiết
  • <button> cung cấp sẵn khả năng truy cập, tiêu điểm và xử lý nhập từ bàn phím, nên đây là giải pháp đơn giản và tiêu chuẩn

Cách tiếp cận sai: tạo nút bằng <div>

  • Trong cộng đồng người dùng React hay HTMX, có khá nhiều trường hợp triển khai tương tác như mở modal bằng dạng <div onclick="...">
    • Mã ví dụ:
      <div onclick="showSignIn()">Open Modal</div>
      
  • Vấn đề của cách này
    • Trình đọc màn hình không nhận diện phần tử đó là phần tử tương tác
    • Không thể di chuyển tiêu điểm bằng bàn phím tới đó
    • Chỉ sự kiện click hoạt động, còn nhấn Enter hay Spacebar thì không phản hồi

Giới hạn của các nỗ lực “sửa” khả năng truy cập

  • Thêm thuộc tính [role="button"] có thể giải quyết vấn đề nhận diện của trình đọc màn hình, nhưng
    vấn đề về khả năng nhận tiêu điểm và xử lý nhập từ bàn phím vẫn còn nguyên
  • Có thể thêm [tabindex="0"] để cho phép nhận tiêu điểm, nhưng
    có nguy cơ làm rối thứ tự tiêu điểm hoặc gây ra các lần chuyển không mong muốn
  • Muốn xử lý nhập từ bàn phím thì phải đăng ký sự kiện keydown ở phạm vi toàn cục (document),
    phát hiện phím Enter hoặc ' ' (space), rồi tìm phần tử đang được focus để thực thi
    document.addEventListener('keydown', (event) => {
      if (event.key !== 'Enter' && event.key !== ' ') return;
      const notRealBtn = document.activeElement.closest('[onclick]');
      if (!notRealBtn) return;
      // 실행 코드
    });
    
  • Kết quả là, bạn đang tái hiện một cách phức tạp những gì <button> vốn đã cung cấp sẵn theo mặc định

Các tính năng mặc định mà <button> cung cấp

  • Phần tử <button> tự động hỗ trợ những điều sau
    • Vai trò ngầm định ([role="button"])
    • Khả năng nhận tiêu điểm tự động
    • Khi đang có tiêu điểm, nhấn EnterSpacebar sẽ phát sinh sự kiện click
  • Nếu muốn triển khai cùng hành vi đó bằng <div>, bạn sẽ cần nhiều thuộc tính và script, còn
    <button> giải quyết được chỉ với một dòng
    <button onclick="showSignIn()">Open Modal</button>
    

Kết luận: đơn giản là tốt nhất

  • <button>cách đơn giản nhất để vừa đáp ứng tiêu chuẩn khả năng truy cập vừa giảm lượng mã nguồn
  • Thay vì thêm xử lý sự kiện hay thuộc tính không cần thiết, dùng phần tử HTML tiêu chuẩn sẽ có lợi hơn về khả năng bảo trì và hiệu quả
  • Với thông điệp “càng lười thì càng nên dùng đúng phần tử”, bài viết nhấn mạnh tầm quan trọng của thói quen phát triển biết tránh độ phức tạp không cần thiết và tận dụng tính năng mặc định

7 bình luận

 
come2mecome 2025-11-04

Đây là một bài viết rất hay. Có thể tóm tắt ý chính của phần nội dung là: "hãy sử dụng các thẻ HTML một cách có ý nghĩa." Nếu cung cấp sự kiện click bằng thẻ div (hoặc các thẻ khác), thì theo tôi điều đó hoàn toàn không khác gì thời kỳ trước đây khi người ta dùng thẻ table để dựng layout.

 
carnoxen 2025-11-11

Tất nhiên nếu nhét các thuộc tính aria-* vào thì có thể sẽ rõ ràng hơn, nhưng thay vì phải khổ sở như thế thì thà cứ dùng đúng thẻ còn hơn lol

 
roxie 2025-11-06

Hoài niệm thật nhỉ haha

 
carnoxen 2025-11-02

Các trang web cơ quan công quyền ở nước mình dường như dùng &lt;a&gt; rất nhiều...

 
GN⁺ 2025-11-02
Ý kiến Hacker News
  • Một trong những điều khiến tôi khó chịu là các website triển khai điều hướng bằng trình xử lý onclick
    Chỉ cần dùng thẻ <a> thì mở tab mới, tích hợp với công cụ hỗ trợ tiếp cận, menu chuột phải và mọi thứ khác đều tự động hoạt động tốt
    Nếu là điều hướng thì nên dùng liên kết thay vì một nồi lẩu JavaScript

    • Trong vài năm gần đây, kiểu triển khai này ngày càng nhiều hơn
      Có lẽ do ảnh hưởng của framework hoặc do sự thờ ơ
      Dù vậy, cách truyền thống gần như luôn tốt hơn về mặt UX
      Tôi mong những ai cố thay thế thẻ <a> sẽ gặp một chút bất tiện
    • Tôi nghĩ vấn đề là các lập trình viên bắt đầu với React đã lao ngay vào “những thứ thú vị” mà không học các khái niệm HTML cơ bản
      Những người này tạo ra các pattern sai, rồi các lập trình viên đi sau cứ thế làm theo
      Rất hiếm khi tôi thực sự phải trang trí một <div> để nó trông như nút bấm
    • Nên loại bỏ cả cuộn trang dựa trên JS
      Tôi hay cuộn bằng nút giữa chuột, nhưng nhiều website làm hỏng chức năng này
    • Nghe chuyện này lại khiến tôi nhớ đến trình kiểm tra liên kết của Microsoft Office 365
      Nhấp chuột trái thì hiện trang kiểm tra an toàn, còn nhấp chuột giữa thì đi thẳng luôn
    • Ngay cả trong dự án React tôi tham gia gần đây, toàn bộ điều hướng đều dùng onClick
      Ngay cả các phần tử về bản chất là liên kết cũng đều bị xử lý bằng click handler, thật khó hiểu
  • Hầu hết các nút nên khai báo rõ type="button"
    Giá trị mặc định là submit, nên nếu ở trong form thì nó sẽ tự động gửi
    Có lẽ một số lập trình viên không biết điều này nên mới dùng <div>

    • Tôi nghĩ bài viết dài của OP đã bỏ sót thông tin cốt lõi này
      Nút với kiểu mặc định hoạt động kỳ quặc và đôi khi còn bỏ qua JS handler
    • Mặc định đó tương ứng với <input type="submit">, còn <button> thì khác
    • Tôi cũng tự mình trải qua rồi mới biết điều này
    • Dùng <div> thì có thể tránh được vấn đề type="submit"
      <div> vốn trống ngay từ đầu nên bạn chỉ thêm những chức năng cần thiết, và sau này cũng dễ chỉnh sửa
      Trong khi đó, để hiểu hành vi mặc định của <button> thì phải đọc tài liệu
      Rốt cuộc đây là vấn đề lựa chọn giữa kiểm soát tường minh vs tính năng tích hợp sẵn
  • Sẽ hay hơn nếu bài viết mở rộng theo hướng “hãy dùng đúng phần tử HTML được tạo ra cho mục đích đó
    Nhiều lập trình viên SPA không hiểu rõ ngữ nghĩa của các phần tử HTML nên cứ phải phát minh lại bánh xe

    • Giá mà các phần tử dễ style hơn một chút
      Ví dụ date picker mặc định quá xấu nên người ta lại thay bằng bản dựa trên JS
    • Câu “hãy dùng nguyên nền tảng” được nhắc nhiều trong frontend kể từ sau HTML5, nhưng vẫn chưa lan rộng khắp nơi
    • Thực tế là đa số lập trình viên hầu như không biết các phần tử HTML, và cứ muốn giải quyết mọi thứ bằng một cái DIV
    • Khoảng năm 2010, style nút bấm khác nhau giữa các trình duyệt nên phải tự làm lấy
      Đó là bối cảnh khiến các nút tùy biến xuất hiện
  • Bây giờ đã có cả một thế hệ phải bấm lung tung khắp màn hình để tìm vùng có thể click
    Mười năm trước có ai đó đã làm cho việc kéo liên kết quan trọng hơn chọn văn bản, nên giờ gần như không thể chọn chữ được nữa
    Có khi phải fork trình duyệt mới sửa được chuyện này

    • Tôi có thói quen kéo liên kết để mở trong tab nền
      Nếu giữ Alt (hoặc Option) thì có thể chọn văn bản bên trong liên kết
    • Việc trên iOS cố sao chép số điện thoại mà lại tự động gọi cũng gây khó chịu tương tự
      Đó thực sự là hành vi không mong muốn
    • Văn bản không thể chọn được thật điên tiết
      Dùng ứng dụng TextSniper trên macOS thì có thể chọn vùng và dùng OCR để sao chép chữ
      Nhờ đó mà Google Analytics cũng đỡ khó dùng hơn một chút
    • Tôi cũng thường xuyên thất bại khi cố chọn một phần văn bản trong liên kết
      Những vấn đề như vậy cần được nhắc đến thường xuyên hơn
    • Cũng có tiện ích mở rộng trình duyệt để chọn văn bản trong liên kết
      Trước đây là Select Like A Boss, giờ gọi là Drag-Select Link Text
  • Tôi từng mất khá lâu loay hoay vì lỗi kích thước flexbox do stylesheet mặc định của Chrome có button {align-items: flex-start}
    Dù vậy, tôi vẫn cố dùng đúng phần tử HTML khi có thể, nhưng trong các side project nhỏ thì đôi lúc <div> lại tiện hơn

    • Thuộc tính appearance: none hữu ích để reset style nút bấm
      Tôi tạo class .unbuttonify để nó hoạt động như nút nhưng trông theo kiểu khác
    • Tôi muốn nhấn mạnh rằng lập trình viên frontend nên nắm vững kiến thức CSS cơ bản
  • Nên dùng phần tử đúng với mục đích ban đầu của nó bất cứ khi nào có thể

  • Tôi có hai điều không thích về nút bấm
    Một là kiểu gì cũng phải style lại cho nó,
    hai là cảnh báo không thể lồng nút vào nhau
    Trên thực tế, đây là tình huống gặp khá thường xuyên

  • LLM thường sinh ra những pattern sai như thế này
    Chúng hay phớt lờ tính năng mặc định của trình duyệt và triển khai theo cách phức tạp hơn mức cần thiết
    Tôi thường bảo Claude đơn giản hóa kiểu code như vậy
    Ngay cả trong TypeScript, chúng cũng có xu hướng làm cách xử lý lỗi trở nên kỳ quặc

    • LLM rất giỏi viết code, nhưng lại thiếu cảm quan kỹ nghệ phần mềm
    • Do đặc tính dự đoán token, LLM thường chọn các pattern phức tạp thường xuyên hơn
  • Tôi cố gắng mặc định dùng nút bấm bất cứ khi nào có thể
    Tuy nhiên, nếu về bản chất nó phải hoạt động như liên kết thì tôi dùng thẻ <a>

    • Tôi thường phân biệt: URL thay đổi thì là liên kết, không đổi thì là nút bấm
    • Nếu đó là một “siêu liên kết dùng để di chuyển trong web app” thì đó là thẻ <a>
  • Tôi từng thắc mắc vì sao lại có ý kiến dùng <div>

    • Có lẽ vì <div> thuận tiện hơn cho các kiểu tùy biến hình thức kỳ quặc
      Thế là nó vừa không giống nút bấm, vừa không hoạt động như nút bấm
    • Ví dụ, các site như TV Tropes gấp/mở danh sách dài theo dạng “thư mục”, và từng triển khai việc này bằng <div onclick>
    • Lý do phổ biến nhất là ngại ghi đè style nút mặc định
 
nemorize 2025-11-02

Hãy dùng button

background, border, outline, appearance, -webkit-appearance, cursor
Có quá nhiều stylesheet mặc định phải ghi đè T_T

 
rtyu1120 2025-11-03

Vì vậy mới có CSS Reset.