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

Erişilebilir bir bölme düğmesi bileşeninin nasıl oluşturulacağına dair temel bir genel bakış.

Bu yayında, bölünmüş düğme oluşturmanın bir yolu üzerine düşünmek istiyorum . Demoyu deneyin.

Tanıtım

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

Genel bakış

Bölünmüş düğmeler, birincil düğmeyi ve ek düğmelerin listesini gizleyen düğmelerdir. İkincil, daha seyrek kullanılan işlemleri ihtiyaç duyulana kadar iç içe yerleştirirken sık kullanılan bir işlemi ortaya çıkarmak için kullanışlıdırlar. Bölme düğmesi, yoğun bir tasarımın minimum düzeyde hissetmesine yardımcı olabilir. Gelişmiş bir bölme düğmesi, son kullanıcı işlemini hatırlayabilir ve bunu birincil konuma yükseltebilir.

Ortak bir bölme düğmesi e-posta uygulamanızda bulunabilir. Birincil işlem göndermedir ancak bunu daha sonra gönderebilir veya taslak olarak kaydedebilirsiniz:

Bir e-posta uygulamasında görülen örnek bölme düğmesi.

Kullanıcının etrafa bakması gerekmediği için paylaşılan işlem alanı güzel. Önemli e-posta işlemlerinin, bölün düğmesinde bulunduğunu bilirler.

Parçalar

Düğmelerin genel düzenleme ve son kullanıcı deneyiminden bahsetmeden önce, bölme düğmesinin temel parçalarına yakından bakalım. VisBug'un erişilebilirlik inceleme aracı burada bileşenin makro görünümünü göstermeye yardımcı olarak HTML'nin yönlerini, her bir ana bölümün stilini ve erişilebilirliğini gösterir.

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

Üst düzey bölme düğmesi kapsayıcısı

En üst düzey bileşen, birincil işlemi ve .gui-popup-button öğelerini içeren gui-split-button sınıfına sahip bir satır içi flexbox'tır.

Gui-split-button sınıfı, bu sınıfta kullanılan CSS özelliklerini inceler ve gösterir.

Birincil işlem düğmesi

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

Düğme öğesine ilişkin CSS kurallarını gösteren denetleyici.

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

"Pop-up düğmesi" destek öğesi, ikincil düğmelerin listesini etkinleştirmek ve bunlara atıfta bulunmak içindir. Bunun bir <button> olmadığına ve odaklanılabilir olmadığına dikkat edin. Bununla birlikte, pop-up'ı sunmak için .gui-popup için kullanılan konumlandırma çapası ve :focus-within için ana makinedir.

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

Pop-up kart

Bu, mutlak konumlandırılmış ve düğme listesini anlamsal olarak sarmalayan sabit .gui-popup-button öğesine bağlı bir kayan karttır.

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

İkincil işlemler

Birincil işlem düğmesinden biraz daha küçük bir yazı boyutuna sahip olan odaklanılabilir bir <button>, bir simge ve birincil düğme için tamamlayıcı bir stil içerir.

Düğme öğesine ilişkin CSS kurallarını gösteren denetleyici.

Özel mülkler

Aşağıdaki değişkenler, renk harmonisi oluşturmaya ve bileşen genelinde kullanılan değerleri değiştirmek için merkezi bir yer oluşturmaya 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

Markup

Öğe, özel bir sınıf adıyla bir <div> ile 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 özelliklerine (aria-haspopup ve aria-expanded) dikkat edin. Bu işaretler, ekran okuyucuların bölünmüş düğme deneyiminin özelliği ve durumundan haberdar olması açısından kritik öneme sahiptir. title özelliği herkes için yararlıdır.

<svg> simgesi ve .gui-popup kapsayıcı öğesini 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, onu genişleten düğmenin bir alt öğesidir. Bu stratejinin tek sorunu, .gui-split-button kapsayıcısının overflow: hidden'yi kullanamamasıdır. Çünkü bu işlem, pop-up'ın görsel olarak mevcut olmasını engeller.

<li><button> içeriğiyle dolu bir <ul>, ekran okuyuculara kendisini bir "düğme listesi" olarak duyurur. Bu, tam olarak 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>

Şıklık ve renklerle eğlenmek için, https://heroicons.com adresindeki 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 yerleştirildiğinde stiller, renk ve düzen sağlamak için hazırdır.

Bölünmüş düğme kapsayıcısının stilini belirleme

inline-flex görüntüleme türü, diğer bölme düğmeleri, işlemler veya öğelerle satır içi olarak sığması gerektiğinden bu sarmalama bileşeni için oldukça uygundur.

.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öl düğmesi.

<button> stili

Düğmeler, ne kadar kod gerektiğini gizleme konusunda son derece başarılıdır. Tarayıcı varsayılan stillerini geri almanız veya değiştirmeniz gerekebilir ancak bazı devralma işlemlerini zorunlu kılmanız, etkileşim durumları eklemeniz ve çeşitli kullanıcı tercihlerine ve giriş türlerine uyarlamanız gerekir. Düğme stilleri hızlı bir şekilde eklenir.

Bu düğmeler, bir üst öğeyle bir arka planı paylaştıklarından normal düğmelerden farklıdır. Genellikle bir düğmenin kendi arka planı ve metin rengi vardır. Ancak bunlar da bunu paylaşır ve etkileşime kendi geçmişlerini 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ı içeren etkileşim durumları ekleyin ve durum için eşleşen özel özelliklerin kullanımını kullanın:

.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 efektini tamamlamak için birkaç özel stil kullanması gerekir:

.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 biraz renk katar:

.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));
    }
  }
}

Harika bir düğme, mikro etkileşimlere ve küçük ayrıntılara dikkat etti.

