Responsive ve Platforma Uygun Tasarım: Touch Target, Thumb Zone, Breakpoint Kararları ve Dark Mode
Responsive tasarım ekranı küçültmek değil, dar ekranda neyin gerekli olduğuna karar vermek, breakpoint'i içeriğin kırıldığı yere koymak, birincil hedefleri 44–48 piksele çıkarmak ve dark mode'u tonla ayrılan yüzeylerle ayrı bir tema olarak kurmaktır. Bookstore kataloğunda 97 genişliğin 26'sında yatay kaydırmayı, telefonda görünmeyen hover butonunu, 15 piksellik Checkout'u ve filtreyle ters çevrilen kapakları ölçüp düzeltiyoruz.
Sürüm notu: Bu yazıdaki örnek ekranlar Playwright 1.65 ile Chromium 158'de render edilip ölçülmüştür; script'ler Node.js 24 ile çalıştırılmıştır. WCAG (Web Content Accessibility Guidelines; web erişilebilirlik standardı) kriterleri 2.2 sürümüne göredir.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) için bookstore-web'in Classics sayfası masaüstünde tasarlanmış, sonra "mobil uyumlu" yapılmış. CSS'in sonunda şu satırlar var:
/* "Add to cart" hover'da görünür */
.add { position: absolute; left: var(--space-3); right: var(--space-3); bottom: var(--space-3); padding: var(--space-2); border: 0; border-radius: 6px; background: var(--color-action); color: var(--color-on-action); font: inherit; opacity: 0; }
.card:hover .add { opacity: 1; }
/* cihaz breakpoint'leri: iPad, sonra iPhone */
@media (max-width: 768px) {
.grid { grid-template-columns: repeat(2, 180px); gap: var(--space-4); }
}
@media (max-width: 375px) {
.grid { grid-template-columns: 340px; }
.author, .price { display: none; }
}
/* dark mode: bütün sayfayı ters çevir */
@media (prefers-color-scheme: dark) {
html { filter: invert(1) hue-rotate(180deg); }
}
Masaüstünde grid 300 piksellik üç kolon. Sayfanın üstünde 320 piksel yüksekliğinde bir kampanya banner'ı, altında 12 piksellik kategori link'leri, sağ üstte "Cart (2) · Checkout" yazan küçük bir link var. 390 piksel genişliğindeki bir telefonda kitabı sepete eklemek isteyen Ada butonu nasıl bulur? Hangi ekran genişliklerinde sayfa yana kayar? 375 piksellik bir telefonda kitapların fiyatı görünür mü? Telefonu dark mode'da kullanan biri kapakları nasıl görür?
Önce ölçelim
Script sayfayı 320'den 1280 piksele kadar 10'ar piksel adımlarla 97 genişlikte açıp yatay kaydırma olup olmadığına bakıyor. Sonra 390×844 piksellik, dokunmatik ve hover'ı olmayan bir telefon emüle ediyor: ilk ekrana kaç kartın tam sığdığını, "Add to cart"ın hover olmadan görünüp görünmediğini, bütün dokunma hedeflerinin boyutunu ve Checkout'un ekranın neresinde durduğunu ölçüyor. En sonda aynı sayfayı dark mode'da render edip kart ile zeminin piksel renklerini ve kapağın değişip değişmediğini okuyor:
node scripts/09-responsive.mjs
--- before
horizontal scroll at 26 of 97 widths (320–350, 380–390, 770–960 px)
phone 390x844: hover available false; whole cards in the first screen 2; "Add to cart" visible without hover on 0 of 6
targets: 12, failing WCAG 2.5.8 (24 px) 1, under 44 px 12, under 48 px 12
checkout: 108x15 px, centre at 3% of the screen height
at 375 px: price visible on 0 of 6 cards
dark: page 1,1,1, card 0,0,0, card vs page 1.00:1; cover colours changed (inverted)
--- after
horizontal scroll at 0 of 97 widths
phone 390x844: hover available false; whole cards in the first screen 3; "Add to cart" visible without hover on 6 of 6
targets: 12, failing WCAG 2.5.8 (24 px) 0, under 44 px 0, under 48 px 11
checkout: 138x48 px, centre at 96% of the screen height
at 375 px: price visible on 6 of 6 cards
dark: page 33,34,36, card 69,72,76, card vs page 1.73:1; cover colours unchanged
Tahminlerin cevabı before satırlarında: telefonda hover olmadığı için "Add to cart" altı kartın hiçbirinde görünmüyor; Ada sepete ekleme yolunu bulamıyor. Sayfa 97 genişliğin 26'sında yana kayıyor; bu aralıklar tam olarak breakpoint'lerin arasında kalan genişlikler. 375 piksellik telefonda fiyat altı kartın hiçbirinde yok. Checkout 15 piksel yüksekliğinde ve ekranın en üstünde. Dark mode'da kart ile zemin aynı renk ve kapaklar negatif film gibi ters dönmüş.
Mobile-first: Önce Neyin Sığacağına Karar Ver
Mobile-first'ün asıl anlamı CSS yazma sırası değil, öncelik kararı: 390 piksellik bir ekranda yalnız gerçekten gerekli olanlar sığar, bu yüzden önce neyin gerekli olduğuna karar vermek zorunda kalırsın. Before bu kararı tersinden vermiş. 375 pikselde yer açmak için fiyatı ve yazarı gizlemiş, ama kullanıcının kitap seçerken en çok baktığı bilgi fiyat. Öte yandan 320 piksellik dekoratif banner'ı korumuş; ilk ekranın üçte birinden fazlası bir kampanya cümlesine gidiyor.
After'da kampanya tek satırlık bir metin, kitap listesinin hemen üstünde ve uyarı renginde; fiyat, yazar ve "Add to cart" her genişlikte görünüyor. İlk ekrana tam sığan kart sayısı 2'den 3'e çıktı ve her kartta sepete ekleme butonu görünür. İçeriği gizlemek yerine düzeni değiştirmek gerekiyor: dar ekranda kart, kapağı solda ve bilgiyi sağda gösteren yatay bir satıra dönüşüyor.
Breakpoint'i İçerik Belirler, Cihaz Değil
Before'un breakpoint'leri cihaz adlarıyla seçilmiş: 768 "iPad", 375 "iPhone". Ölçüm bunun bedelini gösteriyor: 380–390 piksel arası iki kolon 180 piksel ve boşluklarla ekrana sığmıyor, 770–960 piksel arası üç kolon 300 piksel sığmıyor, 320–350 arası tek kolonluk 340 piksel sığmıyor. Her yeni telefon boyutu yeni bir istisna demek. web.dev'in Learn Design kursu da breakpoint'leri cihaz genişliklerine göre değil içeriğin ihtiyaç duyduğu yere koymayı öneriyor, çünkü cihaz boyutları her yıl değişiyor.
After grid'i sabit kolon sayısıyla değil, bir kartın ihtiyaç duyduğu en küçük genişlikle tanımlıyor; kaç kolon olacağına tarayıcı karar veriyor:
/* grid içeriğin kırıldığı yerde kırılır: 220 px sığdıkça yeni bir kolon */
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 220px), 1fr)); gap: var(--space-4); margin: 0; padding: 0; list-style: none; }
Sayfada tek bir media query kaldı ve o da bir cihaza değil bir içerik kararına bağlı: ekran 64rem'e ulaşınca sepet özeti alttaki sabit bardan çıkıp sayfanın akışına giriyor. 97 genişliğin hiçbirinde yatay kaydırma yok. 320 pikselin ölçüme dahil olması tesadüf değil: WCAG'nin AA (çoğu yasal yükümlülüğün istediği orta seviye) seviyesindeki 1.4.10 Reflow kriteri içeriğin 320 CSS piksel genişlikte iki yönlü kaydırma gerektirmeden kullanılabilmesini istiyor; bu genişlik, 1280 piksellik bir ekranda yüzde 400 yakınlaştırmaya karşılık geliyor. minmax, clamp ve media query range syntax'ının ayrıntısı "CSS3: Flexbox, Grid ve Modern CSS" serisinin responsive design yazısında.
Component Kendi Genişliğine Bakar
Aynı kitap kartı telefonda tek kolonda geniş, tablette iki kolonda dar, masaüstünde dört kolonda yine dar. Kartın düzenini ekranın genişliğine bağlamak, kartı başka bir yere (örneğin sepet sayfasının "Bunları da beğenebilirsin" şeridine) koyduğunda yanlış sonuç veriyor. Container query, component'in düzenini kendi kutusunun genişliğine bağlıyor:
.card { container-type: inline-size; background: var(--color-surface-raised); border: 1px solid var(--color-border); border-radius: var(--radius-md); box-shadow: var(--shadow-raised); }
.book { display: grid; grid-template-columns: 96px 1fr; gap: var(--space-3); padding: var(--space-3); }
/* kart ekranın değil kendi genişliğine göre karar verir: dar kartta kapak üstte */
@container (max-width: 300px) {
.book { grid-template-columns: 1fr; }
.book img { max-width: 160px; }
.add { width: 100%; }
}
390 piksellik telefonda kart 358, 700 piksellik ekranda iki kolonda 326 piksel ve kapak solda duruyor; 768 piksellik bir tablette üç kolona bölünen kartlar 235 piksele düştüğü için kapak üste geçiyor. Tasarım kararını da bu şekilde düşünmek gerekiyor: "telefonda nasıl görünür" yerine "bu component 280 piksele düşünce ne olur". Container query'nin size ve style query'leri CSS3 serisinin container query yazısında ayrıntılı anlatılıyor.
Touch Target: 24, 44 ve 48
Parmak fareden çok daha kaba bir işaretçi ve hedef boyutu için birden fazla sayı dolaşıyor. Hepsi doğru, ama farklı soruların cevabı:
| Kaynak | Değer | Niteliği |
|---|---|---|
| WCAG 2.2, 2.5.8 Target Size (Minimum) | 24×24 CSS px | AA, alt sınır; boşluk istisnası var |
| WCAG 2.2, 2.5.5 Target Size (Enhanced) | 44×44 CSS px | AAA (en yüksek uygunluk seviyesi) |
| Apple Human Interface Guidelines | 44×44 pt varsayılan, 28×28 pt en az | iOS ve iPadOS kontrolleri |
| Material 3 | 48×48 dp | yaklaşık 9 mm fiziksel boyut |
2.5.8'in boşluk istisnası önemli: W3C'nin (web standartlarını yayımlayan kuruluş) açıklamasına göre 24 pikselden küçük bir hedef, merkezine yerleştirilen 24 piksel çaplı bir daire başka bir hedefe ya da başka bir küçük hedefin dairesine değmiyorsa yine geçiyor. Cümle içindeki link'ler, tarayıcının kendi çizdiği kontroller ve aynı işi yapan daha büyük bir kontrolün bulunduğu durumlar da istisna. Script bu kuralı birebir uyguluyor ve sonuç öğretici: before'un 12 hedefinin 12'si 44 pikselin altında, ama 2.5.8'i yalnız 1'i, 14 piksel genişliğindeki "All" link'i geçemiyor. 2.5.8 bir alt sınır; kullanılabilir bir hedefin ölçüsü değil. Kriterin kendisi de önemli link ve kontroller için 44 pikseli hedeflemeyi öneriyor.
After bütün hedefleri en az 44 piksel yüksekliğe çıkardı, Checkout'u 48'e. Material'ın 48 dp'sinin altında kalan 11 hedef bilinçli bir seçim: 44 piksel Apple'ın varsayılanı ve WCAG'nin AAA eşiği, bu ekranın yoğunluğu için yeterli. Bir hedefin görünen kısmının küçük ama dokunulabilir alanının büyük olabileceğini de unutma: Material'ın kendi örneğinde 24 dp'lik bir ikon, çevresindeki padding ile 48 dp'lik bir dokunma alanına sahip. 05. yazıdaki compact density seçeneği de bu sınırların içinde kalmalı.
Tablodaki hiçbir sayı before'un asıl sorununu yakalamıyor: "Add to cart" hover'da görünüyor ve telefonda hover yok. Ölçümdeki hover available false satırı matchMedia('(hover: hover)')'un sonucu. Hover yalnız bir ek feedback olabilir, bilgiye ya da işleme giden tek yol olamaz.
Başparmak Erişimi
Telefon kullanımı üzerine en çok alıntılanan çalışma Steven Hoober'ın 2013'te UXmatters'ta yayımladığı gözlem: halka açık yerlerde ekrana dokunan 780 kişinin yüzde 49'u telefonu tek elle, yüzde 36'sı bir elde tutup diğerinin parmağıyla, yüzde 15'i iki elle kullanıyordu. Yazıdaki "başparmak erişim alanı" çizimleri de çok yayıldı. Ama Hoober 2017'de yazının altına düştüğü notta bu erişim çizimlerinin doğru olmadığını ve dikkate alınmamasını istedi; sonraki çalışmalarında insanların ekranın ortasına kenarlardan ve köşelerden daha sık, daha hızlı ve daha isabetli dokunduğunu gördüğünü belirtti.
Bundan çıkan pratik kural "her şeyi alta koy" değil, daha sade: sık kullanılan birincil işlemi tutuşu değiştirmeden ulaşılabilecek yere koy, yıkıcı ya da nadir işlemleri köşelere. Before'da Checkout ekranın yüzde 3 yüksekliğinde, sağ üst köşede ve 15 piksel; tek elle tutan biri için en zor yer. After'da sepet özeti ve Checkout ekranın altında sabit bir barda, merkezi ekran yüksekliğinin yüzde 96'sında ve 48 piksel. Barın alt padding'i env(safe-area-inset-bottom) ile çentikli ve ev çubuklu telefonlarda sistem alanının altında kalmıyor. Bir önceki yazıdaki bottom bar önerisi de aynı mantığa dayanıyor; hedefin kendisi Fitts yasasının iki yarısını da karşılıyor: büyük ve yakın.
/* sepet başparmağın olduğu yerde: altta bir bar */
.cart-bar { position: fixed; left: 0; right: 0; bottom: 0; display: flex; justify-content: space-between; align-items: center; gap: var(--space-4); padding: var(--space-3) var(--space-4) calc(var(--space-3) + env(safe-area-inset-bottom));
background: var(--color-surface-raised); border-top: 1px solid var(--color-border); box-shadow: var(--shadow-raised); }
Sabit bir bar sayfanın son içeriğini örtebilir; after bu yüzden body'ye barın yüksekliği kadar alt padding veriyor. Klavye focus'unun bu barın altında kalmaması da 07. yazıda andığımız 2.4.11 Focus Not Obscured kriterinin konusu.
Dark Mode'u Bir Tema Olarak Tasarlamak
filter: invert(1) hue-rotate(180deg) tek satırla dark mode veriyor gibi görünüyor, ölçüm ise iki sorunu gösteriyor. Birincisi görseller: filtre fotoğrafları ve kapakları da ters çeviriyor, Dune'un turuncu kapağı negatif film gibi görünüyor. İkincisi derinlik: beyaz kart beyaz zeminden yalnız gölgeyle ayrılıyordu; ters çevrilince ikisi de siyah, gölge görünmez, kart ile zemin arasındaki kontrast 1.00:1. 04. yazıda paleti ters çevirmeden yeniden ayarlamıştık; burada aynı fikri yükseklik (elevation) için uyguluyoruz:
/* 09: yükseklik: açık temada gölge, koyu temada daha açık bir ton */
:root {
--color-surface-raised: #ffffff;
--shadow-raised: 0 1px 2px rgb(0 0 0 / 0.08), 0 4px 12px rgb(0 0 0 / 0.08);
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--color-surface-raised: var(--gray-800);
--shadow-raised: none;
}
}
Açık temada yükselen yüzey beyaz ve gölgeli, koyu temada gölge yok ama yüzey zeminden daha açık bir ton. Kart ile zemin arasındaki fark 1.73:1; metin kontrastı değil, yüzeyleri birbirinden ayırmaya yetecek bir fark. Platform rehberleri de aynı yolu izliyor: Material 3 koyu temada yüzeyleri surface ve beş kademeli surface container rolüyle ayırıyor, Apple'ın Human Interface Guidelines'ı koyu modda iki arka plan seti tanımlıyor: daha sönük "base" renkler geride kalan arayüz için, daha parlak "elevated" renkler öne çıkan popover ve modal'lar için.
Görseller ve logo da birer tema kararı. Kapaklar fotoğraf ve illüstrasyon olduğu için iki temada da aynı kalıyor; after'daki logo ise stroke="currentColor" kullanan inline bir SVG (vektörel görsel biçimi), metin rengiyle birlikte kendiliğinden açık temada koyu, koyu temada açık oluyor. Renkli bir logo ya da şeffaf zeminli bir ürün fotoğrafı için picture element'inde media="(prefers-color-scheme: dark)" ile ikinci bir dosya sunmak gerekebilir.
Son karar kullanıcı tercihine saygı. Varsayılan, işletim sisteminin ayarı (prefers-color-scheme); sitede ayrıca bir tema seçeneği varsa genellikle üç seçenek sunulur: System, Light, Dark. Token'lardaki :root:not([data-theme="light"]) seçicisi, kullanıcı açıkça "Light" seçtiğinde sistem dark olsa bile açık temayı koruyor. Tercihin saklanması ve light-dark() ile uygulanması CSS3 serisinin custom property ve theming yazısında, Tailwind'deki karşılığı Tailwind CSS serisinin theme yazısında.

