Mert Özen Her Satırda Daha İleri
Frontend UI/UX Tasarım İlkeleri — #05

Spacing, Layout Grid ve Alignment: 4/8'lik Spacing Sistemi, Whitespace ve Responsive Grid

Mert Özen 14 Kas 2026 11 dk 8 görüntülenme
Spacing, Layout Grid ve Alignment: 4/8'lik Spacing Sistemi, Whitespace ve Responsive Grid

Tutarlı bir arayüzün boşlukları 4 ve 8'in katlarından oluşan sınırlı bir skaladan gelir ve grup içindeki boşluk gruplar arasındakinden küçük tutulur. Bookstore kataloğunda 12 rastgele boşluk değerini, kartlar arasından geniş kart içi boşluğu ve 19 kenarlığı ölçüyor, spacing token'ları, tek sol kenar, zemin tonu ve içerikten dışa auto-fill grid ile düzeltiyor, 12 kolonluk grid ile karşılaştırıp compact density ekliyoruz.

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.

Önce Tahmin Et

Sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) için bookstore-web'in "Classics" katalog sayfası altı kitap kartı gösteriyor. Stil dosyası yıllar içinde, her sorun çıktığında bir piksel ekleyip çıkararak oluşmuş:

.catalog { max-width: 1100px; margin: 0 auto; padding: 18px 13px; }
.catalog-header { display: flex; justify-content: space-between; align-items: baseline; border-bottom: 1px solid #ccc; margin-bottom: 7px; }
.grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; }
.card { padding: 22px 15px 18px; border: 1px solid #ccc; }
.card img { width: 100%; height: auto; border: 1px solid #ccc; margin-bottom: 11px; }
.title { font-size: 16px; text-align: center; margin: 0 0 9px; }
.author { color: #555; font-size: 14px; margin: 0 0 3px; }
.price { font-weight: 700; margin: 0; padding-top: 5px; border-top: 1px solid #ccc; text-align: right; }
.btn { display: block; margin: 14px 0 0 auto; padding: 5px 10px; border: 1px solid #1d4ed8; background: #fff; color: #1d4ed8; font: inherit; font-size: 14px; }

Üç soru. Sayfada kaç farklı boşluk değeri var? Kartın iç boşluğu ile iki kart arasındaki boşluktan hangisi daha büyük ve bu, gözün kartları nasıl gruplayacağını nasıl etkiler? 390 piksel genişlikli bir telefonda bu sayfa nasıl görünür?

Bir önceki yazıda renk paletini ve semantic renkleri token'lara bağladık. Bu yazıda token dosyasına spacing ekliyor, kartlar arasındaki ilişkiyi boşlukla kuruyor ve layout grid kararını veriyoruz.

Boşluk Bir Tasarım Aracı

Spacing çoğu zaman "aralar biraz açılsın" diye son anda ayarlanır. Oysa boşluk, bir önceki yazılarda gördüğümüz hiyerarşi ve gruplamanın en ucuz aracı. İki öğe arasındaki mesafe, kullanıcıya bu öğelerin birbiriyle ne kadar ilgili olduğunu söylüyor; bu, Gestalt'ın proximity prensibi. Kenarlık, arka plan ya da başlık eklemeden yalnız boşluğu değiştirerek bir sayfanın gruplarını yeniden kurabilirsin.

Ölçerek başlayalım. Script, katalogdaki her element'in hesaplanmış margin, padding ve gap değerlerini topluyor, kartın iç boşluğunu ve kartlar arasındaki mesafeyi ölçüyor, kenarlığı olan element'leri ve kart içindeki metinlerin sol kenarlarını sayıyor:

node scripts/05-spacing.mjs
--- before: spacing values 3/5/6/7/9/10/11/13/14/15/18/22 (12 not on the 4px grid)
  card padding 15px, gap between cards 6px, bordered elements 19, text edges in a card 16/62/66/90
  @1280: 6 columns, card 383px tall, page 1280px wide
  @390: 6 columns, card 336px tall, page 688px wide (horizontal scroll)
--- after: spacing values 4/8/12/16/24 (0 not on the 4px grid)
  card padding 12px, gap between cards 24px, bordered elements 0, text edges in a card 12
  @1280: 6 columns, card 353px tall, page 1280px wide
  @390: 2 columns, card 367px tall, page 390px wide
--- after compact: spacing values 4/8/12/16/24 (0 not on the 4px grid)
  card padding 8px, gap between cards 16px, bordered elements 0, text edges in a card 8
  @1280: 7 columns, card 324px tall, page 1280px wide
  @390: 2 columns, card 373px tall, page 390px wide

İlk sorunun cevabı: before'da on iki farklı boşluk değeri var (3, 5, 6, 7, 9, 10, 11, 13, 14, 15, 18, 22) ve on ikisi de 4'ün katı değil. Bunların hiçbiri bilerek seçilmemiş; her biri bir ekran görüntüsüne bakılıp "biraz daha" denerek yazılmış. Sonuç, birbirine çok yakın ama eşit olmayan aralıklar: göz 9 ile 11 piksel arasındaki farkı hiyerarşi olarak değil düzensizlik olarak algılıyor.

Sınırlı Bir Spacing Skalası: 4 ve 8'in Katları

Typography'de olduğu gibi çözüm, az sayıda değerden oluşan bir skala. En yaygın yaklaşım 8'in katlarıyla çalışmak ve küçük işler için 4'ü de eklemek. Material Design'ın ikinci sürümünün (M2) layout rehberi de component'leri 8dp'lik (dp: Android'in yoğunluktan bağımsız piksel birimi, web'deki CSS pikseline denk) bir ızgaraya, ikonları, metni ve component içindeki bazı küçük öğeleri 4dp'lik bir ızgaraya hizalıyordu; Material 3'ün spacing sistemi de 8dp'yi temel birim alan bir skala kullanıyor. 4 ve 8 bölünebilirliği yüksek sayılar: ekran genişlikleri, ikon boyutları ve yaygın font boyutlarıyla iyi uyuşuyor ve iki değer arasında karar vermek kolay.

Bookstore'un token dosyasına eklenen skala yedi adım:

/* 05: spacing skalası, 4px taban */
:root {
  --space-1: 0.25rem; /* 4px */
  --space-2: 0.5rem;  /* 8px */
  --space-3: 0.75rem; /* 12px */
  --space-4: 1rem;    /* 16px */
  --space-6: 1.5rem;  /* 24px */
  --space-8: 2rem;    /* 32px */
  --space-12: 3rem;   /* 48px */
  --radius-md: 8px;
}

Adlar adım numarası değil, 4 pikselin katı: --space-6 6 × 4 = 24 piksel. Arada 5, 7, 9, 10, 11 yok; büyüdükçe adımlar seyreliyor, çünkü büyük boşluklarda 4 piksellik fark göze görünmüyor. Bu ad verme şekli Tailwind'in spacing skalasıyla aynı mantıkta; Tailwind'de token'ların nasıl tanımlandığı ilerideki "Tailwind CSS" serisinin theme ve design token yazısında. Skalada olmayan bir değere ihtiyaç duyduğunda önce tasarımı sorgula: çoğu zaman sorun boşluk değil, gruplama.

İlişkili Öğeler Yakın, Gruplar Uzak

İkinci sorunun cevabı en önemli bulgu: before'da kartın iç boşluğu 15 piksel (üstte 22), iki kart arasındaki mesafe ise 6 piksel. Bir kartın kenarı, kendi içeriğinden çok komşu kartın kenarına yakın. Göz kartları ayrı gruplar olarak değil, kenarlıklarla bölünmüş tek bir şerit olarak görüyor ve bu yüzden 19 kenarlık var: boşluğun söylemesi gereken "bunlar ayrı şeyler" bilgisini çizgiler taşımaya çalışıyor.

Kural basit: bir grubun içindeki boşluk, gruplar arasındaki boşluktan küçük olmalı. After'da kartın içi 12, kartlar arası 24 piksel. Kartın içinde de aynı kural sürüyor: başlık ile yazar arasında 4 piksel (birlikte okunan bilgi), yazar ile fiyat arasında 8, fiyat ile buton arasında 12. Boşluk büyüdükçe "bir sonraki şey biraz daha ayrı" diyor.

/* kartın içinde küçük adımlar, kartlar arasında daha büyüğü */
.card { display: flex; flex-direction: column; padding: var(--card-pad, var(--space-3)); background: var(--color-surface); border-radius: var(--radius-md); }
.card img { width: 100%; height: auto; border-radius: 4px; }
.title { font-size: var(--text-base); margin: var(--space-3) 0 0; }
.author { color: var(--color-text-muted); font-size: 14px; margin: var(--space-1) 0 0; }
.price { font-weight: var(--weight-bold); margin: var(--space-2) 0 0; font-variant-numeric: tabular-nums; }
.btn { margin-top: var(--space-3); padding: var(--space-2) var(--space-3); border: 1px solid var(--color-border-strong); border-radius: 6px; background: transparent; color: var(--color-link); font: inherit; font-size: 14px; font-weight: var(--weight-semibold); }

Margin'lerin hepsi yalnız üstte (margin: X 0 0). Her öğe kendinden önceki öğeyle arasındaki mesafeyi belirliyor; bir öğenin hem altına hem üstüne margin verip hangisinin geçerli olduğunu düşünmek gerekmiyor. Margin collapse ve gap'in mekaniği "CSS3: Flexbox, Grid ve Modern CSS" serisinin box model yazısında anlatıldı.

Kenarlık Yerine Boşluk ve Zemin Tonu

After'da kenarlıklı element sayısı 19'dan 0'a indi. Kartı zeminden ayıran şey artık bir çizgi değil, --color-surface ile verilen açık gri bir zemin ve kartlar arasındaki 24 piksel. Kapağın etrafındaki çerçeve, fiyatın üstündeki çizgi ve header'ın altındaki çizgi kaldırıldı; bunların her biri ekrana bir şey eklemeden göz için bir engel daha oluşturuyordu.

Kenarlığın yeri hâlâ var, ama anlam taşıdığında: bir input'un ya da zeminsiz bir butonun sınırı, kontrolü tanımlayan bilgi olduğu için kalıyor ve bir önceki yazıda gördüğümüz gibi 3:1 kontrast istiyor. Süs amaçlı ayırıcı ise çoğu zaman boşlukla değiştirilebilir. Bir gruplama için sırayla şunları dene: önce boşluk, sonra zemin tonu, en son kenarlık.

Hizalama: Tek Bir Sol Kenar

Script'in "text edges in a card" satırı kart içindeki görünür öğelerin sol kenarlarını sayıyor. Before'da dört farklı kenar var: kapak 16 pikselde, ortalanmış başlık 62'de, sağa yaslanmış buton 66'da ve sağa yaslanmış fiyat 90'da. Göz kartı okurken dört farklı yerden başlamak zorunda kalıyor. After'da tek kenar var: her şey 12 pikselden başlıyor. Bu, Gestalt'ın continuity prensibinin pratik karşılığı; aynı çizgi üzerinde dizilen öğeler tek bir akış olarak okunuyor.

Ortalama, kısa ve tek satırlık içerik için iyi (bir buton etiketi, boş bir sepetin mesajı); birden fazla satırlık ve farklı uzunluktaki metinlerde ise her satırın başlangıcı değiştiği için okumayı zorlaştırıyor. Bir kartta ya da formda varsayılan sol hizalı olsun, ortalama bilinçli bir istisna olsun.

Bir de optik hizalama var: matematiksel olarak hizalı olan her zaman hizalı görünmez. Yuvarlak bir ikon, kare bir ikonla aynı kutuya konduğunda daha küçük görünür; büyük bir başlığın ilk harfinin kendi iç boşluğu, başlığı alttaki metinden bir iki piksel içeride gösterebilir. Bu düzeltmeler küçük ve elle yapılır; bir sistemin kuralı değil, son kontrolün parçası.

Layout Grid: 12 Kolon mu, İçerikten Dışa mı?

Üçüncü sorunun cevabı çıktının @390 satırında: before'un repeat(6, 1fr) grid'i telefonda da altı kolon açmaya çalışıyor. Kolonlar içeriklerinin en küçük genişliğinin altına inemediği için sayfa 688 piksele taşıyor ve yatay kaydırma çıkıyor.

05-catalog-mobile-tr.png

Burada iki yaklaşım var. Birincisi klasik 12 kolonluk grid: sayfa genişliği on iki eşit kolona bölünüyor, her bileşen bu kolonların kaçını kapladığını söylüyor ve breakpoint'lerde bu sayı değişiyor. Avantajı, farklı sayfalar ve ekipler arasında ortak bir dil: "kenar çubuğu 3 kolon, içerik 9 kolon" herkesin anladığı bir cümle. 12 sayısı 2, 3, 4 ve 6'ya bölündüğü için çoğu düzene uyuyor. Dezavantajı, her genişlik için ayrı karar gerektirmesi; yukarıdaki before tam da bu kararın mobil için verilmemesi.

İkincisi içerikten dışa layout: önce içeriğin en küçük makul genişliğini belirliyorsun, kolon sayısını tarayıcı hesaplıyor. Bookstore'un kitap kartı 150 pikselin altında okunmuyor; o zaman grid "bu genişlikte kaç tane 150 piksellik kolon sığıyorsa o kadar" diyor:

/* içerikten dışa grid: 150px ve üstü kolonlardan sığdığı kadar */
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: var(--space-6); }

Ölçümde bu tek satır 1280 pikselde 6, 390 pikselde 2 kolon veriyor ve hiçbir breakpoint yazılmadı. web.dev'in Learn Design kursundaki macro layout örneği de aynı kalıbı kullanıyor ve çok sayıda breakpoint'in bakımının zorlaştığını hatırlatıyor. auto-fill ile minmax()'ın mekaniği "CSS3: Flexbox, Grid ve Modern CSS" serisinin CSS Grid yazısında.

İkisi birbirinin alternatifi değil. Sayfanın ana iskeleti (header, kenar çubuğu, içerik alanı) için 12 kolonluk ya da birkaç sabit bölgeli bir grid, bölgelerin içindeki tekrar eden kartlar için içerikten dışa grid iyi bir ikili. Breakpoint'leri cihazlara göre değil içeriğin kırıldığı yere koymak ise bu serinin responsive tasarım yazısında.

05-catalog-spacing-tr.png

Density: Kart ve Tablo Yoğunluğu

Aynı layout'un farklı kullanıcılar için farklı yoğunlukta olması gerekebilir. Bookstore'un müşterisi kapakları büyük görmek ister; kurumsal portalda yüzlerce kitabı listeleyen bir okul satın alma sorumlusu ise ekrana daha fazla satır sığmasını ister. Bunu ayrı bir tasarım yapmadan, spacing token'larını bir adım küçülterek sağlayabilirsin:

/* compact density: aynı layout, daha küçük adımlar */
[data-density="compact"] .grid { grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); gap: var(--space-4); }
[data-density="compact"] .card { --card-pad: var(--space-2); }
[data-density="compact"] .title { margin-top: var(--space-2); }

Ölçüm çıktısındaki after compact satırları: 1280 pikselde 6 yerine 7 kolon ve 353 yerine 324 piksellik kart. 390 pikselde ise kolon sayısı yine 2 ve kart 373 piksel, comfortable'dan 6 piksel uzun: aradaki boşluk küçülünce kolon genişliyor, kapak da onunla büyüyor. Dar ekranda kart yüksekliğini kapak belirlediği için yoğunluk ayarı burada yer kazandırmıyor. Yoğunluk seçeneği en çok tablolarda ve listelerde işe yarıyor: satır yüksekliği ve hücre padding'i bir adım küçüldüğünde aynı ekrana belirgin şekilde daha fazla satır sığar. Compact modda bile tıklanabilir hedeflerin boyutu bir alt sınırın altına inmemeli; bunun ölçüsü bu serinin responsive tasarım yazısında, touch target konusu içinde.

Üretimde Dikkat

Spacing token'ı ancak kullanıldığında işe yarar. Bu yazıdaki script gibi bir kontrol, sayfada 4'ün katı olmayan bir boşluk değeri bulduğunda CI'da uyarı verebilir; component kütüphanesi büyüdükçe bu kontrol, code review'da gözden kaçan margin: 7px'leri yakalar.

Gerçek içerikle ve gerçek sayıyla test et. Altı kitaplık bir katalog güzel görünür; sonuç sayfası 1 kitap da gösterebilir 400 de. Tek kartlık bir satırın, son satırda yalnız kalan kartın ve çok uzun bir kitap adının düzeni bozup bozmadığına bak.

Boşluğu dar ekranda daralt ama sıfırlama. Telefonda sayfa kenarı padding'ini 24'ten 16 piksele indirmek mantıklı; 0'a indirmek metni ekranın kenarına yapıştırır.

Sık Düşülen Tuzaklar

Birincisi, boşluğu göz kararıyla ayarlamak. Her seferinde "biraz daha" demek, before'daki on iki farklı değeri üretiyor.

İkincisi, grup içi boşluğu gruplar arası boşluktan büyük tutmak. Kartın içi kartlar arasından genişse kartlar ayrı görünmez ve ayırmak için kenarlık eklemek gerekir.

Üçüncüsü, her şeyi kenarlıkla ayırmak. Çizgiler hızla birikir; önce boşluk ve zemin tonunu dene.

Dördüncüsü, aynı kart içinde farklı hizalamalar. Ortalanmış başlık, sola yaslı yazar ve sağa yaslı fiyat gözü üç farklı yere gönderir.

Beşincisi, sabit kolon sayısını mobilde kontrol etmemek. repeat(6, 1fr) telefonda yatay kaydırma demek.

Küçük Bir Deneme

★☆☆ after.css'te grid'in gap'ini var(--space-2) yap. Kart içi boşluk 12, kartlar arası 8 piksel olduğunda kartların gruplanmasının nasıl değişeceğini tahmin et, sonra node scripts/shoot.mjs web/05-spacing/after.html ile ekran görüntüsü al ve bak.

★★☆ 05-spacing.mjs'e kart içindeki ardışık öğeler arasındaki dikey mesafeleri yazdıran bir adım ekle (kapak-başlık, başlık-yazar, yazar-fiyat, fiyat-buton). After'da mesafelerin büyüdükçe ilgiyi azalttığını sayılarla göster.

★★★ Kurumsal portal için bir sipariş tablosu yaz (kitap, ISBN (kitabın uluslararası standart numarası), adet, birim fiyat, toplam). Comfortable ve compact iki yoğunluğu yalnız spacing token'larıyla tanımla ve 1280×800'lük bir ekrana her iki modda kaç satır sığdığını script'le ölç.

Bir sonraki yazıda ekranlardan form'lara geçiyoruz: label'ın yeri, floating label'ın sorunları, zorunlu ve opsiyonel alanları işaretlemek, hata mesajlarının ne zaman ve nasıl gösterileceği ve Bookstore'un checkout formunda alan sayısını azaltmak.