Tạo thành phần nút

Thông tin tổng quan cơ bản về cách tạo các thành phần <button> thích ứng với màu sắc, thích ứng và dễ tiếp cận.

Trong bài đăng này, tôi muốn chia sẻ suy nghĩ của mình về cách tạo một thành phần <button> thích ứng với màu sắc, phản hồi và hỗ trợ tiếp cận. Dùng thử bản minh hoạxem nguồn

Người dùng có thể tương tác với các nút thông qua bàn phím và chuột trong giao diện sáng và tối.

Nếu bạn thích xem video, hãy xem phiên bản video của bài đăng này trên YouTube:

Tổng quan

Hỗ trợ trình duyệt

  • Chrome: 1.
  • Edge: 12.
  • Firefox: 1.
  • Safari: 1.

Nguồn

Phần tử <button> được tạo để tương tác với người dùng. Sự kiện click của ứng dụng này kích hoạt từ bàn phím, chuột, thao tác chạm, giọng nói và nhiều thiết bị khác, với các quy tắc thông minh về thời gian. Thư viện này cũng đi kèm với một số kiểu mặc định trong mỗi trình duyệt, vì vậy, bạn có thể sử dụng trực tiếp các kiểu này mà không cần tuỳ chỉnh. Sử dụng color-scheme để chọn cả nút sáng và tối do trình duyệt cung cấp.

Ngoài ra, còn có nhiều loại nút, mỗi loại nút được hiển thị trong phần nhúng Codepen trước đó. <button> không có loại sẽ thích ứng với việc nằm trong <form>, thay đổi thành loại gửi.

<!-- buttons -->
<button></button>
<button type="submit"></button>
<button type="button"></button>
<button type="reset"></button>

<!-- button state -->
<button disabled></button>

<!-- input buttons -->
<input type="button" />
<input type="file">

Trong Thử thách GUI của tháng này, mỗi nút sẽ có kiểu để giúp phân biệt rõ ràng ý định của nút đó. Nút đặt lại sẽ có màu cảnh báo vì chúng có tác động huỷ bỏ, còn nút gửi sẽ có văn bản nhấn nhá màu xanh dương để chúng xuất hiện nổi bật hơn một chút so với các nút thông thường.

Xem trước tập hợp cuối cùng của tất cả các loại nút, hiển thị trong một biểu mẫu và không phải trong một biểu mẫu, với các bổ sung thú vị cho nút biểu tượng và nút tuỳ chỉnh.
Bản xem trước tập hợp cuối cùng của tất cả các loại nút, hiển thị trong một biểu mẫu và không phải trong một biểu mẫu, với các bổ sung thú vị cho nút biểu tượng và nút tuỳ chỉnh

Nút cũng có các lớp giả để CSS sử dụng cho việc tạo kiểu. Các lớp này cung cấp các móc CSS để tuỳ chỉnh cảm giác của nút: :hover cho khi chuột di qua nút, :active cho khi chuột hoặc bàn phím đang nhấn và :focus hoặc :focus-visible để hỗ trợ tạo kiểu công nghệ hỗ trợ.

button:hover {}
button:active {}
button:focus {}
button:focus-visible {}
Bản xem trước bộ hoàn chỉnh của tất cả các loại nút trong giao diện tối.
Xem trước tập hợp cuối cùng của tất cả các loại nút trong giao diện tối

Markup (note: đây là tên ứng dụng)

Ngoài các loại nút do quy cách HTML cung cấp, tôi đã thêm một nút có biểu tượng và một nút có lớp tuỳ chỉnh btn-custom.

<button>Default</button>
<input type="button" value="<input>"/>
<button>
  <svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
    <path d="..." />
  </svg>
  Icon
</button>
<button type="submit">Submit</button>
<button type="button">Type Button</button>
<button type="reset">Reset</button>
<button disabled>Disabled</button>
<button class="btn-custom">Custom</button>
<input type="file">

Sau đó, để kiểm thử, mỗi nút sẽ được đặt bên trong một biểu mẫu. Bằng cách này, tôi có thể đảm bảo rằng các kiểu được cập nhật phù hợp cho nút mặc định, hoạt động như một nút gửi. Tôi cũng chuyển đổi chiến lược biểu tượng, từ SVG cùng dòng sang SVG được che, để đảm bảo cả hai đều hoạt động tốt như nhau.

