selupucin
Tasarım

Tasarım Sisteminden Production'a: Tutarlılığı Kodda Korumak

Tasarım sistemi kurmak kolay, tutarlı kalmak zor. Token katmanlarından bileşen API'sine ve CI'a: kararları production'da ayakta tutmanın yolu.

Deniz Selupuçin7 dk okuma

Sistem kurmak kolay, tutarlı kalmak zor

Bir tasarım sisteminin en kolay günü ilk günüdür. Zor olan, altı ay ve otuz yeni ekran sonra sistemin hâlâ üründe geçerli olması. Sistemler genelde ölmez, aşınır: bir gölge elle yazılır, bir buton "sadece bu sayfaya özel" diye kopyalanır, bir gri tonu "neredeyse aynısı" diye eklenir. Bir yıl sonra elinde sistem değil, sistemin anısı kalır.

Bu yazı tamamen uygulama tarafında. Kararın verildiğini, sistemin gerçekten gerekli olduğunu varsayıyorum; "gerekli mi, ne zaman?" sorusunu tasarım sistemi neden ve ne zaman gerekli yazısında ayrıca tarttım, kavramın kısa tanımı da tasarım sistemi maddesinde. Burada tek bir soruya cevap arıyorum: kararlar verildikten sonra onları production'da nasıl ayakta tutarsın?

Token katmanları: primitive, semantic, bileşen

Token, bir tasarım kararının adlandırılmış ve makinece okunabilir hâlidir. Ama düz bir token listesi ölçeklenmez; üç katman gerekir.

  1. Primitive (ham palet): Değerin kendisi — violet-600, slate-950, space-4. Anlamı yok, ölçüsü var.
  2. Semantic (anlamsal): Amaca göre isim — accent, ink, surface, border-subtle. Bileşenlerin gördüğü tek katman budur.
  3. Bileşen düzeyi: Yalnızca bir bileşenin gerçekten kendine ait bir kararı varsa — button-bg, card-border.

Kritik kural tek cümlede: bileşen primitive katmana asla bakmaz. Bir buton violet-600 yazdığı anda tema değiştirme yeteneğini kaybedersin; çünkü rengi değiştirmek için artık bileşeni değiştirmen gerekir.

İsimlendirme: görünüşü değil amacı söyle

--color-mor kötü bir isimdir; marka rengi yeşile döndüğü gün yalan söylemeye başlar. --color-accent iyidir, çünkü değeri değil rolü adlandırır. Aynı mantık boşlukta da geçerli: --space-card-gap bir niyeti anlatır, --space-18 yalnızca bir sayıyı.

Token'ları koda taşımak: tek kaynak

Tailwind v4'te token'ları CSS'in içinde tanımlıyorum; ayrı bir yapılandırma dosyası yok, tek dosya hem kaynak hem çıktı:

@import "tailwindcss";

@theme {
  /* 1 — primitive: ham palet, bileşenler bunu görmez */
  --color-violet-600: oklch(0.55 0.24 285);
  --color-slate-950: oklch(0.16 0.02 275);

  /* 2 — semantic: bileşenlerin kullandığı katman */
  --color-accent: var(--color-violet-600);
  --color-ink: var(--color-slate-950);
  --color-surface: oklch(1 0 0);

  /* 3 — hareket ve yarıçap da token'dır */
  --radius-card: 1.125rem;
  --ease-out-soft: cubic-bezier(0.22, 1, 0.36, 1);
}

Bu blok yalnızca değişken tanımlamaz; bg-accent, text-ink, rounded-card gibi utility'leri de üretir. Yani token'ı tanımladığın an kullanım biçimi de doğar ve "hangi sınıfı yazacağım?" sorusu ortadan kalkar.

Buradan çıkan disiplin şu: proje genelinde ham hex ve ham piksel yasak. Bir değeri elle yazmak istiyorsan ya sistemde eksik bir token vardır ya da yaptığın şey sistemin dışındadır. İkisi de konuşulması gereken bir karardır, sessizce geçilmesi gereken bir detay değil.

Tema override tek kaynağı bozmamalı

Koyu tema, beyaz etiket ya da kampanyaya özel bir varyant istendiğinde en yaygın hata, bileşenlerin içine koşullar serpiştirmektir. Doğru yol semantic katmanı yeniden bağlamaktır; bileşene hiç dokunmazsın:

[data-theme="dark"] {
  --color-surface: var(--color-slate-950);
  --color-ink: oklch(0.97 0.01 285);
}

Bileşen hâlâ bg-surface text-ink yazıyor. Tema değişti, bileşen değişmedi. Bu, sistemin en somut getirisi: değişim maliyeti tek katmanda toplanır.

Bileşen API'si: yanlış kullanımı tipte imkânsız kıl

