Bölme düğmesi bileşeni oluşturma

Erişilebilir bölünmüş düğme bileşeni oluşturmaya dair temel bir genel bakış.

Bu gönderide, bölünmüş düğme oluşturmanın bir yolunu paylaşmak istiyorum . Demoyu deneyin.

Demo

Videoyu tercih ediyorsanız bu yayının YouTube sürümünü burada bulabilirsiniz:

Genel Bakış

Bölünmüş düğmeler, birincil bir düğmeyi ve ek düğmelerin listesini gizleyen düğmelerdir. Daha az kullanılan ikincil işlemleri gerektiğinde iç içe yerleştirirken ortak bir işlemi göstermek için yararlıdırlar. Bölünmüş düğmeler, yoğun bir tasarımın minimal görünmesine yardımcı olabilir. Gelişmiş bölünmüş düğmeler, kullanıcının son işlemini bile hatırlayabilir ve birincil konuma taşıyabilir.

E-posta uygulamanızda yaygın olarak kullanılan bir bölme düğmesi bulunur. Birincil işlem göndermektir ancak daha sonra gönderebilir veya taslak olarak kaydedebilirsiniz:

E-posta uygulamasında gösterilen örnek bir bölünmüş düğme.

Kullanıcının etrafına bakmasına gerek olmadığı için paylaşılan işlem alanı kullanışlıdır. Temel e-posta işlemlerinin bölünmüş düğmede yer aldığını bilirler.

Parçalar

Genel koordinasyonlarını ve nihai kullanıcı deneyimlerini tartışmadan önce bölünmüş düğmelerin temel bölümlerini inceleyelim. VisBug'un erişilebilirlik inceleme aracı, bileşenin makro görünümünü göstermeye yardımcı olmak için burada kullanılır. Bu araç, her ana bölüm için HTML, stil ve erişilebilirlik özelliklerini gösterir.

Bölünmüş düğmeyi oluşturan HTML öğeleri.

Üst düzey bölünmüş düğme kapsayıcısı

En üst düzey bileşen, birincil işlemi ve .gui-popup-button içeren gui-split-button sınıfına sahip bir satır içi esnek kutudur.

İncelenen gui-split-button sınıfı ve bu sınıfta kullanılan CSS özellikleri gösteriliyor.

Birincil işlem düğmesi

Başlangıçta görünür ve odaklanılabilir olan <button>, odak, fareyle üzerine gelme ve etkin etkileşimlerin .gui-split-button içinde görünmesi için iki eşleşen köşe şekliyle kapsayıcıya sığar.

Düğme öğesinin CSS kurallarını gösteren denetleyici.

Pop-up açma/kapatma düğmesi

"Pop-up düğme" destek öğesi, ikincil düğmelerin listesini etkinleştirmek ve bu listeye atıfta bulunmak için kullanılır. <button> olmadığına ve odaklanamayacağınıza dikkat edin. Ancak pop-up'ı sunmak için kullanılan .gui-popup için konumlandırma ankrajı ve :focus-within için ana bilgisayardır.

gui-popup-button sınıfının CSS kurallarını gösteren denetleyici.

Pop-up kart

Bu, ankrajına .gui-popup-button göre konumlandırılmış, mutlak olarak yerleştirilmiş ve semantik olarak düğme listesini sarmalayan bir yüzen kart alt öğesidir.

gui-popup sınıfının CSS kurallarını gösteren denetleyici

İkincil işlemler

Birincil işlem düğmesinden biraz daha küçük yazı tipi boyutuna sahip, odaklanılabilir bir <button>, birincil düğmeyle uyumlu bir simge ve stil içerir.

Düğme öğesinin CSS kurallarını gösteren denetleyici.

Özel özellikler

Aşağıdaki değişkenler, renk uyumu oluşturmanıza ve bileşen genelinde kullanılan değerleri değiştirmek için merkezi bir yer oluşturmanıza yardımcı olur.

@custom-media --motionOK (prefers-reduced-motion: no-preference);
@custom-media --dark (prefers-color-scheme: dark);
@custom-media --light (prefers-color-scheme: light);