<form>
  <button>Default</button>
  <input type="button" value="<input>"/>
  <button>Icon <span data-icon="cloud"></span></button>
  <button type="submit">Submit</button>
  <button type="button">Type Button</button>
  <button type="reset">Reset</button>
  <button disabled>Disabled</button>
  <button class="btn-custom btn-large" type="button">Large Custom</button>
  <input type="file">
</form>

Ma trận tổ hợp khá lớn ở thời điểm này. Giữa các loại nút, lớp giả và nằm trong hoặc ngoài biểu mẫu, có hơn 20 tổ hợp nút. Thật may là CSS có thể giúp chúng ta diễn đạt rõ ràng từng thuộc tính!

Hỗ trợ tiếp cận

Các phần tử nút có thể truy cập tự nhiên nhưng có một số tính năng nâng cao phổ biến.

Di chuột và lấy nét cùng nhau

Tôi muốn nhóm :hover:focus với bộ chọn giả lập chức năng :is(). Điều này giúp đảm bảo giao diện của tôi luôn xem xét bàn phím và các kiểu công nghệ hỗ trợ.

button:is(:hover, :focus) {
  
}
Hãy dùng thử bản minh hoạ!

Vòng tròn lấy nét tương tác

Tôi muốn tạo ảnh động cho vòng tiêu điểm cho người dùng bàn phím và công nghệ hỗ trợ. Tôi thực hiện việc này bằng cách tạo ảnh động cho đường viền cách nút 5px, nhưng chỉ khi nút không hoạt động. Điều này tạo ra hiệu ứng khiến vòng lấy nét thu nhỏ trở lại kích thước nút khi nhấn.

:where(button, input):where(:not(:active)):focus-visible {
  outline-offset: 5px;
}

Đảm bảo độ tương phản màu đạt yêu cầu

Có ít nhất 4 cách kết hợp màu khác nhau giữa chế độ sáng và tối cần xem xét độ tương phản màu: nút, nút gửi, nút đặt lại và nút bị vô hiệu hoá. VisBug được dùng ở đây để kiểm tra và hiển thị tất cả điểm số cùng một lúc:

Ẩn biểu tượng khỏi những người không nhìn thấy

Khi tạo nút biểu tượng, biểu tượng phải hỗ trợ trực quan cho văn bản nút. Điều này cũng có nghĩa là biểu tượng này không có giá trị đối với người khiếm thị. May mắn thay, trình duyệt cung cấp một cách để ẩn các mục khỏi công nghệ trình đọc màn hình để những người khiếm thị không bị làm phiền bởi hình ảnh nút trang trí:

<button>
  <svg … aria-hidden="true">...</svg>
  Icon Button
</button>
Công cụ dành cho nhà phát triển Chrome hiển thị cây hỗ trợ tiếp cận cho nút này. Cây này bỏ qua hình ảnh nút vì hình ảnh này có aria-hidden được đặt thành true.
Công cụ của Chrome cho nhà phát triển hiển thị cây hỗ trợ tiếp cận cho nút. Cây này bỏ qua hình ảnh nút vì thuộc tính aria-hidden được đặt thành true (đúng)

Kiểu

Trong phần tiếp theo, trước tiên, tôi sẽ thiết lập một hệ thống thuộc tính tuỳ chỉnh để quản lý các kiểu thích ứng của nút. Với các thuộc tính tuỳ chỉnh đó, tôi có thể bắt đầu chọn các phần tử và tuỳ chỉnh giao diện của chúng.

Chiến lược tuỳ chỉnh thích ứng

Chiến lược thuộc tính tuỳ chỉnh được sử dụng trong Thử thách GUI này rất giống với chiến lược được sử dụng trong phần tạo bảng phối màu. Đối với hệ thống màu sáng và tối thích ứng, một thuộc tính tuỳ chỉnh cho mỗi giao diện sẽ được xác định và đặt tên tương ứng. Sau đó, một thuộc tính tuỳ chỉnh duy nhất được dùng để lưu giữ giá trị hiện tại của giao diện và được gán cho một thuộc tính CSS. Sau đó, bạn có thể cập nhật một thuộc tính tuỳ chỉnh thành một giá trị khác, sau đó cập nhật kiểu nút.

button {
  --_bg-light: white;
  --_bg-dark: black;
  --_bg: var(--_bg-light);

  background-color: var(--_bg);
}

@media (prefers-color-scheme: dark) {
  button {
    --_bg: var(--_bg-dark);
  }
}

