Tüm yazılar Yazılım

Modern CSS: Grid, Değişkenler ve Karanlık Tema

Bu siteyi saf CSS ile yazarken kurduğum tema sistemi, çözdüğüm ızgara taşmaları ve kontrast düzeltmeleri. Kütüphane yok, gerçek kod var.

DT
Demir Taşdemir Mobil Uygulama & Web Geliştirici
— dk okuma

Şu an okuduğunuz sitenin CSS'ini kütüphane kullanmadan sıfırdan yazdım; tek dosya, yaklaşık 2600 satır. Mobil tarafta yıllardır Auto Layout ve Compose gibi düzen sistemleriyle çalıştığım için “CSS düzeni kolaydır” diye başladım, sonra ızgara taşmalarıyla ve koyu temada okunmayan gri metinlerle epey vakit kaybettim. Aşağıdakiler o süreçte gerçekten karşıma çıkan sorunlar ve kullandığım çözümler.

Tema bir renk listesi değil, rol listesi

İlk denemede değişkenlerimi renge göre adlandırmıştım: --gri-acik, --mavi-koyu. Koyu temayı eklediğim gün bu isimler anlamsızlaştı, çünkü “açık gri” koyu temada artık koyu bir renk oluyordu. Adları renge göre değil role göre koyunca her şey yerine oturdu: zemin, yüzey, kenarlık, metin, sönük metin.

Değişmeyen değerleri :root içinde tutuyorum; temaya göre değişenleri ise üzerindeki data-theme özniteliğine bağlı iki blokta topluyorum.

:root {
  --brand-1: #5B7CFA;
  --radius: 16px;
  --maxw: 1200px;
  --gutter: clamp(20px, 5vw, 48px);
}

[data-theme="light"] {
  --bg: #FCFCFE;
  --surface: #FFFFFF;
  --text: #10132A;
  --text-dim: #6B7189;
  color-scheme: light;
}

[data-theme="dark"] {
  --bg: #08080D;
  --surface: #12121C;
  --text: #EDEDF2;
  --text-dim: #8A8AA0;
  color-scheme: dark;
}

Bileşenler artık hiçbir yerde ham renk kodu görmüyor; sadece background: var(--surface) yazıyor. Yeni bir tema eklemek, yeni bir seçici açıp aynı isimlere farklı değerler vermekten ibaret.

color-scheme satırı görünenden önemli. Tarayıcının kendi çizdiği parçaları — kaydırma çubuğu, form denetimleri, otomatik doldurma zemini — temayla uyumlu hale getiriyor. Onsuz koyu sayfanın kenarında bembeyaz bir kaydırma çubuğu kalıyor.

Tema geçişinde yanıp sönmeyi durdurmak

Tema seçimini localStorage'da tutuyorum, ama asıl mesele ne zaman uyguladığın. Ana betiğim sayfanın sonunda defer ile yükleniyor; tema kodunu oraya koyduğumda koyu tema seçmiş ziyaretçi önce beyaz bir sayfa görüyor, sonra ekran karara kalıyordu. Klasik FOUC.

