Renk uyarlamalı ve erişilebilir bir ipucu özel öğesi oluşturma hakkında temel bilgiler.
Bu yayında, renge uyarlanabilir ve erişilebilir bir <tool-tip>
özel öğenin nasıl oluşturulacağı konusundaki düşüncelerimi paylaşmak istiyorum. Demoyu deneyin ve kaynağı görüntüleyin.
Video tercih ediyorsanız bu yayının YouTube versiyonunu aşağıda bulabilirsiniz:
Genel Bakış
İpucu, kullanıcı arayüzlerine ek bilgiler içeren, modsuz, engellemeyen ve etkileşimli olmayan bir yer paylaşımıdır. Varsayılan olarak gizlidir ve ilişkili bir öğenin üzerine gelindiğinde veya öğeye odaklanıldığında görünür hale gelir. İpucu doğrudan seçilemez veya ipucuyla doğrudan etkileşimde bulunulamaz. İpuçları, etiketlerin veya diğer değerli bilgilerin yerine geçmez. Kullanıcılar, görevlerini ipucu olmadan da tamamen tamamlayabilmelidir.

Yapılmaması gerekenler: Etiketler yerine ipuçlarını kullanma
Toggletip ve İpucu
Birçok bileşende olduğu gibi, ipucunun ne olduğuna dair farklı açıklamalar vardır. Örneğin, MDN, WAI ARIA, Sarah Higley ve Inclusive Components'ta bu açıklamaları bulabilirsiniz. İpuçları ve açma/kapatma ipuçları arasındaki ayrımı beğeniyorum. İpucu, etkileşimli olmayan ek bilgiler içermelidir. Açma/kapatma ipucu ise etkileşim ve önemli bilgiler içerebilir. Bu ayrımın temel nedeni erişilebilirliktir. Kullanıcıların pop-up'a nasıl gideceği ve içindeki bilgilere ve düğmelere nasıl erişeceği sorusu önemlidir. Toggletips hızla karmaşık hale gelir.
Designcember sitesindeki bir toggletip'in videosunu aşağıda bulabilirsiniz. Bu video, kullanıcının açık olarak sabitleyebileceği, keşfedebileceği ve ardından hafifçe kapatarak veya Escape tuşuyla kapatabileceği, etkileşimli bir yer paylaşımını gösterir:
Bu GUI Challenge'da, neredeyse her şeyi CSS ile yapmaya çalışan bir ipucu yolu izlendi. İşte bu ipucunu oluşturma yöntemi.
Brüt kar
Özel bir öğe kullanmayı seçtim <tool-tip>
. İçerik üreticiler, istemezlerse özel öğeleri web bileşenlerine dönüştürmek zorunda değildir. Tarayıcı, <foo-bar>
öğesini <div>
gibi değerlendirir. Özel öğeyi, daha az özgüllüğe sahip bir sınıf adı gibi düşünebilirsiniz. JavaScript kullanılmaz.
<tool-tip>A tooltip</tool-tip>
Bu, içinde metin bulunan bir div'e benzer. [role="tooltip"]
ekleyerek uygun ekran okuyucuların erişilebilirlik ağacına bağlanabiliriz.
<tool-tip role="tooltip">A tooltip</tool-tip>
Artık ekran okuyucular tarafından ipucu olarak tanınıyor. Aşağıdaki örnekte, ilk bağlantı öğesinin ağacında tanınan bir ipucu öğesi olduğunu, ikincisinde ise olmadığını görüyor musunuz? İkinci kullanıcıda bu rol yok. Stiller bölümünde bu ağaç görünümünü iyileştireceğiz.
Ardından, ipucunun odaklanılabilir olmaması gerekir. Ekran okuyucu, ipucu rolünü anlamazsa kullanıcıların içeriği okumak için <tool-tip>
öğesine odaklanmasına izin verir. Kullanıcı deneyimi için bu gerekli değildir. Ekran okuyucular, içeriği üst öğeye ekler. Bu nedenle, içeriğin erişilebilir olması için odaklanılması gerekmez. Burada, hiçbir kullanıcının sekme akışında bu ipucu içeriğini yanlışlıkla bulmamasını sağlamak için inert
kullanabiliriz:
<tool-tip inert role="tooltip">A tooltip</tool-tip>
Ardından, ipucunun konumunu belirtmek için arayüz olarak özellikleri kullanmayı seçtim. Varsayılan olarak tüm <tool-tip>
'ler "üst" konumunu alır ancak tip-position
eklenerek konum öğe bazında özelleştirilebilir:
<tool-tip role="tooltip" tip-position="right ">A tooltip</tool-tip>
Bu gibi durumlarda sınıflar yerine özellikleri kullanmayı tercih ederim. Böylece <tool-tip>
öğesine aynı anda birden fazla konum atanmaz.
Yalnızca bir tane olabilir veya hiç olmayabilir.
Son olarak, <tool-tip>
öğelerini, ipucu sağlamak istediğiniz öğenin içine yerleştirin. Burada, bir resim ve <tool-tip>
öğesini <picture>
öğesinin içine yerleştirerek alt
metnini görme engelli olmayan kullanıcılarla paylaşıyorum:
<picture>
<img alt="The GUI Challenges skull logo" width="100" src="...">
<tool-tip role="tooltip" tip-position="bottom">
The <b>GUI Challenges</b> skull logo
</tool-tip>
</picture>
Burada bir <tool-tip>
öğesini <abbr>
öğesinin içine yerleştiriyorum:
<p>
The <abbr>HTML <tool-tip role="tooltip" tip-position="top">Hyper Text Markup Language</tool-tip></abbr> abbr element.
</p>
Erişilebilirlik
Togglesip yerine ipucu oluşturmayı seçtiğim için bu bölüm çok daha basit. Öncelikle, istediğimiz kullanıcı deneyimini özetleyelim:
- Sınırlı alanlarda veya karmaşık arayüzlerde ek mesajları gizleyin.
- Kullanıcı bir öğeyle etkileşim kurmak için fareyle üzerine geldiğinde, odaklandığında veya dokunduğunda mesajı gösterin.
- Fareyle üzerine gelme, odaklanma veya dokunma işlemi sona erdiğinde mesajı tekrar gizleyin.
- Son olarak, kullanıcı hareket azaltma tercihini belirttiyse hareketlerin azaltıldığından emin olun.
Hedefimiz, isteğe bağlı ek mesajlaşma. Görme engelli olmayan bir fare veya klavye kullanıcısı, mesajı gözleriyle okumak için fareyle üzerine gelebilir. Görme engelli bir ekran okuyucu kullanıcısı, mesajı ortaya çıkarmak için odaklanabilir ve mesajı aracı aracılığıyla sesli olarak alabilir.