Điều tôi thích là giao diện sáng và tối được khai báo rõ ràng. Tính năng gián tiếp và trừu tượng được chuyển sang thuộc tính tuỳ chỉnh --_bg, hiện là thuộc tính "phản ứng" duy nhất; --_bg-light--_bg-dark là tĩnh. Bạn cũng có thể đọc rõ rằng giao diện sáng là giao diện mặc định và giao diện tối chỉ được áp dụng có điều kiện.

Chuẩn bị cho tính nhất quán trong thiết kế

Bộ chọn dùng chung

Bộ chọn sau đây được dùng để nhắm đến tất cả các loại nút và ban đầu sẽ hơi khó hiểu. :where() được sử dụng để tuỳ chỉnh nút không cần có thông tin cụ thể. Các nút thường được điều chỉnh cho các tình huống thay thế và bộ chọn :where() đảm bảo rằng tác vụ đó dễ dàng. Bên trong :where(), mỗi loại nút đều được chọn, bao gồm cả ::file-selector-button, không thể sử dụng bên trong :is() hoặc :where().

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"],
  input[type="file"]
),
:where(input[type="file"])::file-selector-button {
  
}

Tất cả các thuộc tính tuỳ chỉnh sẽ nằm trong phạm vi của bộ chọn này. Đã đến lúc xem xét tất cả các thuộc tính tuỳ chỉnh! Có khá nhiều thuộc tính tuỳ chỉnh được sử dụng trong nút này. Tôi sẽ mô tả từng nhóm trong quá trình thực hiện, sau đó chia sẻ ngữ cảnh chuyển động tối và giảm ở cuối phần này.

Màu nhấn của nút

Nút Gửi và biểu tượng là nơi tuyệt vời để thêm màu sắc:

--_accent-light: hsl(210 100% 40%);
--_accent-dark: hsl(210 50% 70%);
--_accent: var(--_accent-light);

Màu văn bản nút

Màu văn bản của nút không phải là màu trắng hoặc đen, mà là các phiên bản tối hoặc sáng của --_accent bằng cách sử dụng hsl() và tuân theo màu sắc 210:

--_text-light: hsl(210 10% 30%);
--_text-dark: hsl(210 5% 95%);
--_text: var(--_text-light);

Màu nền của nút

Nền của nút tuân theo cùng một mẫu hsl(), ngoại trừ các nút giao diện sáng – các nút này được đặt thành màu trắng để bề mặt của chúng xuất hiện gần người dùng hoặc ở phía trước các bề mặt khác:

--_bg-light: hsl(0 0% 100%);
--_bg-dark: hsl(210 9% 31%);
--_bg: var(--_bg-light);

Nền của nút

Màu nền này dùng để làm cho một nền tảng xuất hiện phía sau các nền tảng khác, rất hữu ích cho nền của dữ liệu đầu vào tệp:

--_input-well-light: hsl(210 16% 87%);
--_input-well-dark: hsl(204 10% 10%);
--_input-well: var(--_input-well-light);

Khoảng đệm của nút

Khoảng cách xung quanh văn bản trong nút được thực hiện bằng cách sử dụng đơn vị ch, một độ dài tương đối so với kích thước phông chữ. Điều này trở nên quan trọng khi các nút lớn chỉ có thể tăng font-size và tỷ lệ nút theo tỷ lệ:

--_padding-inline: 1.75ch;
--_padding-block: .75ch;

Đường viền nút

Bán kính đường viền nút được lưu vào một thuộc tính tuỳ chỉnh để dữ liệu đầu vào của tệp có thể khớp với các nút khác. Màu đường viền tuân theo hệ thống màu thích ứng đã thiết lập:

--_border-radius: .5ch;

--_border-light: hsl(210 14% 89%);
--_border-dark: var(--_bg-dark);
--_border: var(--_border-light);

Hiệu ứng làm nổi bật khi di chuột qua nút

Các thuộc tính này thiết lập một thuộc tính kích thước để chuyển đổi khi tương tác và màu làm nổi bật tuân theo hệ thống màu thích ứng. Chúng ta sẽ đề cập đến cách các thành phần này tương tác với nhau ở phần sau của bài đăng này, nhưng cuối cùng, các thành phần này được dùng cho hiệu ứng box-shadow:

--_highlight-size: 0;

--_highlight-light: hsl(210 10% 71% / 25%);
--_highlight-dark: hsl(210 10% 5% / 25%);
--_highlight: var(--_highlight-light);

