Ders 14 / 22
Renk Sistemi
Rengin rol olarak tanımlanması, tek tondan açıklık ekseninde rampa üretimi, rollerin basamaklara bağlanması ve eşleşmelerin bağıl parlaklık üzerinden denetlenmesi.
İçindekiler
Önceki üç ders metnin biçimini sabitledi. Renk tarafında ise elde yalnızca ilk dersten kalan iki değer var: koyu gri metin ve beyaz zemin. Katalog arayüzü bununla yürümez. Ödünç alma düğmesinin birincil eylem olduğu, seçili kaydın seçili olduğu, bir kaydın rafta bulunmadığı — bunların hepsi ayrı yüzeyler ve ayrı metin renkleri ister.
Buradaki asıl risk renk seçmek değil, renkleri tek tek seçmektir. Her ihtiyaç doğduğunda bir renk daha eklenirse arayüz kısa sürede birbirine yakın onlarca değer taşır; hangisinin nerede kullanılacağı kimsenin bilmediği bir liste oluşur. Ölçek tipografide bu sorunu nasıl çözdüyse, renk sistemi de aynı biçimde çözer: değerler tek tek seçilmez, türetilir; ve kullanım yerleri değerle değil rolle adlandırılır.
Renk Bir Değer Değil Bir Rol Taşır
#275ea5 bir değerdir. “Birincil eylemin zemini” bir roldür. Aradaki fark, ikincisinin
arayüz değiştiğinde de doğru kalmasıdır.
Değerle adlandırılmış bir sistemde — “mavi”, “açık mavi”, “koyu mavi” — üç sorun çıkar. Birincisi, aynı değer birbiriyle ilgisiz yerlerde kullanılır ve sonradan yalnızca birinin değişmesi gerektiğinde ayrıştırılamaz. İkincisi, ton değiştiğinde bütün adlar yalan söyler; “mavi” adlı sabit yeşile ayarlanır. Üçüncüsü, koyu temada değerler tersine döner ve “açık mavi” adı koyu bir rengi gösterir. Koyu tema, bu kursun yedinci dersinin konusudur ve rol adlandırmasının bedelini orada geri öder.
Rol adlandırması üç parçadan kurulur: neyin rengi (yüzey, metin, kenarlık, eylem), hangi
düzeyde (birincil, ikincil) ve gerekiyorsa neyin üzerinde (-uzeri eki). Bir rolün
üzerine düşen metnin rolü ayrıca adlandırılır, çünkü o metin zeminiyle birlikte denetlenir.
Rampa Tek Tondan Türetilir
Bir aileyi oluşturan basamaklar tek tek seçilmez. Ton ve doygunluk sabit tutulur, yalnızca açıklık değiştirilir. Bu yaklaşım M14/K02 CSS ile Görsel Sunum kursunda tanıtılmıştı; burada üretilen rampanın her basamağının bağıl parlaklık (relative luminance) değeri de hesaplanır, çünkü rolleri birbirine eşleştirmenin ölçütü budur.
// rampa.mjs — tek tondan aciklik ekseninde renk rampasi ve rampanin bagil parlakligi // HSL -> RGB (CSS renk gosterimi dersindeki donusumun aynisi) function hslRgb(h, s, l) { s /= 100; l /= 100; const k = (n) => (n + h / 30) % 12; const a = s * Math.min(l, 1 - l); const f = (n) => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1))); return [f(0), f(8), f(4)].map((v) => Math.round(v * 255)); } const onaltilik = (rgb) => "#" + rgb.map((v) => v.toString(16).padStart(2, "0")).join(""); // WCAG bagil parlaklik ve kontrast orani function kanal(v) { const s = v / 255; return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4); } const parlaklik = ([r, g, b]) => 0.2126 * kanal(r) + 0.7152 * kanal(g) + 0.0722 * kanal(b); function kontrast(a, b) { const [x, y] = [parlaklik(a), parlaklik(b)].sort((p, q) => q - p); return (x + 0.05) / (y + 0.05); } const BASAMAK = [ { ad: "000", l: 100 }, { ad: "050", l: 97 }, { ad: "100", l: 92 }, { ad: "200", l: 84 }, { ad: "300", l: 74 }, { ad: "400", l: 62 }, { ad: "500", l: 50 }, { ad: "600", l: 40 }, { ad: "700", l: 31 }, { ad: "800", l: 22 }, { ad: "900", l: 14 }, ]; const AILE = { notr: { ton: 214, doygunluk: 8 }, ana: { ton: 214, doygunluk: 62 }, ikincil: { ton: 268, doygunluk: 46 }, }; const rampa = {}; for (const [ad, { ton, doygunluk }] of Object.entries(AILE)) { rampa[ad] = {}; for (const b of BASAMAK) { rampa[ad][b.ad] = hslRgb(ton, doygunluk, b.l); } } const BEYAZ = [255, 255, 255]; for (const ad of Object.keys(AILE)) { console.log(`\n--- ${ad} (ton ${AILE[ad].ton}, doygunluk ${AILE[ad].doygunluk}%) ---`); console.log("basamak aciklik onaltilik bagil parlaklik beyaza karsi"); for (const b of BASAMAK) { const rgb = rampa[ad][b.ad]; console.log( `${b.ad.padStart(7)} ${String(b.l).padStart(7)}% ${onaltilik(rgb)} ` + `${parlaklik(rgb).toFixed(4).padStart(15)} ${kontrast(rgb, BEYAZ).toFixed(2).padStart(11)}:1` ); } }
--- notr (ton 214, doygunluk 8%) ---
basamak aciklik onaltilik bagil parlaklik beyaza karsi
000 100% #ffffff 1.0000 1.00:1
050 97% #f7f7f8 0.9307 1.07:1
100 92% #e9eaec 0.8223 1.20:1
200 84% #d3d6d9 0.6695 1.46:1
300 74% #b7bcc2 0.4993 1.91:1
400 62% #969da6 0.3335 2.74:1
500 50% #757e8a 0.2054 4.11:1
600 40% #5e656e 0.1281 5.89:1
700 31% #494e55 0.0752 8.39:1
800 22% #34383d 0.0390 11.80:1
900 14% #212327 0.0167 15.74:1
--- ana (ton 214, doygunluk 62%) ---
basamak aciklik onaltilik bagil parlaklik beyaza karsi
000 100% #ffffff 1.0000 1.00:1
050 97% #f3f7fc 0.9260 1.08:1
100 92% #dee9f7 0.8052 1.23:1
200 84% #bdd3ef 0.6364 1.53:1
300 74% #94b7e6 0.4588 2.06:1
400 62% #6296da 0.2947 3.05:1
500 50% #3075cf 0.1786 4.59:1
600 40% #275ea5 0.1115 6.50:1
700 31% #1e4980 0.0660 9.05:1
800 22% #15335b 0.0328 12.68:1
900 14% #0e213a 0.0149 16.19:1
--- ikincil (ton 268, doygunluk 46%) ---
basamak aciklik onaltilik bagil parlaklik beyaza karsi
000 100% #ffffff 1.0000 1.00:1
050 97% #f7f4fb 0.9144 1.09:1
100 92% #eae1f4 0.7787 1.27:1
200 84% #d5c3e9 0.5906 1.64:1
300 74% #bb9edb 0.4013 2.33:1
400 62% #9b72cb 0.2331 3.71:1
500 50% #7c45ba 0.1209 6.15:1
600 40% #633795 0.0755 8.36:1
700 31% #4d2b73 0.0454 11.00:1
800 22% #361e52 0.0232 14.34:1
900 14% #231334 0.0107 17.30:1
Üç aile aynı basamak adlarını taşıyor ve aynı açıklık değerlerinden üretildi. Buna rağmen aynı basamaktaki renklerin bağıl parlaklıkları eşit değil. Beş yüzüncü basamakta nötr 0.2054, ana 0.1786, ikincil 0.1209 parlaklık taşıyor. Aradaki fark küçük değil: ikincil ailenin beş yüzüncü basamağı, nötr ailenin aynı basamağından yaklaşık yüzde kırk daha karanlık.
Bunun nedeni, HSL gösterimindeki açıklık ekseninin algısal olmamasıdır. Bağıl parlaklık üç kanalı eşit ağırlıklandırmaz; yeşile 0.7152, kırmızıya 0.2126, maviye 0.0722 katsayı verir. Mavi ağırlıklı bir ton aynı açıklık değerinde daha karanlık bir parlaklık üretir.
Bunun pratik sonucu şudur: basamak numarası eşitliği, kontrast eşitliği anlamına gelmez. Bir rolü bir basamağa bağlamak yetmez; eşleşmenin gerçekten çalıştığı ayrıca hesaplanmalıdır.
Roller Basamaklara Bağlanır ve Eşleşmeler Denetlenir
Rol tablosu, arayüzün ihtiyaç duyduğu görevleri sayar ve her birine bir basamak verir. Ardından hangi rolün hangi zemin üzerine düştüğü listelenir ve her eşleşme denetlenir.
// roller.mjs — renk rollerinin rampa basamaklarina baglanmasi ve eslesme denetimi function hslRgb(h, s, l) { s /= 100; l /= 100; const k = (n) => (n + h / 30) % 12; const a = s * Math.min(l, 1 - l); const f = (n) => l - a * Math.max(-1, Math.min(k(n) - 3, Math.min(9 - k(n), 1))); return [f(0), f(8), f(4)].map((v) => Math.round(v * 255)); } const onaltilik = (rgb) => "#" + rgb.map((v) => v.toString(16).padStart(2, "0")).join(""); function kanal(v) { const s = v / 255; return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4); } const parlaklik = ([r, g, b]) => 0.2126 * kanal(r) + 0.7152 * kanal(g) + 0.0722 * kanal(b); function kontrast(a, b) { const [x, y] = [parlaklik(a), parlaklik(b)].sort((p, q) => q - p); return (x + 0.05) / (y + 0.05); } const ACIKLIK = { "000": 100, "050": 97, 100: 92, 200: 84, 300: 74, 400: 62, 500: 50, 600: 40, 700: 31, 800: 22, 900: 14 }; const TON = { notr: [214, 8], ana: [214, 62], ikincil: [268, 46] }; const renk = (aile, basamak) => hslRgb(TON[aile][0], TON[aile][1], ACIKLIK[basamak]); // Roller: her rol bir gorevi adlandirir, bir rengi degil. const ROL = { "yuzey": ["notr", "000"], "yuzey-ikincil": ["notr", "050"], "kenarlik": ["notr", "300"], "metin-birincil": ["notr", "900"], "metin-ikincil": ["notr", "600"], "eylem-ana": ["ana", "600"], "eylem-ana-uzeri": ["notr", "000"], "vurgu-soluk": ["ana", "100"], "vurgu-soluk-uzeri": ["ana", "800"], "eylem-ikincil": ["ikincil", "600"], }; console.log("rol aile basamak onaltilik"); for (const [ad, [aile, b]] of Object.entries(ROL)) { console.log(`${ad.padEnd(20)} ${aile.padEnd(8)} ${b.padStart(7)} ${onaltilik(renk(aile, b))}`); } // Eslesmeler: hangi rol hangi zeminin uzerine dusuyor ve hangi esik gecerli. const ESLESME = [ ["metin-birincil", "yuzey", 4.5], ["metin-ikincil", "yuzey", 4.5], ["metin-ikincil", "yuzey-ikincil", 4.5], ["kenarlik", "yuzey", 3.0], ["eylem-ana-uzeri", "eylem-ana", 4.5], ["eylem-ana", "yuzey", 3.0], ["vurgu-soluk-uzeri", "vurgu-soluk", 4.5], ["eylem-ikincil", "yuzey", 3.0], ]; console.log("\non plan zemin kontrast esik sonuc"); for (const [on, zemin, esik] of ESLESME) { const k = kontrast(renk(...ROL[on]), renk(...ROL[zemin])); console.log( `${on.padEnd(19)} ${zemin.padEnd(15)} ${k.toFixed(2).padStart(7)}:1 ${esik.toFixed(1).padStart(5)} ${k >= esik ? "gecti" : "KALDI"}` ); } // Iki eylem rengi yalniz tonla ayriliyorsa, gri tonlamada birbirine karisir. const a = renk(...ROL["eylem-ana"]); const b = renk(...ROL["eylem-ikincil"]); console.log( `\neylem-ana (${onaltilik(a)}) ile eylem-ikincil (${onaltilik(b)}) arasindaki kontrast: ${kontrast(a, b).toFixed(3)}:1` ); console.log(`bagil parlakliklar: ${parlaklik(a).toFixed(4)} ve ${parlaklik(b).toFixed(4)}`); // Ayni ayrimi aciklik ekseninde kurmak icin kac basamak gerekiyor? console.log("\neylem-ikincil basamagi kontrast (eylem-ana'ya karsi)"); for (const bas of ["200", "300", "400", "500", "600", "700", "800"]) { const k = kontrast(a, renk("ikincil", bas)); console.log(`${bas.padStart(22)} ${k.toFixed(3).padStart(7)}:1 ${k >= 3.0 ? "ayrilir" : "-"}`); } // Kenarlik rolu esigi gecmediyse: hangi notr basamak 3.0'i ilk saglar? console.log("\nnotr basamak yuzeye karsi kontrast 3.0 esigi"); for (const bas of ["200", "300", "400", "500", "600"]) { const k = kontrast(renk("notr", bas), renk(...ROL["yuzey"])); console.log(`${bas.padStart(12)} ${k.toFixed(2).padStart(21)}:1 ${k >= 3.0 ? "gecti" : "KALDI"}`); }
rol aile basamak onaltilik
yuzey notr 000 #ffffff
yuzey-ikincil notr 050 #f7f7f8
kenarlik notr 300 #b7bcc2
metin-birincil notr 900 #212327
metin-ikincil notr 600 #5e656e
eylem-ana ana 600 #275ea5
eylem-ana-uzeri notr 000 #ffffff
vurgu-soluk ana 100 #dee9f7
vurgu-soluk-uzeri ana 800 #15335b
eylem-ikincil ikincil 600 #633795
on plan zemin kontrast esik sonuc
metin-birincil yuzey 15.74:1 4.5 gecti
metin-ikincil yuzey 5.89:1 4.5 gecti
metin-ikincil yuzey-ikincil 5.51:1 4.5 gecti
kenarlik yuzey 1.91:1 3.0 KALDI
eylem-ana-uzeri eylem-ana 6.50:1 4.5 gecti
eylem-ana yuzey 6.50:1 3.0 gecti
vurgu-soluk-uzeri vurgu-soluk 10.33:1 4.5 gecti
eylem-ikincil yuzey 8.36:1 3.0 gecti
eylem-ana (#275ea5) ile eylem-ikincil (#633795) arasindaki kontrast: 1.287:1
bagil parlakliklar: 0.1115 ve 0.0755
eylem-ikincil basamagi kontrast (eylem-ana'ya karsi)
200 3.966:1 ayrilir
300 2.794:1 -
400 1.753:1 -
500 1.058:1 -
600 1.287:1 -
700 1.693:1 -
800 2.206:1 -
notr basamak yuzeye karsi kontrast 3.0 esigi
200 1.46:1 KALDI
300 1.91:1 KALDI
400 2.74:1 KALDI
500 4.11:1 gecti
600 5.89:1 gecti
Denetim iki bulgu üretti. Eşiklerin gerekçesi ve hangi ölçütten geldikleri altıncı dersin konusudur; burada önemli olan denetimin sistem kurulurken yapılmasıdır.
Birinci Bulgu: Kenarlık Rolü Eşiği Geçmiyor
Kenarlık rolü nötr üç yüzüncü basamağa bağlanmıştı ve yüzeye karşı 1.91:1 kontrast oranı üretiyor. Eşik 3.0 olduğuna göre bu kenarlık, arayüzün bir bileşen sınırını ilan etmek için kullandığı çizgiyi güvenilir biçimde göstermiyor.
Son tablo düzeltmenin ölçüsünü veriyor: nötr ailede eşiği geçen ilk basamak 500, oranı 4.11:1. Dört yüzüncü basamak 2.74 ile hâlâ altta kalıyor. Kenarlık rolü nötr 500’e taşınır.
Bu düzeltmenin bir yan etkisi var ve fark edilmesi gerekir: nötr 500 gözle bakıldığında belirgin bir gri çizgidir, üç yüzüncü basamağın verdiği ince ayrımdan uzaktır. Kenarlığın tek işi bileşen sınırını göstermekse ve o sınır zaten yüzey farkıyla ya da boşlukla kuruluyorsa, doğru karar kenarlığı koyulaştırmak değil kaldırmaktır. Yakınlık ve Gruplama dersindeki kural burada geri döner: çizgiyle kurulan bir ayrım, çoğu zaman boşlukla daha ucuza kurulur. Denetim başarısız olduğunda seçenek iki tanedir — rolü eşiğe taşımak ya da rolü gereksiz kılmak.
İkinci Bulgu: İkinci Ton Bir Hiyerarşi Kanalı Değil
Ana eylem rengi ile ikincil eylem rengi aynı basamakta duruyor ve aralarındaki kontrast oranı 1.287:1. Bu, iki rengin bağıl parlaklık ekseninde neredeyse aynı yerde olduğu anlamına gelir. Renk ayrımı sınırlı olan bir okur ya da gri tonlamalı bir çıktı için bu iki düğme aynı düğmedir.
Üçüncü tablo çözümün neden ton ekseninde olmadığını gösteriyor. İkincil aileyi ana eylemden 3.0 kontrastla ayırmak için 200 basamağına, yani açık bir mora inmek gerekiyor; ama o basamak beyaz metin taşıyamaz, çünkü kendisi yüzeye 1.64:1 kontrastla oturuyor. Aradaki basamakların hiçbiri ayrımı üretmiyor: 500 basamağında oran 1.058, yani iki renk pratikte özdeş parlaklıkta.
Buradan çıkan kural, ilk dersteki iki kanal kuralının renk tarafındaki karşılığıdır: birincil ve ikincil eylem ton farkıyla ayrılmaz. Ayrım yüzey kanalında kurulur — birincil eylem dolgulu bir yüzey taşır, ikincil eylem yalnızca kenarlık ya da düz metin olarak çizilir. Ton farkı bu ayrımın üzerine eklenebilir; ayrımı tek başına taşıyamaz.
İkinci tonun sistemde bir yeri vardır, ancak o yer eylem hiyerarşisi değildir: birbirinden bağımsız kategorileri işaretlemek, bir bilgi türünü diğerinden ayırmak ya da vurgu alanlarında zemin oluşturmak. Katalog arayüzünde ikincil aile, koleksiyon türünü gösteren etiketlerde kullanılır; oradaki ayrım hiyerarşik değil, kategoriktir ve etiketin üzerindeki metin ayrımı zaten taşır.
Sistemin Yazılı Biçimi
Rampa ve roller iki ayrı katman olarak yazılır. Birinci katman ham basamaklardır, ikinci katman rollerdir ve roller yalnızca basamaklara başvurur:
:root { /* basamaklar */ --notr-000: #ffffff; --notr-050: #f7f7f8; --notr-500: #757e8a; --notr-600: #5e656e; --notr-900: #212327; --ana-100: #dee9f7; --ana-600: #275ea5; --ana-800: #15335b; /* roller */ --yuzey: var(--notr-000); --yuzey-ikincil: var(--notr-050); --kenarlik: var(--notr-500); --metin-birincil: var(--notr-900); --metin-ikincil: var(--notr-600); --eylem-ana: var(--ana-600); --eylem-ana-uzeri: var(--notr-000); }
İki katmanın ayrı tutulması, bileşenlerin yalnızca rol adlarını kullanmasını sağlar. Bir
bileşen --notr-600 yazdığı anda sistem delinmiştir: o bileşen artık ton değişikliğinden,
tema değişikliğinden ve rol yeniden atamasından etkilenmez. Rol adlarına bağlı kalan bir
bileşen ise bu değişikliklerin hepsini ücretsiz devralır.
Özet
- Renk kararları değerle değil rolle adlandırılır; rol, arayüz değiştiğinde de doğru kalan addır ve tema geçişini mümkün kılan yapıdır.
- Bir renk ailesi tek tondan, açıklık ekseninde türetilir; basamak adları aileler arasında ortaktır.
- Aynı basamak numarası aynı kontrastı vermez; bağıl parlaklık kanalları eşit ağırlıklandırmadığı için ton değiştiğinde aynı açıklık farklı parlaklık üretir.
- Rol eşleşmeleri sistem kurulurken denetlenir; denetim başarısız olduğunda ya rol eşiğe taşınır ya da rolün gerekli olup olmadığı sorgulanır.
- Birincil ve ikincil eylem ton farkıyla ayrılmaz; iki ton aynı basamakta neredeyse özdeş parlaklık taşır ve gri tonlamada tek bir renge iner.
- Sistem iki katman olarak yazılır: ham basamaklar ve rol adları. Bileşenler yalnızca rol adlarına başvurur.
Sonraki Adım
Bu ders rengi görsel bir göreve bağladı: yüzey, metin, kenarlık, eylem. Arayüzün rengi kullandığı ikinci bir alan daha var ve orada renk görsel bir görev değil, doğrudan bir anlam taşıyor. Ödünç alma işleminin başarıyla tamamlandığı, kaydın rafta bulunmadığı, iade tarihinin geçtiği bilgisi arayüzde renkle bildiriliyor. Sonraki ders bu anlamların hangi renklere bağlandığını, bağlamanın neden tek başına yeterli olmadığını ve her işlevsel rengin kendi zemin ve metin çiftini nasıl taşıması gerektiğini ele alır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.