Çözüm, içine küçük ve engelleyici (defer'siz) bir betik koymak. İlk boyamadan önce çalışıyor, dolayısıyla beyaz sıçrama olmuyor:

<script>
(function () {
  try {
    var kayitli = localStorage.getItem('dt-tema');
    document.documentElement.dataset.theme = kayitli || 'light';
  } catch (e) { /* gizli mod: varsayılan açık tema kalır */ }
})();
</script>

İki ayrıntı: gizli sekmede localStorage erişimi hata fırlatabildiği için try/catch şart. Bir de tema değişince meta[name="theme-color"] içeriğini güncelliyorum; mobil tarayıcıda adres çubuğunun rengi böylece sayfayla uyumlu kalıyor.

Sistem tercihini de dinleyin

Kayıtlı tercih yoksa matchMedia('(prefers-color-scheme: dark)') ile sistem temasına düşebilirsiniz. Ben bu sitede varsayılanı bilinçli olarak açık tema bıraktım, çünkü işe alım tarafındaki okuyucu çoğunlukla siteyi ilk kez ve gündüz açıyor.

Grid mi, Flexbox mı?

Bu soruya artık tek bir cümleyle cevap veriyorum: düzeni ben mi belirliyorum, içerik mi? Satır ve sütunları önceden biliyorsam Grid; öğeler sığdıkça yan yana dizilip taşınca alt satıra geçsin istiyorsam Flexbox.

DurumSeçimNeden
Sayfa iskeleti, iki sütunlu heroGridSütun oranlarını ben tanımlıyorum
Kart listesiGridKartlar aynı genişlikte ve hizalı kalmalı
Etiket/rozet şeridiFlexboxGenişlik içeriğe göre, taşınca alta sarsın
Buton grubu, kart üstü meta satırıFlexboxTek eksen, değişken öğe sayısı
Bir öğeyi tam ortalamakGridplace-items: center tek satır

İkisi rakip değil, iç içe geçiyor. Blog kart listesi Grid, kartın içindeki tarih–kategori–okuma süresi satırı Flexbox.

auto-fit, auto-fill ve minmax()

Duyarlı kart ızgarası için tek satır yetiyor. Blog listesinde şunu kullanıyorum:

.yazilar {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: 20px;
}

Anlamı: kart en az 320 piksel olsun, sığdığı kadar sütun aç, kalan boşluğu 1fr ile paylaştır. Medya sorgusu yok, kırılım noktası yok. auto-fill ile auto-fit arasındaki fark ise az sayıda öğede ortaya çıkıyor:

  • auto-fill: satıra sığan tüm sütunları oluşturur, dolduramadıklarını boş bırakır. Üç yazı varken kartlar sola yaslı, doğal genişlikte kalır.
  • auto-fit: boş kalan sütunları sıfıra çökertir, mevcut öğeler kalan alanı paylaşır. Üç yazı varken kartlar kocaman olur.

Blog listesinde auto-fill tercih ettim, çünkü tek yazılık bir kategoride ekran boyunda bir kart görmek istemiyorum. Sayfa başlarındaki dört kutuluk özet şeridinde ise öğe sayısı sabit olduğu için auto-fit daha iyi çalışıyor.

min-width: 0 — en çok vaktimi alan iki kelime

Ana sayfanın hero bölümü iki sütunlu bir Grid ve sağ sütunda bir kod kartı var. Kartın içindeki uzun satırlar yüzünden sayfa yatay kaymaya başladı; sağ sütun kapsayıcıdan taşıyordu. Genişliklerle, overflow: hidden ile, hatta sabit piksel değerleriyle uğraştım. Sorun bambaşkaydı.

Grid ve Flex öğeleri varsayılan olarak min-width: auto alır; yani kendi içeriklerinden daha dar olamazlar. Sarmalanmayan uzun bir kod satırı, sütunun asgari genişliğini kendi uzunluğuna eşitliyordu. 1fr da aslında minmax(auto, 1fr) demek olduğu için bu kuralı bozmuyor.

.hero-inner {
  display: grid;
  grid-template-columns: 1.15fr .85fr;
  gap: clamp(32px, 5vw, 64px);
}
/* Öğeler içeriklerinden daha dar olabilsin:
   kod kartı artık kendi içinde kaydırılıyor. */
.hero-inner > * { min-width: 0; }

Aynı sorunun ikinci hâli sabit sütunlarda çıkıyor. Dört kutuluk istatistik şeridinde repeat(4, 1fr) yazınca uzun bir başlık sütunları eşitsiz yapıyordu; repeat(4, minmax(0, 1fr)) ile gerçekten eşit dört sütun elde ettim. Metin kırpma (text-overflow: ellipsis) de bu düzeltme olmadan hiç çalışmıyor — Flex satırlarında da aynısı geçerli.

Yatay kayma görüyorsanız

Önce overflow: hidden yazmayın; o sadece belirtiyi gizler. Geliştirici araçlarında taşan öğeyi bulup zincirdeki Grid/Flex ebeveynlerine min-width: 0 uygulayın. Benim vakalarımın neredeyse tamamı buydu.

clamp() ile akışkan tipografi ve düzelttiğim bir hata

clamp(en-az, tercih, en-çok) sayesinde başlık boyutları için ayrı medya sorguları yazmıyorum. Boşluklarda da işe yarıyor; sayfa kenar boşluğum tek satır:

:root { --gutter: clamp(20px, 5vw, 48px); }

/* İlk hâli — orta değer tamamen vw */
h2 { font-size: clamp(1.75rem, 4vw, 2.75rem); }

/* Düzeltilmiş hâli — rem tabanı eklendi */
h2 { font-size: clamp(1.75rem, 1.1rem + 2.6vw, 2.75rem); }

Aradaki fark erişilebilirlik. Orta değeri sadece vw ile yazarsanız yazı boyutu yalnızca pencere genişliğine bağlı kalır; kullanıcı tarayıcının yazı tipi boyutunu büyüttüğünde başlık kıpırdamaz. WCAG'nin metin yeniden boyutlandırma ölçütü %200'e kadar büyütmede içeriğin kaybolmamasını ister. Orta değere bir rem tabanı eklediğinizde başlık hem ekranla akar hem de kullanıcı tercihine cevap verir. Bu siteyi yazarken ilk hâlini kullanmıştım, sonradan başlıkları taban değerli sürüme çevirdim.

Bir de gövde metnini clamp() ile oynatmamaya karar verdim. Paragraf boyutu için sabit bir rem değeri ve max-width: 70ch civarı bir satır uzunluğu, akışkan boyuttan daha iyi okunuyor.

Kontrast: koyu temada en kolay kaçırılan yer

Koyu tema yaparken en sık düşülen tuzak, gri metni fazla söndürmek. WCAG AA seviyesi normal metin için 4,5:1, büyük metin için 3:1 kontrast oranı ister. Arayüz bileşenlerinin ve grafiklerin sınırları içinse ayrı bir ölçüt var ve orada eşik 3:1.

Benim --text-dim değerim koyu temada başta çok koyuydu; tarih, okuma süresi gibi küçük metinler #08080D zemininde eşiğin altında kalıyordu. Değeri #8A8AA0'a açtım. Açık temada da renkli rozetler sorun çıkardı: beyaz üstünde açık yeşil bir “yayında” etiketi güzel görünüyor ama okunmuyordu, o yüzden açık temada rozet metnini koyulaştırıp zemini şeffaf yeşile çektim.

Pratik yöntemim şu: renk seçerken tahmin yürütmüyorum, tarayıcının geliştirici araçlarındaki renk seçicisi kontrast oranını doğrudan gösteriyor. Sadece iki durumu ayırmak gerekiyor — dekoratif bir ayraç çizgisinin 3:1 olması şart değil, ama bir kartın veya form alanının sınırı yalnızca o çizgiyle anlaşılıyorsa şart.

Son olarak, odak halkasını asla kaldırmayın. outline: none yazıp yerine bir şey koymamak, klavyeyle gezen kullanıcıyı sayfada kör bırakıyor. Ben varsayılanı kaldırdığım yerlerde :focus-visible ile kendi halkamı çiziyorum.

Toparlarsak

Bu sitede işe yarayan altı şey: değişkenleri renge değil role göre adlandırmak, temayı ilk boyamadan önce uygulamak, düzeni ben kuruyorsam Grid içerik kuruyorsa Flexbox seçmek, kart ızgaralarını auto-fill + minmax() ile medya sorgusuz kurmak, taşma gördüğümde önce min-width: 0 denemek ve renkleri gözle değil kontrast ölçerek seçmek.

Hiçbiri yeni bilgi değil; hepsi belgelerde yazıyor. Ama min-width: 0 satırının neden gerektiğini, kendi sayfam iki gün boyunca yatay kaydıktan sonra gerçekten öğrendim. CSS'te düzenin çalışmadığı yerlerde genelde eksik olan bir özellik değil, varsayılan bir davranışın farkında olmamak oluyor.

  • CSS
  • Grid
  • Karanlık Tema
  • Erişilebilirlik
  • Duyarlı Tasarım
  • Web
Paylaş: LinkedIn X WhatsApp
DT

Demir Taşdemir

Mobil Uygulama & Web Geliştirici

2018'den beri yazılım geliştiriyorum. App Store ve Google Play'de 11 uygulama yayınladım; şu an 6 mobil uygulama, 1 e-ticaret platformu ve 1 masaüstü oyun üzerinde çalışıyorum.

Sitenizin arayüzü mü elden geçecek?

Tema sistemi, duyarlı düzen ve erişilebilirlik düzeltmeleri dahil web siteleri geliştiriyorum. Projenizi konuşalım.