Độ bóng văn bản của nút

Mỗi nút có một kiểu bóng văn bản tinh tế. Điều này giúp văn bản nằm trên nút, cải thiện khả năng đọc và thêm một lớp trình bày đẹp mắt.

--_ink-shadow-light: 0 1px 0 var(--_border-light);
--_ink-shadow-dark: 0 1px 0 hsl(210 11% 15%);
--_ink-shadow: var(--_ink-shadow-light);

Biểu tượng nút

Biểu tượng có kích thước bằng hai ký tự nhờ đơn vị ch có độ dài tương đối. Điều này sẽ giúp biểu tượng điều chỉnh theo tỷ lệ với văn bản của nút. Màu biểu tượng dựa vào --_accent-color để có màu thích ứng và nằm trong giao diện.

--_icon-size: 2ch;
--_icon-color: var(--_accent);

Bóng nút

Để bóng thích ứng đúng cách với ánh sáng và bóng tối, bóng cần thay đổi cả màu sắc và độ mờ. Bóng đổ trong giao diện sáng sẽ đẹp nhất khi có màu sắc tinh tế và được phủ màu theo màu của bề mặt mà chúng phủ lên. Bóng của giao diện tối cần tối hơn và bão hoà hơn để có thể phủ lên các màu bề mặt tối hơn.

--_shadow-color-light: 220 3% 15%;
--_shadow-color-dark: 220 40% 2%;
--_shadow-color: var(--_shadow-color-light);

--_shadow-strength-light: 1%;
--_shadow-strength-dark: 25%;
--_shadow-strength: var(--_shadow-strength-light);

Với màu sắc và độ đậm nhạt thích ứng, tôi có thể kết hợp hai độ sâu của bóng:

--_shadow-1: 0 1px 2px -1px hsl(var(--_shadow-color)/calc(var(--_shadow-strength) + 9%));

--_shadow-2: 
  0 3px 5px -2px hsl(var(--_shadow-color)/calc(var(--_shadow-strength) + 3%)),
  0 7px 14px -5px hsl(var(--_shadow-color)/calc(var(--_shadow-strength) + 5%));

Hơn nữa, để tạo cho các nút có giao diện hơi 3D, hiệu ứng đổ bóng hộp 1px sẽ tạo ra ảo giác:

--_shadow-depth-light: 0 1px var(--_border-light);
--_shadow-depth-dark: 0 1px var(--_bg-dark);
--_shadow-depth: var(--_shadow-depth-light);

Hiệu ứng chuyển đổi nút

Theo mẫu cho màu thích ứng, tôi tạo hai thuộc tính tĩnh để chứa các tuỳ chọn hệ thống thiết kế:

--_transition-motion-reduce: ;
--_transition-motion-ok:
  box-shadow 145ms ease,
  outline-offset 145ms ease
;
--_transition: var(--_transition-motion-reduce);

Tất cả thuộc tính cùng nhau trong bộ chọn

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"],
  input[type="file"]
),
:where(input[type="file"])::file-selector-button {
  --_accent-light: hsl(210 100% 40%);
  --_accent-dark: hsl(210 50% 70%);
  --_accent: var(--_accent-light);

--_text-light: hsl(210 10% 30%); --_text-dark: hsl(210 5% 95%); --_text: var(--_text-light);

--_bg-light: hsl(0 0% 100%); --_bg-dark: hsl(210 9% 31%); --_bg: var(--_bg-light);

--_input-well-light: hsl(210 16% 87%); --_input-well-dark: hsl(204 10% 10%); --_input-well: var(--_input-well-light);

--_padding-inline: 1.75ch; --_padding-block: .75ch;

--_border-radius: .5ch; --_border-light: hsl(210 14% 89%); --_border-dark: var(--_bg-dark); --_border: var(--_border-light);

--_highlight-size: 0; --_highlight-light: hsl(210 10% 71% / 25%); --_highlight-dark: hsl(210 10% 5% / 25%); --_highlight: var(--_highlight-light);

--_ink-shadow-light: 0 1px 0 hsl(210 14% 89%); --_ink-shadow-dark: 0 1px 0 hsl(210 11% 15%); --_ink-shadow: var(--_ink-shadow-light);

--_icon-size: 2ch; --_icon-color-light: var(--_accent-light); --_icon-color-dark: var(--_accent-dark); --_icon-color: var(--accent, var(--_icon-color-light));

--_shadow-color-light: 220 3% 15%; --_shadow-color-dark: 220 40% 2%; --_shadow-color: var(--_shadow-color-light); --_shadow-strength-light: 1%; --_shadow-strength-dark: 25%; --_shadow-strength: var(--_shadow-strength-light); --_shadow-1: 0 1px 2px -1px hsl(var(--_shadow-color)/calc(var(--_shadow-strength) + 9%)); --_shadow-2: 0 3px 5px -2px hsl(var(--_shadow-color)/calc(var(--_shadow-strength) + 3%)), 0 7px 14px -5px hsl(var(--_shadow-color)/calc(var(--_shadow-strength) + 5%)) ;

--_shadow-depth-light: hsl(210 14% 89%); --_shadow-depth-dark: var(--_bg-dark); --_shadow-depth: var(--_shadow-depth-light);

--_transition-motion-reduce: ; --_transition-motion-ok: box-shadow 145ms ease, outline-offset 145ms ease ; --_transition: var(--_transition-motion-reduce); }