Önceki bölümde erişilebilirlik ağacı, ipucu rolü ve inert özelliklerini ele aldık. Geriye kalan, bunları test etmek ve kullanıcı deneyiminin, ipucu mesajını kullanıcıya uygun şekilde gösterdiğini doğrulamaktır. Test sırasında, sesli mesajın hangi bölümünün ipucu olduğu net değil. Erişilebilirlik ağacında hata ayıklama sırasında da görülebilir. "En üst" bağlantı metni, tereddüt etmeden "Bak, ipuçları!" ile birlikte çalıştırılır. Ekran okuyucu, metni bölmüyor veya ipucu içeriği olarak tanımlamıyor.
<tool-tip>
öğesine yalnızca ekran okuyucu için bir sözde öğe ekleyerek görme engelli kullanıcılar için kendi istem metnimizi ekleyebiliriz.
&::before {
content: "; Has tooltip: ";
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
Bağlantı metninden sonra noktalı virgül ve "Has tooltip: " (İçerik ipucu var:) isteminin eklendiği güncellenmiş erişilebilirlik ağacını aşağıda görebilirsiniz.
Artık ekran okuyucu kullanıcısı bağlantıya odaklandığında "üst" ifadesi okunuyor, kısa bir duraklamanın ardından "ipucu var: bak, ipuçları" ifadesi duyuruluyor. Bu, ekran okuyucu kullanıcısına birkaç güzel kullanıcı deneyimi ipucu verir. Bu duraklama, bağlantı metni ile ipucu arasında güzel bir ayrım sağlar. Ayrıca, "has tooltip" duyurulduğunda ekran okuyucu kullanan bir kullanıcı, daha önce duyduysa bu duyuruyu kolayca iptal edebilir. Ek mesajı gördüğünüz gibi, bu işlem fareyle hızlıca üzerine gelip çekmeye çok benziyor. Bu, güzel bir kullanıcı deneyimi eşitliği gibi geldi.
Stiller
<tool-tip>
öğesi, temsil ettiği öğenin alt öğesi olacaktır. Bu nedenle, önce yer paylaşımı efektinin temel özellikleriyle başlayalım. position absolute
ile doküman akışından çıkarın:
tool-tip {
position: absolute;
z-index: 1;
}
Üst öğe bir yığın bağlamı değilse ipucu, istediğimiz gibi olmayan en yakın yığın bağlamına yerleştirilir. Blokta yardımcı olabilecek yeni bir seçici var, :has()
:
:has(> tool-tip) {
position: relative;
}
Tarayıcı desteği konusunda çok fazla endişelenmeyin. Öncelikle bu ipuçlarının ek bilgiler olduğunu unutmayın. Çalışmıyorlarsa sorun yoktur. İkinci olarak, JavaScript bölümünde :has()
desteği olmayan tarayıcılar için ihtiyacımız olan işlevleri çoklu doldurmak üzere bir komut dosyası dağıtacağız.
Ardından, ipuçlarını etkileşimli olmayan hale getirelim. Böylece, işaretçi etkinliklerini üst öğelerinden çalmazlar:
tool-tip {
…
pointer-events: none;
user-select: none;
}
Ardından, ipucunu çapraz geçişle geçirebilmemiz için ipucunu opaklık ile gizleyin:
tool-tip {
opacity: 0;
}
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
}
:is()
ve :has()
, bu noktada işin büyük kısmını üstlenerek üst öğeleri içeren tool-tip
öğesinin, alt ipucunun görünürlüğünü değiştirmek için kullanıcı etkileşiminin farkında olmasını sağlar. Fare kullanıcıları imleci üzerine getirebilir, klavye ve ekran okuyucu kullanıcıları odaklanabilir, dokunmatik ekran kullanıcıları ise dokunabilir.
Görme engelli kullanıcılar için yer paylaşımını gösterme ve gizleme işlevi çalıştığına göre, temalandırma, konumlandırma ve balona üçgen şekli ekleme için bazı stiller eklemenin zamanı geldi. Aşağıdaki stiller, özel özellikler kullanmaya başlar. Bu stiller, şu ana kadar yaptığımız çalışmalara dayanır ancak gölgeler, tipografi ve renkler de ekleyerek kayan bir ipucu gibi görünür:
tool-tip {
--_p-inline: 1.5ch;
--_p-block: .75ch;
--_triangle-size: 7px;
--_bg: hsl(0 0% 20%);
--_shadow-alpha: 50%;
--_bottom-tip: conic-gradient(from -30deg at bottom, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) bottom / 100% 50% no-repeat;
--_top-tip: conic-gradient(from 150deg at top, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) top / 100% 50% no-repeat;
--_right-tip: conic-gradient(from -120deg at right, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) right / 50% 100% no-repeat;
--_left-tip: conic-gradient(from 60deg at left, rgba(0,0,0,0), #000 1deg 60deg, rgba(0,0,0,0) 61deg) left / 50% 100% no-repeat;
pointer-events: none;
user-select: none;
opacity: 0;
transform: translateX(var(--_x, 0)) translateY(var(--_y, 0));
transition: opacity .2s ease, transform .2s ease;
position: absolute;
z-index: 1;
inline-size: max-content;
max-inline-size: 25ch;
text-align: start;
font-size: 1rem;
font-weight: normal;
line-height: normal;
line-height: initial;
padding: var(--_p-block) var(--_p-inline);
margin: 0;
border-radius: 5px;
background: var(--_bg);
color: CanvasText;
will-change: filter;
filter:
drop-shadow(0 3px 3px hsl(0 0% 0% / var(--_shadow-alpha)))
drop-shadow(0 12px 12px hsl(0 0% 0% / var(--_shadow-alpha)));
}
/* create a stacking context for elements with > tool-tips */
:has(> tool-tip) {
position: relative;
}
/* when those parent elements have focus, hover, etc */
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
transition-delay: 200ms;
}
/* prepend some prose for screen readers only */
tool-tip::before {
content: "; Has tooltip: ";
clip: rect(1px, 1px, 1px, 1px);
clip-path: inset(50%);
height: 1px;
width: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
}
/* tooltip shape is a pseudo element so we can cast a shadow */
tool-tip::after {
content: "";
background: var(--_bg);
position: absolute;
z-index: -1;
inset: 0;
mask: var(--_tip);
}
/* top tooltip styles */
tool-tip:is(
[tip-position="top"],
[tip-position="block-start"],
:not([tip-position]),
[tip-position="bottom"],
[tip-position="block-end"]
) {
text-align: center;
}
Tema ayarlamaları
Metin rengi, CanvasText
sistem anahtar kelimesi aracılığıyla sayfadan devralındığı için ipucu yalnızca birkaç rengi yönetir. Ayrıca, değerleri depolamak için özel özellikler oluşturduğumuzdan yalnızca bu özel özellikleri güncelleyebilir ve geri kalanını temanın yönetmesine izin verebiliriz:
@media (prefers-color-scheme: light) {
tool-tip {
--_bg: white;
--_shadow-alpha: 15%;
}
}
Açık temada, arka planı beyaza uyarlıyor ve opaklıklarını ayarlayarak gölgeleri çok daha az belirgin hale getiriyoruz.
Sağdan sola
Sağdan sola okuma modlarını desteklemek için özel bir özellik, belge yönünün değerini sırasıyla -1 veya 1 olarak saklar.
tool-tip {
--isRTL: -1;
}
tool-tip:dir(rtl) {
--isRTL: 1;
}
Bu, ipucunun konumlandırılmasına yardımcı olmak için kullanılabilir:
tool-tip[tip-position="top"]) {
--_x: calc(50% * var(--isRTL));
}
Üçgenin nerede olduğunu belirlemeye yardımcı olur:
tool-tip[tip-position="right"]::after {
--_tip: var(--_left-tip);
}
tool-tip[tip-position="right"]:dir(rtl)::after {
--_tip: var(--_right-tip);
}
Son olarak, translateX()
üzerinde mantıksal dönüşümler için de kullanılabilir:
--_x: calc(var(--isRTL) * -3px * -1);
İpucu konumlandırma
Hem fiziksel hem de mantıksal ipucu konumlarını işlemek için ipucunu inset-block
veya inset-inline
özellikleriyle mantıksal olarak konumlandırın. Aşağıdaki kodda, dört konumun her birinin hem soldan sağa hem de sağdan sola yönler için nasıl stilize edildiği gösterilmektedir.
Üst ve blok başlangıcı hizalaması
tool-tip:is([tip-position="top"], [tip-position="block-start"], :not([tip-position])) {
inset-inline-start: 50%;
inset-block-end: calc(100% + var(--_p-block) + var(--_triangle-size));
--_x: calc(50% * var(--isRTL));
}
tool-tip:is([tip-position="top"], [tip-position="block-start"], :not([tip-position]))::after {
--_tip: var(--_bottom-tip);
inset-block-end: calc(var(--_triangle-size) * -1);
border-block-end: var(--_triangle-size) solid transparent;
}
Sağa ve satır sonuna hizalama
tool-tip:is([tip-position="right"], [tip-position="inline-end"]) {
inset-inline-start: calc(100% + var(--_p-inline) + var(--_triangle-size));
inset-block-end: 50%;
--_y: 50%;
}
tool-tip:is([tip-position="right"], [tip-position="inline-end"])::after {
--_tip: var(--_left-tip);
inset-inline-start: calc(var(--_triangle-size) * -1);
border-inline-start: var(--_triangle-size) solid transparent;
}
tool-tip:is([tip-position="right"], [tip-position="inline-end"]):dir(rtl)::after {
--_tip: var(--_right-tip);
}
Alt ve blok sonu hizalaması
tool-tip:is([tip-position="bottom"], [tip-position="block-end"]) {
inset-inline-start: 50%;
inset-block-start: calc(100% + var(--_p-block) + var(--_triangle-size));
--_x: calc(50% * var(--isRTL));
}
tool-tip:is([tip-position="bottom"], [tip-position="block-end"])::after {
--_tip: var(--_top-tip);
inset-block-start: calc(var(--_triangle-size) * -1);
border-block-start: var(--_triangle-size) solid transparent;
}
Sola ve satır başlangıcına hizalama
tool-tip:is([tip-position="left"], [tip-position="inline-start"]) {
inset-inline-end: calc(100% + var(--_p-inline) + var(--_triangle-size));
inset-block-end: 50%;
--_y: 50%;
}
tool-tip:is([tip-position="left"], [tip-position="inline-start"])::after {
--_tip: var(--_right-tip);
inset-inline-end: calc(var(--_triangle-size) * -1);
border-inline-end: var(--_triangle-size) solid transparent;
}
tool-tip:is([tip-position="left"], [tip-position="inline-start"]):dir(rtl)::after {
--_tip: var(--_left-tip);
}
Animasyon
Şu ana kadar yalnızca ipucunun görünürlüğünü değiştirdik. Bu bölümde, genellikle güvenli bir azaltılmış hareket geçişi olduğu için önce tüm kullanıcılar için opaklığı animasyonla göstereceğiz. Ardından, dönüştürme konumunu animasyonla hareket ettirerek ipucunun üst öğeden kayarak çıkmasını sağlayacağız.
Güvenli ve anlamlı bir varsayılan geçiş
İpucu öğesini, opaklık ve dönüştürme geçişi yapacak şekilde aşağıdaki gibi stilize edin:
tool-tip {
opacity: 0;
transform: translateX(var(--_x, 0)) translateY(var(--_y, 0));
transition: opacity .2s ease, transform .2s ease;
}
:has(> tool-tip):is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
transition-delay: 200ms;
}
Geçişe hareket ekleme
Kullanıcı hareketten rahatsız olmuyorsa ipucu, görünebileceği her taraf için translateX özelliğini biraz konumlandırarak küçük bir mesafede hareket ettirin:
@media (prefers-reduced-motion: no-preference) {
:has(> tool-tip:is([tip-position="top"], [tip-position="block-start"], :not([tip-position]))):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_y: 3px;
}
:has(> tool-tip:is([tip-position="right"], [tip-position="inline-end"])):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_x: -3px;
}
:has(> tool-tip:is([tip-position="bottom"], [tip-position="block-end"])):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_y: -3px;
}
:has(> tool-tip:is([tip-position="left"], [tip-position="inline-start"])):not(:hover):not(:focus-visible):not(:active) tool-tip {
--_x: 3px;
}
}
"in" durumu translateX(0)
olduğundan, bunun "out" durumunu ayarladığını unutmayın.
JavaScript
Bence JavaScript isteğe bağlıdır. Bunun nedeni, bu ipuçlarının hiçbirinin kullanıcı arayüzünüzde bir görevi tamamlamak için okunmasının zorunlu olmamasıdır. Bu nedenle, ipuçları tamamen başarısız olursa sorun olmaz. Bu, ipuçlarını aşamalı olarak geliştirilmiş olarak değerlendirebileceğimiz anlamına da gelir. Sonunda tüm tarayıcılar :has()
özelliğini destekleyecek ve bu komut dosyası tamamen kaldırılacak.
Polyfill komut dosyası iki işlem yapar ve bu işlemleri yalnızca tarayıcı :has()
özelliğini desteklemiyorsa gerçekleştirir. Öncelikle :has()
desteği olup olmadığını kontrol edin:
if (!CSS.supports('selector(:has(*))')) {
// do work
}
Ardından, <tool-tip>
öğelerinin üst öğelerini bulun ve bunlara çalışılacak bir sınıf adı verin:
if (!CSS.supports('selector(:has(*))')) {
document.querySelectorAll('tool-tip').forEach(tooltip =>
tooltip.parentNode.classList.add('has_tool-tip'))
}
Ardından, aynı davranışı :has()
seçiciyle simüle ederek bu sınıf adını kullanan bir stil grubu ekleyin:
if (!CSS.supports('selector(:has(*))')) {
document.querySelectorAll('tool-tip').forEach(tooltip =>
tooltip.parentNode.classList.add('has_tool-tip'))
let styles = document.createElement('style')
styles.textContent = `
.has_tool-tip {
position: relative;
}
.has_tool-tip:is(:hover, :focus-visible, :active) > tool-tip {
opacity: 1;
transition-delay: 200ms;
}
`
document.head.appendChild(styles)
}
İşlem tamamlandı. Artık :has()
desteklenmiyorsa tüm tarayıcılarda ipuçları gösterilir.
Sonuç
Nasıl yaptığımı öğrendiğinize göre siz nasıl yapardınız? 🙂 Toggle ipuçlarını kolaylaştıran popup
API'sini, z-index savaşlarına son veren üst katmanı ve öğeleri pencerede daha iyi konumlandırmayı sağlayan anchor
API'sini merakla bekliyorum. O zamana kadar ipuçları oluşturacağım.
Yaklaşımlarımızı çeşitlendirelim ve web'de içerik oluşturmanın tüm yollarını öğrenelim.
Bir demo oluşturun, bağlantıları bana tweet atın. Ben de bu bağlantıları aşağıdaki topluluk remiksleri bölümüne ekleyeyim.
Topluluk remiksleri
Henüz burada gösterilecek bir şey yok.
Kaynaklar
- Github'daki kaynak kodu