.gui-split-button {
  --theme:             hsl(220 75% 50%);
  --theme-hover:  hsl(220 75% 45%);
  --theme-active:  hsl(220 75% 40%);
  --theme-text:      hsl(220 75% 25%);
  --theme-border: hsl(220 50% 75%);
  --ontheme:         hsl(220 90% 98%);
  --popupbg:         hsl(220 0% 100%);

  --border: 1px solid var(--theme-border);
  --radius: 6px;
  --in-speed: 50ms;
  --out-speed: 300ms;

  @media (--dark) {
    --theme:             hsl(220 50% 60%);
    --theme-hover:  hsl(220 50% 65%);
    --theme-active:  hsl(220 75% 70%);
    --theme-text:      hsl(220 10% 85%);
    --theme-border: hsl(220 20% 70%);
    --ontheme:         hsl(220 90% 5%);
    --popupbg:         hsl(220 10% 30%);
  }
}

Düzenler ve renk

Brüt kar

Öğe, özel sınıf adıyla <div> olarak başlar.

<div class="gui-split-button"></div>

Birincil düğmeyi ve .gui-popup-button öğelerini ekleyin.

<div class="gui-split-button">
  <button>Send</button>
  <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions"></span>
</div>

aria-haspopup ve aria-expanded aria özelliklerine dikkat edin. Bu ipuçları, ekran okuyucuların bölünmüş düğme deneyiminin işlevini ve durumunu bilmesi açısından önemlidir. title özelliği herkes için faydalıdır.

Bir <svg> simgesi ve .gui-popup kapsayıcı öğesi ekleyin.

<div class="gui-split-button">
  <button>Send</button>
  <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
    <svg aria-hidden="true" viewBox="0 0 20 20">
      <path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
    </svg>
    <ul class="gui-popup"></ul>
  </span>
</div>

Basit pop-up yerleşimi için .gui-popup, genişleten düğmenin alt öğesidir. Bu stratejinin tek dezavantajı, pop-up'ın görsel olarak gösterilmesini engelleyeceği için .gui-split-button kapsayıcısının overflow: hidden kullanamamasıdır.

<li><button> içerikleriyle dolu bir <ul>, ekran okuyuculara "düğme listesi" olarak kendini tanıtır. Bu, sunulan arayüzdür.

<div class="gui-split-button">
  <button>Send</button>
  <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
    <svg aria-hidden="true" viewBox="0 0 20 20">
      <path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
    </svg>
    <ul class="gui-popup">
      <li>
        <button>Schedule for later</button>
      </li>
      <li>
        <button>Delete</button>
      </li>
      <li>
        <button>Save draft</button>
      </li>
    </ul>
  </span>
</div>

Renklerle oynamak ve daha şık bir görünüm elde etmek için https://heroicons.com adresinden ikincil düğmelere simgeler ekledim. Simgeler hem birincil hem de ikincil düğmeler için isteğe bağlıdır.

<div class="gui-split-button">
  <button>Send</button>
  <span class="gui-popup-button" aria-haspopup="true" aria-expanded="false" title="Open for more actions">
    <svg aria-hidden="true" viewBox="0 0 20 20">
      <path d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" />
    </svg>
    <ul class="gui-popup">
      <li><button>
        <svg aria-hidden="true" viewBox="0 0 24 24">
          <path d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
        </svg>
        Schedule for later
      </button></li>
      <li><button>
        <svg aria-hidden="true" viewBox="0 0 24 24">
          <path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
        </svg>
        Delete
      </button></li>
      <li><button>
        <svg aria-hidden="true" viewBox="0 0 24 24">
          <path d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z" />
        </svg>
        Save draft
      </button></li>
    </ul>
  </span>
</div>

Stiller

HTML ve içerik eklendikten sonra stiller renk ve düzen sağlamaya hazırdır.

Bölünmüş düğme kapsayıcısına stil uygulama

inline-flex görüntüleme türü, diğer bölünmüş düğmelere, işlemlere veya öğelere satır içi olarak sığması gerektiğinden bu sarmalayıcı bileşen için iyi bir seçimdir.