Các nút mặc định hiển thị cạnh nhau trong giao diện sáng và tối.

Điều chỉnh giao diện tối

Giá trị của mẫu thuộc tính tĩnh -light-dark sẽ trở nên rõ ràng khi bạn đặt các thuộc tính giao diện tối:

@media (prefers-color-scheme: dark) {
  :where(
    button,
    input[type="button"],
    input[type="submit"],
    input[type="reset"],
    input[type="file"]
  ),
  :where(input[type="file"])::file-selector-button {
    --_bg: var(--_bg-dark);
    --_text: var(--_text-dark);
    --_border: var(--_border-dark);
    --_accent: var(--_accent-dark);
    --_highlight: var(--_highlight-dark);
    --_input-well: var(--_input-well-dark);
    --_ink-shadow: var(--_ink-shadow-dark);
    --_shadow-depth: var(--_shadow-depth-dark);
    --_shadow-color: var(--_shadow-color-dark);
    --_shadow-strength: var(--_shadow-strength-dark);
  }
}

Không chỉ dễ đọc, mà người dùng các nút tuỳ chỉnh này cũng có thể sử dụng các thành phần hỗ trợ trần một cách tự tin vì chúng sẽ thích ứng phù hợp với lựa chọn ưu tiên của người dùng.

Giảm việc điều chỉnh chuyển động

Nếu người dùng truy cập này không có vấn đề gì với chuyển động, hãy chỉ định --_transition cho var(--_transition-motion-ok):

@media (prefers-reduced-motion: no-preference) {
  :where(
    button,
    input[type="button"],
    input[type="submit"],
    input[type="reset"],
    input[type="file"]
  ),
  :where(input[type="file"])::file-selector-button {
    --_transition: var(--_transition-motion-ok);
  }
}

Một số kiểu dùng chung

Bạn cần đặt phông chữ của các nút và dữ liệu đầu vào thành inherit để khớp với phông chữ còn lại của trang; nếu không, trình duyệt sẽ tạo kiểu cho các nút và dữ liệu đầu vào đó. Điều này cũng áp dụng cho letter-spacing. Việc đặt line-height thành 1.5 sẽ đặt kích thước hộp thư để cung cấp cho văn bản một số không gian ở trên và dưới:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"],
  input[type="file"]
),
:where(input[type="file"])::file-selector-button {
  /* …CSS variables */

  font: inherit;
  letter-spacing: inherit;
  line-height: 1.5;
  border-radius: var(--_border-radius);
}

Ảnh chụp màn hình hiển thị các nút sau khi áp dụng các kiểu trước đó.

Tạo kiểu cho nút

Điều chỉnh bộ chọn

Bộ chọn input[type="file"] không phải là phần nút của dữ liệu đầu vào, mà là phần tử giả ::file-selector-button, vì vậy, tôi đã xoá input[type="file"] khỏi danh sách:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"],
  input[type="file"]
),
:where(input[type="file"])::file-selector-button {
  
}

Điều chỉnh con trỏ và thao tác chạm

Trước tiên, tôi định kiểu con trỏ thành kiểu pointer. Kiểu này giúp nút cho người dùng chuột biết rằng nút đó có thể tương tác. Sau đó, tôi thêm touch-action: manipulation để các lượt nhấp không cần phải chờ và quan sát một lượt nhấp đúp tiềm ẩn, giúp các nút cảm thấy nhanh hơn:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  cursor: pointer;
  touch-action: manipulation;
}