:focus-visible hakkında bir not

Düğme stillerinin :focus yerine :focus-visible özelliğini nasıl kullandığına dikkat edin. :focus, erişilebilir bir kullanıcı arayüzü oluşturmak için çok önemli bir dokunuş olsa da bir dezavantajı var: Kullanıcının bunu görmesi gerekip gerekmediği konusunda akıllı değil, her türlü odak noktasına başvurulacak.

Aşağıdaki video, :focus-visible'in nasıl akıllı bir alternatif olduğunu göstermek için bu mikro etkileşimi ayrıntılı bir şekilde açıklamaya çalışmaktadır.

Pop-up düğmesinin stil özelliklerini ayarlama

Bir simgeyi ortalamak ve bir pop-up düğme listesini sabitlemek için 4ch flexbox. Birincil düğme gibi, herhangi bir şekilde üzerine gelinceye veya etkileşimde bulunulana ve dolduracak şekilde uzatılana kadar şeffaftır.

Pop-up&#39;ı tetiklemek için kullanılan bölme düğmesinin 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 Nesting ve :is() işlev 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 kullanılan birincil öğelerdir. .gui-popup-button öğesinin alt öğelerinden birinde focus varsa simge ve pop-up'ta opacity, konum ve pointer-events değerini ayarlayı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ıktan sonra son parça, kullanıcının hareket tercihine bağlı olarak koşullu geçiş dönüşümleridir:

.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 dikkatli bir şekilde incelerseniz daha az hareket etmeyi tercih eden kullanıcılar için opaklığın hâlâ geçişte olduğunu fark edebilirsiniz.

Pop-up'ın stilini belirleme

.gui-popup öğesi, biraz daha küçük olacak, birincil düğmeyle etkileşimli olarak eşleştirilecek ve marka üzerinde renk kullanımıyla eşleştirilecek özel özelliklerin ve göreli birimlerin kullanıldığı bir kayan kart düğmesi listesidir. Simgelerin kontrastı daha az, daha ince ve gölgesinde marka mavisi var. Düğmelerde olduğu gibi, bu küçük ayrıntıların bir arada birikmesi güçlü kullanıcı arayüzü ve kullanıcı deneyimiyle ortaya çıkar.

Kayan 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%))
  ;
}

Koyu ve açık renkli her kartta şık bir stile sahip olmaları için simgelere ve düğmelere marka renkleri verilir:

Ödeme, Hızlı Ödeme ve Daha sonrası için kaydetme için bağlantılar ve simgeler.

.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 eklemelerinin yanı sıra 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 stili

Tüm simgeler, inline-size olarak ch birimi kullanılarak, kullanıldıkları düğmeye font-size göre göreli olarak boyutlandırılır. Simgelerin yumuşak ve pürüzsüz olmasına yardımcı olmak için her birine de bazı stiller verilir.

.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

Mantıksal özellikler tüm karmaşık işleri yapar. Kullanılan mantıksal özelliklerin listesi aşağıda verilmiştir: - display: inline-flex, bir satır içi flex öğesi oluşturur. - padding-block ve padding-inline değerlerini bir çift olarak, padding kısaltması yerine, mantıksal kenarları doldurmanın avantajlarından yararlanın. - border-end-start-radius ve arkadaşlar köşeleri belge yönüne göre yuvarlar. - width yerine inline-size değeri, boyutun fiziksel boyutlara bağlı olmamasını sağlar. - border-inline-start, başa 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 geliştirmektir. Yardımcı kitaplıklarımdan ikisi görevleri biraz kolaylaştırmak için kullanılıyor. BlingBlingJS, kısa ve öz DOM sorguları ve kolay etkinlik işleyici kurulumu için kullanılır. roving-ux ise pop-up için erişilebilir klavye ve oyun kumandası 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ın içe aktarılması ve öğelerin seçilip değişkenlere kaydedilmesi sayesinde, deneyimi yeni sürüme geçirme işleminin tamamlanmasına birkaç işlev kaldı.

Fitness endeksi

Klavye veya ekran okuyucu .gui-popup-button öğesine odaklandığında, odağı .gui-popup içindeki ilk (veya en son odaklanılan) 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 geçirir ve seçeneklere göz atmak için standart ok tuşuyla gezinmeyi etkinleştirir.

aria-expanded değiştiriliyor

Bir pop-up'ın gizlenip gizlendiği görsel olarak belli olsa da ekran okuyucuların görsel işaretlerden daha fazlasına ihtiyacı vardır. JavaScript burada bir ekran okuyucu uygun özelliği açıp kapatarak CSS odaklı :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ı etkinleştiriliyor

Kullanıcının odağı kasıtlı olarak bir tuzağa düşmüştür. Bu yüzden, çıkmak için bir yol sağlamamız gerekir. En yaygın yol, Escape anahtarının kullanılmasına izin vermektir. Bunu yapmak için, alt öğelerdeki tüm klavye etkinlikleri bu üst öğede baloncuk olarak gösterileceğinden, pop-up düğmesindeki tuşa basmaları takip edin.

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

Pop-up düğmesi herhangi bir Escape tuşuna basıldığını görürse odağı blur() ile kendisinden kaldırır.

Böl düğmesi tıklamaları

Son olarak, kullanıcı düğmeleri tıklarsa, dokunursa veya klavyeyle etkileşimde bulunursa uygulamanın uygun işlemi gerçekleştirmesi gerekir. Etkinlik baloncuğu burada tekrar kullanılır, ancak bu kez .gui-split-button kapsayıcısında, bir 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ç

Nasıl yaptığımı artık bildiğine göre siz de nasıl yapardınız? 🙂

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

Topluluk remiksleri