OKLCH Renk Uzayı Açıklaması — Tailwind v4 Neden Benimsedi
2025 döneminin herhangi bir tasarım sisteminin — shadcn/ui, Radix Themes, Tailwind v4 varsayılan paleti — kaynağını açın ve hemen göze çarpan ilk şey renklerdir. Hex kodları değil, hsl() üçlüleri değil, üç yıl önce kimsenin konuşmadığı bir işlev: oklch(). Tailwind v4, tüm varsayılan paletini OKLCH literal’leri olarak gönderir. shadcn artık OKLCH özel özellikleri yayan temalar üretir. Vercel’in tasarım sistemi 2024’te bunun etrafında yeniden inşa edildi.
Bu, bir akıma kapılma değildir. Ciddi her tasarım sisteminin renk modellerini sessizce değiştirmesinin belirli ve matematiksel bir nedeni vardır; bunu bir kez gördükten sonra, HSL’nin onu kullandığımız iş için neden her zaman yanlış olduğunu görmemezlikten gelemezsiniz.
Bu yazı, o nedeni ilk ilkelerden adım adım açıklar, bir hex kodundan OKLCH’ye çözümlenmiş bir dönüşümle sonlanır ve kendi paletiniz için bir geçiş tarifi sunar.
Renk uzayları bozulduğunda
Tasarım sistemlerinin bir tonal işi vardır. Bir düğme, dinlenme durumundan biraz daha açık bir tonla üzerine gelinmiş hâlde durur. Soluk bir kart, çevresindeki yüzeyden bir kademe daha koyu oturur. Bir odak halkasının, arkasındaki nötr kromun belirgin biçimde üstünde parlak olması gerekir. Bunu büyük ölçekte iyi yapmak, “daha açık” ve “daha koyu” ifadelerinin paletinizdeki her renk tonu için aynı anlama gelmesini gerektirir.
Bu gereksinim, paletler sekiz renk ve üç durum içerdiğinde göz ardı edilmesi kolaydı. Ekipler 11 kademeli rampalar (Tailwind’in geleneğinde 50–950), sekiz semantik renk, açık ve koyu varyantlar ile iOS, Android ve Web’in sistem renkleriyle birlikte yaşaması gereken marka vurguları sevk etmeye başladığında rahatsızlık vermeye başladı. Birden, “bu teal-500 ile blue-500 aynı açıklıkta mı” sorusu bir sanat yönetimi lüksü değil, gerçek bir mühendislik sorunu hâline geldi.
CSS 3’ten bu yana yük taşıyıcı model olan HSL bunu yanıtlayamadı. Aynı L değerine sahip iki HSL rengi, algılanan parlaklıkta dramatik biçimde farklı görünebilir. lightness: %50 değerindeki saf bir HSL sarısı, aynı açıklıktaki saf bir HSL mavisinden çok daha parlak görünür. Gözleriniz sarı ile maviyi eşit algılamaz; HSL, seçiciler için sezgisel olacak şekilde tasarlandı, rampalar için algısal olarak tutarlı olacak şekilde değil. 2023 itibarıyla, bir avuç rengin ötesine ölçeklenen her tasarım sistemi bunu özel karıştırma betikleri veya elle ayarlanmış geçersiz kılmalarla yamamaya çalışıyordu.
İhtiyacımız olan, L’nin gerçekten “bir insanın bildireceği algılanan parlaklık” anlamına geldiği ve renk tonunu döndürmenin veya doygunluğu azaltmanın görünmez bir biçimde parlaklığı yan etki olarak değiştirmediği bir renk modeliydi. Bu model akademik renk biliminde mevcuttu — yalnızca henüz CSS’e ulaşmamıştı.
HSL sorunu, somut olarak
Şunları bir tarayıcıya bırakın ve yan yana inceleyin:
.a { background: hsl(60 100% 50%); } /* yellow */
.b { background: hsl(240 100% 50%); } /* blue */
.c { background: hsl(120 100% 50%); } /* green */
Üçü de L: %50 değerine sahip. Hiçbiri aynı açıklığa sahipmiş gibi görünmüyor. Sarı neredeyse yakar; mavi, beyaz bir sayfanın üzerinde neredeyse siyah okunur; yeşil aralarında oturur. L’ye %10 ekleyerek bir üzerine gelme durumu oluşturursanız, sarının üzerine gelme rengi neredeyse görünmezken mavinin üzerine gelme rengi dramatik bir kayma olur. Etkileşim ince ayarınız, tasarımcının başlangıçta seçtiği renk tonuna bağlı kalır.
Bu, HSL’de bir hata değildir. HSL, 1978’de sayılarla boyama renk seçicileri için tasarlandı; kullanıcıların bir rengi seçmek için renk tonunu, doygunluğu ve (max(R,G,B) + min(R,G,B)) / 2 olarak tanımlanan “açıklığı” kullanması bekleniyordu. Matematiğin insan algısına dair bir kavramı yoktur. HSL’deki açıklık, sRGB kanallarının geometrik bir orta noktasıdır, başka bir şey değil.
Renk ölçümü için uluslararası standartlar kuruluşu olan CIE, bu sorunu 1970’lerden beri biliyordu. Açıklığı insan görüşünün gerçekte yaptığına daha yakın bir şey olarak tanımlayan iki algısal olarak tekdüze uzay olan CIELAB ve CIELUV’u yayımladılar. 1990’larda CIE LAB, baskı, fotoğrafçılık ve renk yönetiminde standart hâline gelmişti. Ancak RGB’ye dönüştürmesi çetrefilli; CSS bunu hiçbir zaman geniş çapta benimsemedi. Web geliştiricileri HSL kullanmaya devam etti, çünkü doğru olduğu için değil, oradaydı diye.
CIE LAB / LCH: akademik düzeltme, kendi sorunlarıyla
CIELAB, bir XYZ tristimulus değerini (insan konilerinin ışığa nasıl yanıt verdiğini modelleyen) alır ve bunu küp kökü ile 2D bir rotasyondan geçirerek üç kanal üretir: L* (açıklık, 0–100), a* (yeşil ↔ kırmızı) ve b* (mavi ↔ sarı). LCH, aynı uzayın kutupsal biçimde ifadesidir: L*, C* (chroma, nötrden uzaklık), H* (renk tonu açısı).
Bu uzaylar ölçülebilir anlamda algısal olarak tekdüzedir. LAB uzayında herhangi bir yönde bir birim adım olan ΔE’si 1, eğitimli bir gözlemcinin algılayabileceği yaklaşık en küçük renk farkıdır. Baskı ve baskı öncesi iş akışları onlarca yıldır LAB ve LCH üzerinde çalışmaktadır.
Peki CSS neden LCH’yi benimseyip yola devam etmedi?
İki neden. Birincisi, CIE LAB belirli bir görüntüleme koşuluna karşı kalibre edildi (D50 aydınlatma altında 2° standart gözlemci); yüzey yansıması için optimize edildi, emisyonlu ekranlar için değil. Ekranlarda algısal tekdüzeliği kayar — LAB’da “eşit derecede parlak” olan renkler her zaman bir telefonda eşit parlak görünmez. İkincisi, LCH renk gamı tuhaftır. LAB’ın iyi tanımladığı ancak yaygın ekran renk gamlarının dışında kalan görünür renkler vardır ve LCH’den sRGB’ye eşleme zaman zaman renk tonu kaymaları üretir (chroma’sını azalttığınızda maviniz hafifçe morarır). Tasarım sistemi çalışması için her ikisi de kabul edilemez.
CSS Color 4, 2021’de lab() ve lch() ekledi ve bunlar modern tarayıcılarda çalışır. Ancak emisyonlu ekranlarda tutarlı tonal rampalar oluşturmanın belirli sorunu için topluluk arayışına devam etti.
OKLAB / OKLCH: Ottosson’un 2020 içgörüsü
Aralık 2020’de İsveçli bir renk mühendisi olan Björn Ottosson, “Görüntü işleme için algısal bir renk uzayı” başlıklı bir makale yayımladı. Makale küçüktü: üç kısa matris, bir küp kökü adımı, kalibrasyon tabloları yok, telifli referans verisi yok. Ottosson, mevcut IPT ve CAM16-UCS renk modellerini — iyi özelliklere sahip ancak matematiği kötü olan akademik uzayları — aldı ve doğrusal-ışık XYZ tristimulus değerleri üzerinde sıradan matris çarpımlarını kullanarak algısal davranışlarını yaklaşık biçimde sağlayan daha basit bir uzay türetti.
Bunu OKLAB olarak adlandırdı. Kutupsal biçimi OKLCH’dir.
OKLCH’yi özel kılan yenilik değildir — amaca uygunluğudur. Üç özellik birlikte:
- OKLCH’de açıklık gerçekten algısaldır.
L: 0.7değerindeki saf bir sarı ileL: 0.7değerindeki saf bir mavi, kalibre edilmiş bir ekranda aynı parlaklığa sahipmiş gibi görünür.L + 0.05olarak tanımlanan üzerine gelme durumları, paletin tamamında görsel olarak eşdeğer kaymalar üretir. - Chroma değişiklikleri altında renk tonu korunur.
oklch(0.7 0.2 30)’un C değerinioklch(0.7 0.1 30)’a düşürürseniz, renk tonu yerinde kalır. LCH’de aynı işlem genellikle gözle görülür bir renk tonu kayması getirir. OKLCH’de, bir marka renginin soluk bir varyantını oluşturmak için chroma’yı düzleştirebilirsiniz; renk yanlışlıkla başka bir renk tonuna kaymaz. - Matematik ucuzdur. İki matris çarpımı ve bir küp kökü. 30 satırlık bir JavaScript işleviyle uygulanabilir. Arama tabloları yok, cihaz başına kalibrasyon yok, lisans kaygısı yok.
Bu kombinasyon, OKLCH’yi gerçek CSS’te kullanılabilir kıldı. W3C, 2022’de CSS Color 4’e oklch() ekledi. Chrome 111, 2023’te bunu gönderdi. 2024 ortasına kadar her evergreen tarayıcı destekledi. Tailwind v4, aynı yıl bunu varsayılan palet biçimi yaptı.
Matematik: çözümlenmiş bir HEX → OKLCH dönüşümü
#3b82f6’yı — Tailwind’in blue-500’ünü — OKLCH’ye adım adım inceleyelim. Bu, Renk Dönüştürücü aracı ve HEX’ten OKLCH’ye maçası’nın her tuş vuruşunda çalıştırdığı aynı matematiktir. Kaputun altında ne olduğunu bilmek, bir sonraki bölümdeki tuzakların daha anlaşılır olmasını sağlar.
1. Adım: Hex’ten sRGB’ye. Altı haneli hex’i üç çifte bölün ve her birini 255’e bölün.
const r = 0x3b / 255; // 0.231
const g = 0x82 / 255; // 0.510
const b = 0xf6 / 255; // 0.965
Bunlar gamma kodlanmış sRGB değerleridir: görüntü dosyanızın depoladığı kanal değerleri; monitörlerin ışığı nasıl yaydığını telafi etmek için içine pişirilmiş doğrusal olmayan bir eğri ile.
2. Adım: sRGB’den doğrusal sRGB’ye. Gamma eğrisini kaldırın, böylece doğrusal-ışık kanal değerlerimiz olur. CSS Color 4 §11.2’den standart parçalı dönüşüm:
const linear = (v) => v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
const [lr, lg, lb] = [r, g, b].map(linear);
// lr ≈ 0.045, lg ≈ 0.220, lb ≈ 0.923
3. Adım: Doğrusal sRGB’den XYZ D65’e. CSS Color 4 §15.1’de tanımlanan standart bir matris çarpımı:
const x = 0.4124564 * lr + 0.3575761 * lg + 0.1804375 * lb;
const y = 0.2126729 * lr + 0.7151522 * lg + 0.0721750 * lb;
const z = 0.0193339 * lr + 0.1191920 * lg + 0.9503041 * lb;
// x ≈ 0.265, y ≈ 0.231, z ≈ 0.927
Bu, kanonik XYZ tristimulus temsilidir — “bu renk insan koni yanıtı açısından hangi dalga boylarındadır” biçimidir.
4. Adım: XYZ’den LMS’e. Ottosson’un ilk matrisi XYZ’yi OKLAB için ayarlanmış bir uzun/orta/kısa koni-temelleri uzayına eşler:
const lms = [
0.8189330101 * x + 0.3618667424 * y - 0.1288597137 * z,
0.0329845436 * x + 0.9293118715 * y + 0.0361456387 * z,
0.0482003018 * x + 0.2643662691 * y + 0.6338517070 * z,
];
5. Adım: LMS değerlerinin küp kökü. Bu, algısal sıkıştırma adımıdır — CIE LAB’daki küp köküne benzer:
const lms_ = lms.map(Math.cbrt);
6. Adım: LMS’den OKLAB’a. Ottosson’un ikinci matrisi:
const L = 0.2104542553 * lms_[0] + 0.7936177850 * lms_[1] - 0.0040720468 * lms_[2];
const a = 1.9779984951 * lms_[0] - 2.4285922050 * lms_[1] + 0.4505937099 * lms_[2];
const b_ = 0.0259040371 * lms_[0] + 0.7827717662 * lms_[1] - 0.8086757660 * lms_[2];
// L ≈ 0.629, a ≈ -0.022, b_ ≈ -0.191
İşte OKLAB. Açıklık kanalı L ≈ 0.629, gözün bu mavi için “%60 parlaklık” olarak algıladığı değerdir.
7. Adım: OKLAB’dan OKLCH’ye (Kartezyen’den kutupsala).
const C = Math.sqrt(a * a + b_ * b_); // 0.193
const H = (Math.atan2(b_, a) * 180 / Math.PI + 360) % 360; // 263.4
// → oklch(0.629 0.193 263.4)
Sonuç olarak #3b82f6, oklch(0.629 0.193 263.4)’tür. Açıklık 0.629, chroma 0.193, renk tonu 263.4°.
Bu renkten yalnızca L’yi değiştirerek (11 adımda 0.95’ten 0.15’e), C ve H’yi sabit tutarak bir 50–950 rampası oluşturursanız, her tonun görsel olarak aynı renk tonunda olduğu ve açıklığı tekdüze biçimde solduran bir palet elde edersiniz. Aynısını HSL’de yapın; koyu tonlar mora kayar, açık tonlar griye döner. İşte kazanım budur.
Display P3 + Rec2020: OKLCH neden geniş renk gamını açar
OKLCH sınırsızdır. HSL (sRGB ile sınırlı) ve hatta LCH’nin (yansıtıcı yüzeyler için kalibre edilmiş) aksine, OKLCH’nin örtük bir renk gamı yoktur. oklch(0.7 0.25 30) yazabilir ve Display P3’ün renk hacminin içinde ancak sRGB’nin dışında oturan canlı bir kırmızı üretebilirsiniz. Yeni bir iPhone veya MacBook’ta işlenir. Eski bir monitörde, tarayıcı bunu otomatik olarak en yakın sRGB temsiline hizalar.
Bu önemlidir çünkü Apple, Samsung ve W3C, 2010’ların sonlarını geniş renk gamlı donanım göndererek geçirdi. 14”/16” mini-LED MacBook Pro varsayılan olarak P3 gönderir. iPhone 15 Pro, Safari’de Display P3 işler. Android amiral gemileri Rec2020 panelleri gönderir. 2025 itibarıyla, tasarım sistemi trafiğinin önemli bir bölümü, HSL/sRGB’nin kesinlikle ifade edemediği renkleri gösterebilen geniş renk gamlı donanımdadır.
OKLCH, ayrı bir @media (color-gamut: p3) bildirimi yapıştırmadan bu renkleri yazmanıza olanak tanır. Tarayıcı geri dönüşü halleder. Tasarım sisteminizde, kutudan çıktığı gibi “cihazın işleyebileceği en parlak kırmızıyı kullan” özelliği hazır bulunur.
Bu, OKLCH’nin tasarım belirteçleri için neden doğru biçim olduğunun da nedenidir. OKLCH’deki bir --brand değişkeni, niyetin cihazdan bağımsız bir tanımıdır. Tarayıcı, kullanıcının elindeki ekrana ne işleneceğini hesaplar; kodunuz CSS, SwiftUI (Display P3 Color’ı yerel olarak destekler), Android Compose (Rec2020 bilincine sahip) ve Flutter arasında taşınabilir kalır.
Tailwind v4 ve tasarım belirteci devrimi
2024’te yayımlanan Tailwind v4, OKLCH’yi araştırmadan endüstri varsayılanına çeviren dönüm noktasıydı. Tailwind’in yazarları üç görüşlü tercih yaptı:
- Varsayılan palet OKLCH’dir. Slate, gray, zinc, neutral, stone — her Tailwind rengi kaynakta
oklch()ile tanımlanmıştır. 50–950 rampaları yapısı gereği algısal açıklıkta tekdüzedir. - Özel temalar OKLCH literal’leriyle
@themeblokları kullanır. Marka renklerioklch()belirteçleri olarak tanımlanır; aşağı akıştaki yardımcılar (bg-brand-500,text-brand-300) üretilir. - Geri dönüş seremonisi gerekmez. OKLCH desteği olmayan tarayıcılar Tailwind v4’ün belgelenmiş baz çizgisinin altındadır.
Son karar, benimsemeyi ayağa kurşun sıkmayan bir seçim hâline getirenidi. Daha 2023’te tasarımcılar, eski Safari sürümlerinin bozulmaması için her rengin hem oklch() hem de hsl() sürümünü göndermek zorundaydı. Tailwind v4 ile baz çizgisi 2023 veya daha yeni tarayıcılardır ve OKLCH her yerde çalışır.
shadcn/ui’nin tema üreticisi aynı kalıbı izler: marka renginizi girin, bir OKLCH rampası çıkar. Vercel’in tasarım sistemi semantik renkleri için OKLCH kullanır. Radix Themes’in renk ölçekleri OKLCH ile tanımlanır. Topluluk yakınsadı.
Pratik geçiş: HEX paleti → OKLCH paleti
Bugün hex tabanlı bir paletiniz varsa geçiş mekaniktir. İşte tarif:
1. Rampa yapınıza karar verin. Tailwind’in 50–950’si (11 durak), fiili varsayılandır; belirli bir nedeniniz yoksa izlemeye değer. L = 0.97, 0.93, 0.86, 0.76, 0.63, 0.50, 0.42, 0.34, 0.26, 0.18, 0.10 duraklarında pürüzsüz bir algısal rampa elde edilir.
2. Marka hex’inizi OKLCH’ye dönüştürün. Renk Dönüştürücü veya HEX’ten OKLCH’ye aracını kullanın. oklch(0.629 0.193 263.4) gibi bir üçlü elde edersiniz. H değerine dikkat edin — bu marka renk tonunuzdur.
3. C ve H’yi sabit tutun; L’yi değiştirin. Şunu yayarak rampayı oluşturun:
--brand-50: oklch(0.97 0.193 263.4);
--brand-100: oklch(0.93 0.193 263.4);
...
--brand-950: oklch(0.10 0.193 263.4);
4. Aşırı durakları ayarlayın. Çok düşük L’de (≤ 0.20) ve çok yüksek L’de (≥ 0.95), yüksek chroma değerleri sRGB dışında kalır. O duraklar için C’yi azaltın veya tarayıcının otomatik hizalamasını kabul edin. Tailwind’in varsayılanları her iki uca doğru chroma’yı azaltır — o kalıbı kopyalayın.
5. Semantik takma adlar tanımlayın. --surface: var(--brand-50), --surface-elevated: var(--brand-100), --text-primary: var(--brand-900) vb. Artık tasarım belirteçleriniz renk olarak değil, niyet olarak okunur.
6. Kontrastı doğrulayın. Her bir --text-* çiftinin her bir --surface-* çiftine karşı erişilebilirlik çıtanızı karşıladığını doğrulamak için APCA Lc veya WCAG 2 kontrast oranlarını kullanın. OKLCH L algısal olduğundan, kontrast matematiği HSL’de olduğundan daha güvenilirdir.
Bu geçişi 60 renklik bir eski palet üzerinde yürüten bir ekip tipik olarak tek bir öğleden sonrada 30–40 belirteçten oluşan daha küçük, daha tekdüze bir OKLCH paleti bitirir. Yeni palet daha küçük gönderilir, daha küçük CSS üretir ve ek bir ayar yapılmadan üzerine gelme ve devre dışı durumlarında görsel olarak daha iyi tonal hareket üretir.
Tuzaklar ve bunlarla nasıl başa çıkılır
Başlarken bilinmesi gereken birkaç şey var:
Renk gamı dışında uyarıları. Bazı OKLCH değerleri Display P3 veya sRGB dışında kalır. Modern tarayıcılar en yakın geçerli renge hizalamayı otomatik olarak yönetir, ancak hizalama kayıplıdır: oklch(0.7 0.25 30) değeriniz yazdığınızdan biraz daha az doygun işlenebilir. Renk Dönüştürücü’nün renk gamı satırı gibi araçlar renginizin sRGB güvenli mi, P3 güvenli mi yoksa Rec2020 güvenli mi olduğunu söyler ve yazdığınız şeyin gördüğünüz şey olması için tek tıklamayla sRGB’ye hizalama sunar.
Alt piksel chroma tuhaflığı. OKLCH chroma’sı sınırsızdır, ancak görünür renkler için yararlı aralık kabaca 0 ile ~0.4 arasıdır. 0.4’ün üzerindeki değerler yalnızca monokromatik lazer ışığıyla elde edilebilir — bir ekranın işleyebileceği fiziksel renkler değildir. Renk Dönüştürücü bu nedenle chroma kaydırıcısını 0.4’te sınırlar; bunun ötesindeki değerler gerçek bir ekranda algılanabilir bir fark üretmez.
Tarayıcı desteği, 2025 sürümü. Chrome 111+, Safari 15.4+, Firefox 113+ tümü oklch() desteğini yerel olarak sunar. 2023 öncesi tarayıcılar desteklemez. Eski IE/Edge veya eski mobil Safari’yi (kitlenize bağlı olarak trafiğin %1–3’ü) desteklemeniz gerekiyorsa, bir OKLCH bildirimini @supports (color: oklch(0 0 0)) kullanarak bir hex geri dönüşüyle eşleştirebilirsiniz — ancak 2025’te gönderilen tasarım sistemi belirteçleri için geri dönüşün maliyeti genellikle eski yarara ağır basar.
Hex kodlarının kalıcılığı. OKLCH, tasarım sistemi niyeti içindir. CMS’niz eski nedenlerle (e-posta imzaları, Office belgeleri, marka varlığı kontrol listeleri) yine de bir hex değerine ihtiyaç duyabilir. Her OKLCH belirteci için sRGB hizalanmış hex’i yayan üretilmiş bir arama tablosu tutun, ancak hex’te yazmayın.
OKLCH ile OKLAB’ı karıştırmayın. OKLAB dikdörtgen biçimdir (L, a, b kanalları); OKLCH aynı renk uzayının kutupsal biçimidir (L, C, H). Aralarında bir Kartezyen↔kutupsal adımıyla geçiş yapılır. Belirteçler için OKLCH kullanın (daha okunabilir, rampalamak daha kolay); renkleri ara değerlemeniz veya karıştırmanız gerekirse dahili olarak OKLAB kullanın.
Kendi paletinizde deneyin
Burada anlatılanları çalışırken görmenin en hızlı yolu, bir marka hex’ini Renk Dönüştürücü’ye bırakmaktır. HEX alanına marka renginizi yazın ve OKLCH çıktısını okuyun. Ardından OKLCH tarafındaki kaydırıcıları hareket ettirin ve chroma’yı azalttıkça aynı renk tonunun aynı renk tonu olarak kaldığını, renk tonunu döndürdükçe aynı açıklığın aynı açıklık olarak kaldığını izleyin. Birkaç dakika sonra HSL’nin tonal rampalar için neden her zaman yanlış araç olduğunu ve neden her ciddi tasarım sisteminin bunu geride bıraktığını sezgisel olarak kavrayacaksınız.
Belirli HEX-to-OKLCH dönüşümü için HEX’ten OKLCH’ye aracını kullanın — bu makaledeki aynı matematik, bir ek avantajla: renk gamı sınıflandırmasını (sRGB / Display P3 / Rec2020) gösterir, böylece marka renklerinizin hangilerinin her yerde güvenli, hangilerinin tam olarak işlenmesi için geniş renk gamlı bir cihaza ihtiyaç duyduğunu bilirsiniz.
İşte OKLCH bu. Geçişe değer. İyi yapıldığında bir daha asla hsl() yazmayacaksınız.