.gui-split-button {
  display: inline-flex;
  border-radius: var(--radius);
  background: var(--theme);
  color: var(--ontheme);
  fill: var(--ontheme);

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

Bölme düğmesi.

<button> stili

Düğmeler, ne kadar kod gerektiğini gizleme konusunda çok başarılıdır. Tarayıcı varsayılan stillerini geri almanız veya değiştirmeniz gerekebilir ancak bazı devralma işlemlerini uygulamanız, etkileşim durumları eklemeniz ve çeşitli kullanıcı tercihlerine ve giriş türlerine uyum sağlamanız da gerekir. Düğme stilleri hızla artar.

Bu düğmeler, üst öğeyle aynı arka planı paylaştıkları için normal düğmelerden farklıdır. Genellikle düğmelerin arka planı ve metin rengi kendileri için belirlenir. Ancak bu uygulamalar arka planı paylaşır ve yalnızca kendi arka planlarını etkileşime uygular.

.gui-split-button button {
  cursor: pointer;
  appearance: none;
  background: none;
  border: none;

  display: inline-flex;
  align-items: center;
  gap: 1ch;
  white-space: nowrap;

  font-family: inherit;
  font-size: inherit;
  font-weight: 500;

  padding-block: 1.25ch;
  padding-inline: 2.5ch;

  color: var(--ontheme);
  outline-color: var(--theme);
  outline-offset: -5px;
}

Birkaç CSS sözde sınıfı kullanarak ve durumla eşleşen özel özellikleri kullanarak etkileşim durumları ekleyin:

.gui-split-button button {
  

  &:is(:hover, :focus-visible) {
    background: var(--theme-hover);
    color: var(--ontheme);

    & > svg {
      stroke: currentColor;
      fill: none;
    }
  }

  &:active {
    background: var(--theme-active);
  }
}

Birincil düğmenin tasarım etkisini tamamlaması için birkaç özel stile ihtiyacı vardır:

.gui-split-button > button {
  border-end-start-radius: var(--radius);
  border-start-start-radius: var(--radius);

  & > svg {
    fill: none;
    stroke: var(--ontheme);
  }
}

Son olarak, açık tema düğmesi ve simgesine gölge eklenir:

.gui-split-button {
  @media (--light) {
    & > button,
    & button:is(:focus-visible, :hover) {
      text-shadow: 0 1px 0 var(--theme-active);
    }
    & > .gui-popup-button > svg,
    & button:is(:focus-visible, :hover) > svg {
      filter: drop-shadow(0 1px 0 var(--theme-active));
    }
  }
}

Mükemmel bir düğmede mikro etkileşimlere ve küçük ayrıntılara dikkat edilir.

:focus-visible ile ilgili not

Düğme stillerinde :focus yerine :focus-visible kullanıldığına dikkat edin. :focus erişilebilir bir kullanıcı arayüzü oluşturmak için önemli bir dokunuştur ancak bir dezavantajı vardır: Kullanıcının bunu görmesi gerekip gerekmediği konusunda akıllı değildir, her odak için geçerli olur.

Aşağıdaki videoda, :focus-visible'ün akıllı bir alternatif olduğunu göstermek için bu mikro etkileşimin ayrıntılı bir dökümü sunulmaktadır.

Pop-up düğmesine stil uygulama

Bir simgeyi ortalamayı ve pop-up düğme listesini sabitlemeyi sağlayan 4ch bir esnek kutu. Birincil düğme gibi, fareyle üzerine gelinene veya etkileşime geçilene kadar şeffaftır ve alanı dolduracak şekilde uzar.

Pop-up&#39;ı tetiklemek için kullanılan bölünmüş düğmenin ok kısmı.

.gui-popup-button {
  inline-size: 4ch;
  cursor: pointer;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-inline-start: var(--border);
  border-start-end-radius: var(--radius);
  border-end-end-radius: var(--radius);
}

CSS iç içe yerleştirme ve :is() işlevsel seçici ile fareyle üzerine gelme, odak ve etkin durumları katmanlara ayırın:

.gui-popup-button {
  

  &:is(:hover,:focus-within) {
    background: var(--theme-hover);
  }

  /* fixes iOS trying to be helpful */
  &:focus {
    outline: none;
  }

  &:active {
    background: var(--theme-active);
  }
}

Bu stiller, pop-up'ı göstermek ve gizlemek için birincil kancadır. .gui-popup-button, alt öğelerinden birinde focus içeriyorsa simge ve pop-up'ta opacity, konum ve pointer-events ayarlarını yapın.

.gui-popup-button {
  

  &:focus-within {
    & > svg {
      transition-duration: var(--in-speed);
      transform: rotateZ(.5turn);
    }
    & > .gui-popup {
      transition-duration: var(--in-speed);
      opacity: 1;
      transform: translateY(0);
      pointer-events: auto;
    }
  }
}

Giriş ve çıkış stilleri tamamlandığında son adım, kullanıcının hareket tercihine bağlı olarak dönüşümleri koşullu olarak aktarmaktır:

.gui-popup-button {
  

  @media (--motionOK) {
    & > svg {
      transition: transform var(--out-speed) ease;
    }
    & > .gui-popup {
      transform: translateY(5px);

      transition:
        opacity var(--out-speed) ease,
        transform var(--out-speed) ease;
    }
  }
}

Kodu dikkatle inceleyenler, azaltılmış hareketi tercih eden kullanıcılar için opasitenin geçişe devam ettiğini fark edecektir.

Pop-up'a stil uygulama

.gui-popup öğesi, özel özellikler ve göreli birimler kullanılarak oluşturulan, temel düğmeden biraz daha küçük, etkileşimli olarak temel düğmeyle eşleşen ve renk kullanımıyla markaya uygun bir yüzen kart düğmesi listesidir. Simgelerin daha az kontrasta sahip olduğuna, daha ince olduğuna ve gölgede markanın mavi renginin olduğuna dikkat edin. Düğmelerde olduğu gibi, güçlü kullanıcı arayüzü ve kullanıcı deneyimi de bu küçük ayrıntıların bir araya gelmesinin sonucudur.

Yüzen kart öğesi.

.gui-popup {
  --shadow: 220 70% 15%;
  --shadow-strength: 1%;

  opacity: 0;
  pointer-events: none;

  position: absolute;
  bottom: 80%;
  left: -1.5ch;

  list-style-type: none;
  background: var(--popupbg);
  color: var(--theme-text);
  padding-inline: 0;
  padding-block: .5ch;
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  font-size: .9em;
  transition: opacity var(--out-speed) ease;

  box-shadow:
    0 -2px 5px 0 hsl(var(--shadow) / calc(var(--shadow-strength) + 5%)),
    0 1px 1px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 10%)),
    0 2px 2px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 12%)),
    0 5px 5px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 13%)),
    0 9px 9px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 14%)),
    0 16px 16px -2px hsl(var(--shadow) / calc(var(--shadow-strength) + 20%))
  ;
}