Màu sắc và đường viền

Tiếp theo, tôi tuỳ chỉnh kích thước phông chữ, nền, văn bản và màu đường viền bằng cách sử dụng một số thuộc tính tuỳ chỉnh thích ứng đã thiết lập trước đó:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  

  font-size: var(--_size, 1rem);
  font-weight: 700;
  background: var(--_bg);
  color: var(--_text);
  border: 2px solid var(--_border);
}

Ảnh chụp màn hình hiển thị các nút sau khi áp dụng các kiểu trước đó.

Bóng

Các nút này được áp dụng một số kỹ thuật tuyệt vời. text-shadow thích ứng với chế độ sáng và tối, tạo ra giao diện tinh tế, dễ chịu cho văn bản nút nằm gọn trên nền. Đối với box-shadow, hệ thống sẽ chỉ định 3 bóng đổ. Đầu tiên, --_shadow-2 là một bóng hộp thông thường. Bóng thứ hai là một thủ thuật khiến mắt người dùng có cảm giác nút này được bo tròn một chút. Bóng cuối cùng là cho phần đánh dấu khi di chuột, ban đầu có kích thước là 0, nhưng sau đó sẽ được cung cấp kích thước và chuyển đổi để có vẻ như phát triển từ nút.

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  

  box-shadow: 
    var(--_shadow-2),
    var(--_shadow-depth),
    0 0 0 var(--_highlight-size) var(--_highlight)
  ;
  text-shadow: var(--_ink-shadow);
}

Ảnh chụp màn hình hiển thị các nút sau khi áp dụng các kiểu trước đó.

Bố cục

Tôi đã đặt bố cục flexbox cho nút này, cụ thể là bố cục inline-flex phù hợp với nội dung của nút. Sau đó, tôi căn giữa văn bản và căn chỉnh theo chiều dọc và chiều ngang các phần tử con vào giữa. Điều này sẽ giúp các biểu tượng và các thành phần nút khác căn chỉnh đúng cách.

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  

  display: inline-flex;
  justify-content: center;
  align-items: center;
  text-align: center;
}

Ảnh chụp màn hình hiển thị các nút sau khi áp dụng các kiểu trước đó.

Giãn cách

Đối với khoảng cách nút, tôi đã sử dụng gap để các nút đồng cấp không chạm vào nhau và các thuộc tính logic để khoảng đệm hoạt động cho tất cả bố cục văn bản.

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  

  gap: 1ch;
  padding-block: var(--_padding-block);
  padding-inline: var(--_padding-inline);
}

Ảnh chụp màn hình hiển thị các nút sau khi áp dụng các kiểu trước đó.

Trải nghiệm người dùng khi chạm và dùng chuột

Phần tiếp theo chủ yếu dành cho người dùng cảm ứng trên thiết bị di động. Thuộc tính đầu tiên, user-select, dành cho tất cả người dùng; thuộc tính này ngăn văn bản làm nổi bật văn bản nút. Điều này chủ yếu xảy ra trên các thiết bị cảm ứng khi người dùng nhấn và giữ một nút, hệ điều hành sẽ làm nổi bật văn bản của nút đó.

Nhìn chung, tôi nhận thấy đây không phải là trải nghiệm người dùng với các nút trong ứng dụng tích hợp sẵn, vì vậy, tôi tắt tính năng này bằng cách đặt user-select thành không có. Nhấn vào màu đánh dấu (-webkit-tap-highlight-color) và trình đơn theo bối cảnh của hệ điều hành (-webkit-touch-callout) là các tính năng khác của nút tập trung vào web không phù hợp với kỳ vọng chung của người dùng về nút. Vì vậy, tôi cũng xoá các tính năng này.

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  

  user-select: none;
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
}

Kiểu chuyển cảnh

Biến --_transition thích ứng được gán cho thuộc tính transition (hiệu ứng chuyển đổi):

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
),
:where(input[type="file"])::file-selector-button {
  

  transition: var(--_transition);
}

Khi di chuột, trong khi người dùng không chủ động nhấn, hãy điều chỉnh kích thước làm nổi bật bóng để tạo ra một giao diện tập trung đẹp mắt, có vẻ như phát triển từ bên trong nút:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
):where(:not(:active):hover) {
  --_highlight-size: .5rem;
}