Tutarlılığın ikinci ayağı, bileşenlerin kısıtlı ve niyetli olmasıdır. Bir butona istediğin her rengi geçirebiliyorsan sistem değil, süslü bir kaos kurmuşsundur. Seçenekleri kapalı bir kümede tut:

type ButtonBase = {
  variant?: "primary" | "secondary" | "ghost";
  size?: "sm" | "md" | "lg";
};

// Yalnızca ikon içeren buton, erişilebilir ad vermeden derlenemez.
type ButtonProps = ButtonBase &
  (
    | { iconOnly: true; label: string; children?: never }
    | { iconOnly?: false; children: React.ReactNode }
  );

İkinci blok, tipin sadece "hata yakalamak" için değil kural dayatmak için de kullanılabileceğini gösteriyor: ikon-butonu etiketsiz kullanmak artık bir kod incelemesi konusu değil, derleme hatası. Bu yaklaşımın mantığını uçtan uca tip güvenliği yazısında daha geniş anlattım.

Pratikte işe yarayan üç kural:

  • Kaçış kapısı bırak ama daralt. Serbest className yerine yalnızca düzenle ilgili sınıflara (boşluk, genişlik) izin veren bir yol aç; renk ve tipografi API'de hiç görünmesin.
  • Varyant sayısını düşük tut. Beş varyantlı bir buton, kullanıcıya beş karar sunar. Kararı sen ver.
  • Yeni varyant yerine yeni bileşen sor. Bir varyant üçüncü koşulu taşıyorsa, muhtemelen o artık başka bir bileşendir.

Erişilebilirliği bileşen düzeyinde garantiye al

Erişilebilirlik, ekran ekran denetlenerek değil bileşenin içine gömülerek ölçeklenir. Sistem seviyesinde sabitlediklerim:

  • Odak görünürlüğü bileşenin parçası. :focus-visible halkası varyanttan bağımsız, her zemin üzerinde görünür bir token'la tanımlı.
  • Kontrast çiftleri önceden doğrulanmış. ink ve surface gibi eşleşmeler baştan ölçülür; ikincil vurgu rengi yalnızca dolgu ve gradyan içindir, metin ya da tek başına kenarlık rengi olarak API'de hiç açılmaz.
  • Dokunma hedefi minimumu boyut token'ında. sm bile eşiğin altına inemez; küçük görünen buton, tıklanabilir alanını kaybetmez.
  • Hareket tercihine saygı varsayılan. Geçişler, hareket azaltma tercihinde otomatik olarak sönümlenir; bunu her bileşende ayrıca hatırlamak zorunda kalmam.

Temel ilkeleri erişilebilirlik temelleri yazısında, kavramın tanımını ise erişilebilirlik maddesinde topladım. Sistemdeki fark şu: bunlar bir kontrol listesi değil, varsayılan hâline gelir.

Dokümantasyon: canlı örnek, ölü ekran görüntüsü değil

Kimsenin nasıl kullanacağını bilmediği sistem kullanılmaz; kullanılmayan sistem de altı ayda terk edilir. Ama dokümantasyondan kastım kalın bir PDF değil:

  • Her varyant çalışır hâlde görünsün; ekran görüntüsü bir hafta sonra yalan söyler.
  • Yanına kopyalanabilir kod olsun; kopyalama kolaysa yeniden yazma azalır.
  • "Yapma" örnekleri ekle. Sınırı gösteren tek şey, yanlış kullanımın yanlış olduğunu söylemektir.
  • Karar kuralını yaz: ne zaman primary, ne zaman ghost. Görsel değil, karar dokümante edilir.

Dokümantasyonun ölçüsü de basit: yeni bir ekranı kurarken kimseye soru sormadan doğru bileşeni bulabiliyorsan doküman çalışıyordur. Bulamıyorsan sorun genelde eksik açıklama değil, fazla seçenektir.

Testler: gözün yerine makine

Görsel tutarlılık, bir noktadan sonra gözle denetlenemeyecek kadar büyür. Üç test türü yeterli:

  1. Görsel regresyon: Bileşenlerin referans görüntüleriyle karşılaştırılması. Bir gölge iki piksel kaysa yakalar.
  2. Tek-tanım (contract) testi: "Token kaynağı ile üründeki değer birebir aynı mı?" sorusunun otomatik cevabı.
  3. Erişilebilirlik dumanı: Her bileşen hikâyesinde otomatik denetim; rol, ad ve kontrast ihlallerini erken yakalar.
it("semantic token'lar tek kaynaktan gelir", () => {
  for (const [name, value] of Object.entries(semanticTokens)) {
    expect(readCssVar(name)).toBe(value);
  }
});

Uyguladığım ilke şu: bir değer iki yerde yaşıyorsa ya birleştir ya da eşitliğini bir testle kilitle. Çift bakım her zaman, istisnasız sapar.

Sürümleme ve kırıcı değişiklik yönetimi