Simge ve düğmelere, koyu ve açık temalı kartlarda güzel bir stil oluşturmak için marka renkleri verilir:

Ödeme, Hızlı Ödeme ve Daha sonra kaydet bağlantıları ve simgeleri.

.gui-popup {
  

  & svg {
    fill: var(--popupbg);
    stroke: var(--theme);

    @media (prefers-color-scheme: dark) {
      stroke: var(--theme-border);
    }
  }

  & button {
    color: var(--theme-text);
    width: 100%;
  }
}

Koyu tema pop-up'ında metin ve simge gölgesi eklemeleri ile birlikte biraz daha yoğun bir kutu gölgesi bulunur:

Koyu temadaki pop-up.

.gui-popup {
  

  @media (--dark) {
    --shadow-strength: 5%;
    --shadow: 220 3% 2%;

    & button:not(:focus-visible, :hover) {
      text-shadow: 0 1px 0 var(--ontheme);
    }

    & button:not(:focus-visible, :hover) > svg {
      filter: drop-shadow(0 1px 0 var(--ontheme));
    }
  }
}

Genel <svg> simge stilleri

Tüm simgeler, inline-size olarak ch birimi kullanılarak içinde kullanıldıkları font-size düğmesine göre göreceli olarak boyutlandırılır. Her birine, simgelerin ana hatlarının yumuşak ve pürüzsüz görünmesine yardımcı olacak bazı stiller de eklenir.

.gui-split-button svg {
  inline-size: 2ch;
  box-sizing: content-box;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 2px;
}

Sağdan sola düzen