Üretimde Dikkat
Emülasyon gerçek cihazın yerini tutmaz. Playwright'ın mobil emülasyonu viewport'u, dokunmayı ve hover'ın yokluğunu doğru taklit ediyor ama ekranın fiziksel boyutunu, parmağın kalınlığını, güneş altında okunabilirliği ve kullanıcının telefonu nasıl tuttuğunu taklit edemiyor. Ölçümler hatayı erken yakalamak için; son kontrol gerçek telefonda yapılmalı.
Kullanıcının yazı boyutu ayarını hesaba kat. Tarayıcıda varsayılan font boyutunu büyüten biri için rem ile yazılmış 64rem'lik breakpoint metinle birlikte kayıyor; piksel ile yazılmış değerler ise kaymıyor. After'daki 220 piksellik kart minimumu bu açıdan hâlâ zayıf bir nokta: büyük font ayarında kartın içindeki metin sıkışabilir, değeri rem'e çevirmek bunun düzeltmesi.
Dark mode'u her ekranda ayrıca kontrol et. Grafikler, kod blokları, gömülü harita ve video oynatıcıları gibi üçüncü taraf içerikler token'larını bilmiyor; en sık dark mode hatası, açık temaya göre çizilmiş bir grafiğin koyu zeminde kaybolması.
Sık Düşülen Tuzaklar
Birincisi, bilgiyi ya da işlemi yalnız hover'a bağlamak. Dokunmatik ekranda hover yok; hover yalnız ek bir feedback olabilir.
İkincisi, breakpoint'leri cihaz adlarıyla seçmek. İki breakpoint arasındaki her genişlik bir hata adayı; grid'i içeriğin minimum genişliğiyle tanımla.
Üçüncüsü, dar ekranda yer açmak için önemli bilgiyi gizlemek. Önce dekoratif olanı küçült, sonra düzeni değiştir, en son içeriği kısalt.
Dördüncüsü, WCAG 2.5.8'in 24 pikselini hedef sanmak. O bir alt sınır; birincil işlemler için 44–48 piksel hedefle.
Beşincisi, dark mode'u filtreyle ters çevirmek. Görseller bozuluyor, gölgeyle verilen derinlik kayboluyor; yüzeyleri tonla ayıran ayrı bir token seti gerekiyor.
Küçük Bir Deneme
★☆☆ Script'teki genişlik taramasını after için 2 piksellik adımlarla tekrar çalıştır ve hangi genişliklerde kolon sayısının değiştiğini yazdır. Değişim noktalarını 220 piksellik minimum, 16 piksellik gap ve sayfa padding'inden önce hesapla, sonra ölçümle karşılaştır.
★★☆ Kategori şeridindeki link'leri 32 piksele indir ve aralarındaki boşluğu 4 piksel yap. Script'in 2.5.8, 44 ve 48 piksel sayılarını önce tahmin et, sonra ölç; 2.5.8'in boşluk istisnasının sonucu nasıl değiştirdiğini yorumla.
★★★ Sayfaya System, Light ve Dark seçenekleri sunan bir tema seçici ekle; seçim data-theme attribute'u ile uygulansın ve sayfa yeniden açıldığında korunsun. Token'lara kullanıcının sistem açık temadayken Dark seçtiği durum için gereken kuralı ekle ve script'in dark ölçümünü bu durum için de çalıştır.
Bir sonraki yazıda bu dokuz yazıda tek tek eklediğimiz token'ları ve component'leri bir sisteme bağlıyoruz: design system'in token, component, kullanım kuralları ve yönetişimden oluştuğunu, primitive, semantic ve component token katmanlarını, DTCG (Design Tokens Community Group; W3C bünyesinde token formatını belirleyen topluluk grubu) 2025.10 formatını ve tek bir kaynaktan CSS ile Tailwind çıktısı üretmeyi göreceğiz.