Sistem birden fazla yüzeyi besliyorsa artık bir API'dir ve API gibi yönetilmelidir.

  • Token silmek veya yeniden adlandırmak kırıcıdır. Yeni sürüm numarası ister.
  • Önce işaretle, sonra kaldır. Eskiyen token bir sürüm boyunca çalışmaya devam eder ama uyarı üretir. Aynı anda hem bozmak hem taşımak, kimsenin güncellemek istemediği bir sisteme yol açar.
  • Geçişi otomatikleştir. Yeniden adlandırma yapıyorsan dönüşümü çalıştırılabilir bir betikle ver; "şunu şuna çevirin" cümlesiyle değil.
  • Değişiklik günlüğünü niyetle yaz. "accent açıldı" değil, "aksan tonu koyu zeminde kontrast eşiğini geçmiyordu, açıldı".

Drift'i yakalamak: sapmanın erken sinyalleri

Tasarım ile kod arasındaki sapma bir günde olmaz, birikir. Ölçülebilir sinyaller:

  • Kod tabanındaki ham hex ve ham piksel sayısı artıyor.
  • Keyfi değer sınıfları çoğalıyor; her biri sistemde eksik bir token'ın itirafıdır.
  • Bileşen dışından gelen stil ezmeleri artıyor.
  • Aynı işi yapan ikinci bir bileşen doğmuş ve kimse fark etmemiş.

Bu sayıları haftalık takip etmek, en pahalı sorunu en ucuz aşamada yakalar. Sapmanın kaynağı çoğu zaman kötü niyet değil, acele ve eksik token'dır; ikisi de erken görülürse ucuza çözülür.

Sapmayı gördüğünde verilecek karar da ikiye ayrılır: ya ihtiyaç meşrudur ve sisteme yeni bir token ya da varyant olarak girer, ya da tek seferlik bir istisnadır ve geri alınır. Yapılmaması gereken tek şey, üçüncü yolu seçmek: istisnayı görmezden gelip yaşamasına izin vermek. Sistemi bitiren şey büyük bir yanlış karar değil, kaydedilmemiş küçük istisnaların toplamıdır.

Hepsini CI'a bağla

Elle korunan hiçbir kural uzun ömürlü değildir. Sistemin garantilerini boru hattına taşıyorum:

  • Ham renk ve ham boşluk kullanımını reddeden lint kuralı.
  • Token üretimi çalıştırılır ve çıktı ile depo arasındaki fark kontrol edilir; elle düzenlenmiş çıktı reddedilir.
  • Görsel regresyon ve erişilebilirlik testleri her değişiklikte koşar.
  • İstemci paketi bütçesi aşılırsa yapı kırılır; sistem büyürken hız sessizce kaybedilmez.

Bu kapıların hepsi geçtikten sonra tutarlılık artık bir niyet değil, doğrulanmış bir durum olur.

Özet

  • Token'ları üç katmana ayır; bileşenler yalnızca anlamsal katmanı görsün.
  • Token'ı koda taşırken tek kaynak kullan; ham hex ve ham piksel yasak olsun.
  • Tema değişimini semantic katmanda yap, bileşene hiç dokunma.
  • Bileşen API'sini daralt; yanlış kullanımı derlenmez hâle getir.
  • Erişilebilirliği bileşenin varsayılanı yap, ekran denetimi konusu bırakma.
  • Dokümantasyonda kararı anlat, görüntüyü değil; "yapma" örneklerini ekle.
  • Görsel regresyon ve contract testleriyle sapmayı kilitle, hepsini CI'a bağla.

Tasarım sistemi bir kütüphane değil, bir anlaşmadır: tasarımcı ile geliştiricinin — ki bu projede aynı kişi — aynı dili konuşma sözü. Anlaşmayı ayakta tutan şey iyi niyet değil, otomatik kontrollerdir. Mevcut ürününde bu sapmayı yaşıyorsan tasarım sistemi kurulumu yaklaşımıma göz atabilir ya da arayüzünü birlikte gözden geçirmek için bana yazabilirsin.

Devamı

İlgili yazılar

Tasarım
7 dk okuma

Dönüşüm Odaklı Arayüz Tasarımı

Ziyaretçiyi müşteriye çeviren arayüz kararları: net değer önermesi, tek birincil eylem, sürtünmesiz form, güven unsurları, hız ve doğru ölçüm.

Tasarım
8 dk okuma

Web Erişilebilirliğinin Temelleri (WCAG 2.2)

WCAG 2.2 AA'yı pratiğe çeviren temeller: anlamlı HTML, klavye erişimi, kontrast, formlar, ARIA sınırları, hareket duyarlılığı ve gerçek test.

Sırada ne var?

Bir fikriniz mi var? Hayata geçirelim.

merhaba@selupucin.com

Genelde 24 saat içinde dönüş yapıyorum.