Tüm karmaşık işlemleri mantıksal özellikler yapar. Kullanılan mantıksal özelliklerin listesi aşağıda verilmiştir: - display: inline-flex satır içi bir esnek öğe oluşturur. - padding kısayolu yerine padding-block ve padding-inline çiftini kullanarak mantıksal taraflara dolgu eklemenin avantajlarından yararlanın. - border-end-start-radius ve arkadaşları, köşeleri doküman yönüne göre yuvarlar. - width yerine inline-size, boyutun fiziksel boyutlara bağlı olmamasını sağlar. - border-inline-start, başlangıca bir kenarlık ekler. Bu kenarlık, komut dosyasının yönüne bağlı olarak sağda veya solda olabilir.

JavaScript

Aşağıdaki JavaScript'lerin neredeyse tamamı erişilebilirliği artırmak içindir. Görevleri biraz daha kolaylaştırmak için yardımcı kitaplıklarımdan iki tanesi kullanılmıştır. BlingBlingJS, kısa DOM sorguları ve kolay etkinlik dinleyici kurulumu için kullanılır. roving-ux ise pop-up'ta erişilebilir klavye ve gamepad etkileşimlerini kolaylaştırmaya yardımcı olur.

import $ from 'blingblingjs'
import {rovingIndex} from 'roving-ux'

const splitButtons = $('.gui-split-button')
const popupButtons = $('.gui-popup-button')

Yukarıdaki kitaplıklar içe aktarıldıktan ve öğeler seçilip değişkenlere kaydedildikten sonra, deneyimi yükseltmek için birkaç işlev daha uygulamanız gerekir.

Gezici dizin

Bir klavye veya ekran okuyucu .gui-popup-button'e odaklandığında, odağı .gui-popup'daki ilk (veya en son odaklanan) düğmeye yönlendirmek isteriz. Kitaplık, element ve target parametreleriyle bunu yapmamıza yardımcı olur.

popupButtons.forEach(element =>
  rovingIndex({
    element,
    target: 'button',
  }))

Öğe artık odağı hedef <button> alt öğelerine aktarır ve seçeneklere göz atmak için standart ok tuşlarıyla gezinmeyi etkinleştirir.

aria-expanded'ü açma/kapatma

Bir pop-up'ın gösterilip gizlendiği görsel olarak anlaşılabilir olsa da ekran okuyucunun görsel ipuçlarından daha fazlasına ihtiyacı vardır. Burada JavaScript, ekran okuyucuya uygun bir özelliği etkinleştirerek CSS destekli :focus-within etkileşimini tamamlamak için kullanılır.

popupButtons.on('focusin', e => {
  e.currentTarget.setAttribute('aria-expanded', true)
})

popupButtons.on('focusout', e => {
  e.currentTarget.setAttribute('aria-expanded', false)
})

Escape anahtarını etkinleştirme

Kullanıcının odak noktası kasıtlı olarak bir tuzağa yönlendirilmiştir. Bu nedenle, kullanıcının bu tuzaktan çıkabileceği bir yol sunmamız gerekir. En yaygın yöntem, Escape anahtarının kullanılmasına izin vermektir. Bunu yapmak için pop-up düğmesinde tuş basma işlemlerini izleyin. Çünkü alt öğelerdeki tüm klavye etkinlikleri bu üst öğeye iletilir.

popupButtons.on('keyup', e => {
  if (e.code === 'Escape')
    e.target.blur()
})

Pop-up düğmesi Escape tuşuna basıldığını algılarsa blur() ile odak noktasını kendisinden kaldırır.

Bölünmüş düğme tıklamaları

Son olarak, kullanıcı düğmeleri tıkladığında, düğmelere dokunduğunda veya klavyeyle etkileşime geçtiğinde uygulamanın uygun işlemi gerçekleştirmesi gerekir. Burada yine etkinlik kabartması kullanılır ancak bu kez .gui-split-button kapsayıcısında, alt pop-up'tan veya birincil işlemden gelen düğme tıklamalarını yakalamak için kullanılır.

splitButtons.on('click', event => {
  if (event.target.nodeName !== 'BUTTON') return
  console.info(event.target.innerText)
})

Sonuç

Bunu nasıl yaptığımı öğrendiğinize göre, siz ne yapardınız? 🙂

Yaklaşımlarımızı çeşitlendirelim ve web'de uygulama geliştirmenin tüm yollarını öğrenelim. Bir demo oluşturun, bağlantılarını bana tweetleyin. Ardından, aşağıdaki topluluk remiksleri bölümüne ekleyeceğim.

Topluluk remiksleri