Khi lấy tiêu điểm, hãy tăng độ lệch đường viền tiêu điểm so với nút, đồng thời tạo cho tiêu điểm một giao diện đẹp mắt, có vẻ như phát triển từ bên trong nút:

:where(button, input):where(:not(:active)):focus-visible {
  outline-offset: 5px;
}

Biểu tượng

Để xử lý biểu tượng, bộ chọn có thêm bộ chọn :where() cho các phần tử con SVG trực tiếp hoặc các phần tử có thuộc tính tuỳ chỉnh data-icon. Kích thước biểu tượng được đặt bằng thuộc tính tuỳ chỉnh sử dụng thuộc tính logic cùng dòng và khối. Màu nét vẽ được đặt, cũng như drop-shadow để khớp với text-shadow. flex-shrink được đặt thành 0 để biểu tượng không bao giờ bị vỡ. Cuối cùng, tôi chọn biểu tượng có đường viền và gán các kiểu đó ở đây bằng các đầu và điểm nối đường viền fill: noneround:

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
) > :where(svg, [data-icon]) {
  block-size: var(--_icon-size);
  inline-size: var(--_icon-size);
  stroke: var(--_icon-color);
  filter: drop-shadow(var(--_ink-shadow));

  flex-shrink: 0;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

Ảnh chụp màn hình hiển thị các nút sau khi áp dụng các kiểu trước đó.

Tuỳ chỉnh nút gửi

Tôi muốn các nút gửi có giao diện được quảng bá một chút và tôi đã đạt được điều này bằng cách đặt màu văn bản của các nút thành màu nhấn:

:where(
  [type="submit"], 
  form button:not([type],[disabled])
) {
  --_text: var(--_accent);
}

Ảnh chụp màn hình hiển thị các nút sau khi áp dụng các kiểu trước đó.

Tuỳ chỉnh nút đặt lại

Tôi muốn các nút đặt lại có một số dấu hiệu cảnh báo tích hợp để cảnh báo người dùng về hành vi có thể gây hại. Tôi cũng chọn tạo kiểu cho nút giao diện sáng bằng các điểm nhấn màu đỏ nhiều hơn so với giao diện tối. Bạn có thể tuỳ chỉnh bằng cách thay đổi màu cơ bản sáng hoặc tối thích hợp và nút này sẽ cập nhật kiểu:

:where([type="reset"]) {
  --_border-light: hsl(0 100% 83%);
  --_highlight-light: hsl(0 100% 89% / 20%);
  --_text-light: hsl(0 80% 50%);
  --_text-dark: hsl(0 100% 89%);
}

Tôi cũng nghĩ rằng màu đường viền tiêu điểm sẽ phù hợp với màu nhấn là màu đỏ. Màu văn bản sẽ điều chỉnh từ màu đỏ đậm sang màu đỏ nhạt. Tôi sẽ làm cho màu đường viền khớp với màu này bằng từ khoá currentColor:

:where([type="reset"]):focus-visible {
  outline-color: currentColor;
}

Ảnh chụp màn hình hiển thị các nút sau khi áp dụng các kiểu trước đó.

Tuỳ chỉnh các nút đã tắt

Rất thường thấy các nút bị vô hiệu hoá có độ tương phản màu kém trong quá trình cố gắng làm giảm nút bị vô hiệu hoá để nút đó có vẻ ít hoạt động hơn. Tôi đã kiểm thử từng bộ màu và đảm bảo chúng đã vượt qua, điều chỉnh giá trị độ sáng HSL cho đến khi điểm số vượt qua trong DevTools hoặc VisBug.

:where(
  button,
  input[type="button"],
  input[type="submit"],
  input[type="reset"]
)[disabled] {
  --_bg: none;
  --_text-light: hsl(210 7% 40%);
  --_text-dark: hsl(210 11% 71%);

  cursor: not-allowed;
  box-shadow: var(--_shadow-1);
}

Ảnh chụp màn hình hiển thị các nút sau khi áp dụng các kiểu trước đó.

Tuỳ chỉnh nút nhập tệp

Nút nhập tệp là vùng chứa cho một span và một nút. CSS có thể định kiểu cho vùng chứa dữ liệu đầu vào một chút, cũng như nút lồng nhau, nhưng không thể định kiểu cho span. Vùng chứa được cấp max-inline-size để không lớn hơn mức cần thiết, trong khi inline-size: 100% sẽ tự thu nhỏ và vừa với các vùng chứa nhỏ hơn. Màu nền được đặt thành màu thích ứng đậm hơn các bề mặt khác, vì vậy, màu nền sẽ nằm phía sau nút bộ chọn tệp.

:where(input[type="file"]) {
  inline-size: 100%;
  max-inline-size: max-content;
  background-color: var(--_input-well);
}

Nút bộ chọn tệp và các nút loại dữ liệu đầu vào được cung cấp riêng appearance: none để xoá mọi kiểu do trình duyệt cung cấp mà các kiểu nút khác không ghi đè.

:where(input[type="button"]),
:where(input[type="file"])::file-selector-button {
  appearance: none;
}

Cuối cùng, lề được thêm vào inline-end của nút để đẩy văn bản span ra khỏi nút, tạo ra một khoảng trống.

:where(input[type="file"])::file-selector-button {
  margin-inline-end: var(--_padding-inline);
}

Ảnh chụp màn hình hiển thị các nút sau khi áp dụng các kiểu trước đó.

Các trường hợp ngoại lệ đặc biệt về giao diện tối

Tôi đã đặt nền tối hơn cho các nút hành động chính để văn bản có độ tương phản cao hơn, giúp các nút này có giao diện được quảng bá một chút.

@media (prefers-color-scheme: dark) {
  :where(
    [type="submit"],
    [type="reset"],
    [disabled],
    form button:not([type="button"])
  ) {
    --_bg: var(--_input-well);
  }
}

Ảnh chụp màn hình hiển thị các nút sau khi áp dụng các kiểu trước đó.

Tạo biến thể

Để vui và vì tính thực tế, tôi đã chọn hướng dẫn cách tạo một vài biến thể. Một biến thể rất sống động, tương tự như giao diện của các nút chính. Một biến thể khác có kích thước lớn. Biến thể cuối cùng có biểu tượng được tô màu chuyển tiếp.

Nút sống động

Để tạo kiểu nút này, tôi đã ghi đè các thuộc tính cơ sở trực tiếp bằng màu xanh dương. Mặc dù cách này nhanh chóng và dễ dàng, nhưng sẽ xoá các thuộc tính thích ứng và trông giống nhau trong cả giao diện sáng và tối.

.btn-custom {
  --_bg: linear-gradient(hsl(228 94% 67%), hsl(228 81% 59%));
  --_border: hsl(228 89% 63%);
  --_text: hsl(228 89% 100%);
  --_ink-shadow: 0 1px 0 hsl(228 57% 50%);
  --_highlight: hsl(228 94% 67% / 20%);
}

Nút tuỳ chỉnh hiển thị ở chế độ sáng và tối. Màu này có màu xanh dương rất sống động như các nút hành động chính thông thường.

Nút lớn

Kiểu nút này được tạo bằng cách sửa đổi thuộc tính tuỳ chỉnh --_size. Khoảng đệm và các thành phần không gian khác tương ứng với kích thước này, điều chỉnh theo tỷ lệ với kích thước mới.

.btn-large {
  --_size: 1.5rem;
}

Nút lớn xuất hiện bên cạnh nút tuỳ chỉnh, lớn hơn khoảng 150 lần.

Nút biểu tượng

Hiệu ứng biểu tượng này không liên quan gì đến kiểu nút của chúng ta, nhưng hiệu ứng này cho biết cách đạt được hiệu ứng đó chỉ bằng một vài thuộc tính CSS và mức độ hiệu quả của nút trong việc xử lý các biểu tượng không phải SVG nội tuyến.

[data-icon="cloud"] {
  --icon-cloud: url("https://api.iconify.design/mdi:apple-icloud.svg") center / contain no-repeat;

  -webkit-mask: var(--icon-cloud);
  mask: var(--icon-cloud);
  background: linear-gradient(to bottom, var(--_accent-dark), var(--_accent-light));
}

Một nút có biểu tượng xuất hiện trong giao diện sáng và tối.

Kết luận

Giờ thì bạn đã biết cách tôi làm, còn bạn thì sao‽ 🙂

Hãy đa dạng hoá các phương pháp và tìm hiểu tất cả các cách xây dựng trên web.

Hãy tạo bản minh hoạ, gửi đường liên kết cho tôi trên Twitter và tôi sẽ thêm bản minh hoạ đó vào phần phối lại của cộng đồng ở bên dưới!

Bản phối lại của cộng đồng

Chưa có nội dung nào.

Tài nguyên