Metin Stilleri: text-decoration Ailesi, text-underline-offset, text-shadow, letter-spacing, text-indent ve vertical-align
Okunur bir link alt çizgisi longhand'lerle yazılır (text-decoration-thickness, text-underline-offset em ile), letter-spacing em ya da yüzdeyle verilir, inline ikon ise vertical-align ile rakamlara hizalanır. Bookstore kataloğunda text-decoration shorthand'inin kalınlığı sıfırlamasını, em'in px olarak kalıtılmasını, lang="tr" ile text-transform'u, img altındaki boşluğu ve initial-letter desteğini Chromium ve WebKit'te ölçüyoruz.
Sürüm notu: Bu yazı CSS Text Module Level 3/4 ve CSS Text Decoration Level 4 spesifikasyonlarına ve Baseline'ın Kasım 2026 durumuna (web-features 3.46.0) göre yazıldı; örnekler Chromium 158 ve WebKit 27.2 (Playwright 1.65) ile denenmiştir, Firefox desteği web-features ve MDN'in (Mozilla'nın web dokümantasyon projesi) browser-compat-data 8.1.9 verisinden alınmıştır.
Önce Tahmin Et
Önceki yazıda sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) için font'u seçtik. Şimdi metnin kendisini biçimlendiriyoruz. Katalogdaki "New" etiketinin harf aralığını em ile verdik, kitap başlıklarındaki link'lere de kalın bir alt çizgi yazdık:
.badge, .badge-pct, .badge-none { font-size: 12px; text-transform: uppercase; }
.badge { letter-spacing: 0.1em; }
.badge-pct { letter-spacing: 10%; }
.big { font-size: 24px; }
.link { text-decoration-thickness: 2px; text-underline-offset: 0.2em; }
.link.reset { text-decoration: underline; }
<p class="badge">New <span class="big">New</span></p>
<p class="badge-pct">New <span class="big">New</span></p>
<p class="badge-none"><span class="big">New</span></p>
<p><a class="link" href="#">Dune</a> <a class="link reset" href="#">Emma</a></p>
İlk iki paragrafta 12px'lik etiketin içinde 24px'lik bir span var. Span'in letter-spacing'i ne olur: 1.2px mi, 2.4px mi? 10% yazdığımızda sonuç değişir mi? Son satırda .link.reset önce text-decoration-thickness: 2px alıyor, sonra text-decoration: underline shorthand'i. Emma'nın alt çizgisi kaç piksel kalınlıkta çizilir?
text-decoration Ailesi ve Okunur Bir Link Alt Çizgisi
text-decoration bir shorthand ve dört longhand'i var: text-decoration-line (underline, overline, line-through), text-decoration-style (solid, double, dotted, dashed, wavy), text-decoration-color ve text-decoration-thickness. Shorthand'e dahil olmayan iki property daha var: çizginin metinden uzaklığını veren text-underline-offset ve çizginin harflerin aşağı uzanan kısımlarında (g, p, y gibi) kesilip kesilmeyeceğini belirleyen text-decoration-skip-ink. Lab'i iki motorda ölçtüm:
node scripts/02-text.mjs
--- chromium
.link thickness 2px offset 3.2px
.link.reset thickness auto offset 3.2px
hover: thickness 3px, box 95.5x19 → 95.5x19
--- webkit
.link thickness 2px offset 3.2px
.link.reset thickness auto offset 3.2px
hover: thickness 3px, box 95.5x18.1796875 → 95.5x18.1796875
Giriş sorusunun cevabı: Emma'nın kalınlığı auto, yani 2px kayboldu. Shorthand, yazmadığın longhand'leri başlangıç değerine çeker ve text-decoration-thickness artık shorthand'in parçası. text-underline-offset ise shorthand'e dahil olmadığı için 3.2px'te kaldı. Bir ayrıntı: Safari 26.2'den önce Safari'nin shorthand'i kalınlığı içermiyordu; bu yüzden aynı kod eski Safari'de 2px çizgiyi korur, diğer motorlarda sıfırlar. Tutarlı sonuç için link stillerinde longhand yaz.
Bookstore'un base stilinde link'ler şöyle:
a {
color: var(--color-link);
text-decoration-line: underline;
text-decoration-thickness: 1px;
text-underline-offset: 0.2em;
text-decoration-skip-ink: auto;
}
a:hover {
/* renk değil kalınlık: kutu yerinden oynamaz */
text-decoration-thickness: 2px;
}
text-underline-offset: 0.2em çizgiyi harflerden biraz uzaklaştırır ve Türkçe'deki ç ve ş gibi altı işaretli harflerin çizgiye değmesini azaltır. Değeri em ile vermek, font büyüdüğünde boşluğun da büyümesini sağlar; ölçümde 16px font için 3.2px'e hesaplandı. text-decoration-skip-ink: auto zaten varsayılan, ama base stilde açıkça yazmak niyeti belli ediyor.
Hover'da renk değiştirmek yerine çizgiyi kalınlaştırıyoruz. Ölçümde hover sonrası kalınlık 3px'e çıktı, ama link'in kutusu iki motorda da aynı kaldı: çizgi metnin box'ına eklenmiyor, sadece üstüne çiziliyor. Bu yüzden kalınlık değişimi layout'u kaydırmaz. Katalog kartında başlıklar link, ama metinle aynı renkte görünmesini istiyoruz. Alt çizgiyi kaldırmak yerine rengini kenarlık rengine çekip hover'da currentColor'a getiriyoruz:
.book-card h2 a {
color: inherit;
text-decoration-color: var(--color-border);
}
.book-card h2 a:hover { text-decoration-color: currentColor; }
node scripts/02-pages.mjs
--- chromium
badge: font-size 12px, letter-spacing 0.96px
price icon center -6.0px, digits center -5.8px (from baseline)
initial-letter: true, -webkit-initial-letter: false
card link hover: 2px rgb(31, 41, 55), box moved: false
--- webkit
badge: font-size 12px, letter-spacing 0.96px
price icon center -6.0px, digits center -5.8px (from baseline)
initial-letter: false, -webkit-initial-letter: true
card link hover: 2px rgb(31, 41, 55), box moved: false
Son satırlarda kart link'inin hover'da 2px kalınlık ve metin rengi aldığı, kutunun ise kımıldamadığı görülüyor. Link'i metinden ayıran şeyin yalnız renk olmaması gerekir; alt çizgi, renk farkını göremeyen kullanıcılar için de çalışan bir ipucu. Kuralın ayrıntısı ve contrast eşikleri Accessibility (a11y) serisinin konusu.
letter-spacing ve word-spacing: em, px ya da Yüzde
Büyük harfli küçük etiketlerde harfler birbirine yapışık görünür, bu yüzden biraz letter-spacing eklenir. Giriş sorusunun ölçümü:
.badge font-size 12px letter-spacing 1.2px
.badge-pct font-size 12px letter-spacing 10%
.badge .big font-size 24px letter-spacing 1.2px width 61.1px
.badge-pct .big font-size 24px letter-spacing 10% width 64.7px
.badge-none .big font-size 24px letter-spacing normal width 57.5px
0.1em, etiketin kendi 12px font'una göre 1.2px'e hesaplanıyor ve çocuk element'e bu px değeri kalıtılıyor. 24px'lik span de 1.2px alıyor: genişlik harf aralığı olmayan hâlden (57.5px) yalnız 3.6px fazla. Kalıtılan şey em değil, hesaplanmış uzunluk. 10% ise yüzde olarak kalıtılıyor ve her element kendi font boyutuna göre çözüyor: span 2.4px alıyor, genişlik 7.2px artıyor. Yüzdeli letter-spacing browser-compat-data'ya göre Chrome 145 (Şubat 2026), Firefox 133 ve Safari 17.4'ten beri üç motorda da var; web-features 3.46 bu alt özelliği ayrı bir Baseline kaydı olarak listelemiyor.
Pratik kural: letter-spacing'i px ile verme. Font büyüdüğünde aralık aynı kalır ve büyük başlıklarda neredeyse hiç görünmez, küçük metinde de fazla gelir. Etiketin kendisinde em kullanmak yeterli; iç içe font boyutları olan bir component'te yüzdeyi tercih et. Bookstore'daki etiket:
.badge {
display: inline-block;
padding: 0.125rem 0.5rem;
border-radius: 0.25rem;
background: var(--color-accent);
color: #ffffff;
font-size: 0.75rem;
font-weight: 700;
/* px değil em: badge'in kendi font-size'ıyla büyür */
letter-spacing: 0.08em;
text-transform: uppercase;
}
Katalog sayfasında etiketin hesaplanan değeri 0.96px (12px'in %8'i). word-spacing aynı kurallara uyar ama gündelik arayüzde nadiren gerekir; en sık kullanıldığı yer, harf aralığı açılmış büyük harfli bir başlıkta kelimeler arasının harf aralığıyla karışmaması.
text-transform, text-align ve text-indent
Etiketin içindeki metin markup'ta "New" yazıyor; büyük harfe text-transform: uppercase çeviriyor. Bu dönüşüm element'in lang değerine göre yapılır:
<span lang="en" style="text-transform: uppercase">yeni kitap</span> /
<span lang="tr" style="text-transform: uppercase">yeni kitap</span> /
<span lang="tr" style="text-transform: lowercase">ISBN İADE</span>
lang=en uppercase → YENI KITAP
lang=tr uppercase → YENİ KİTAP
lang=tr lowercase → ısbn iade
lang="en" ile "yeni" kelimesi YENI oluyor, lang="tr" ile doğru şekilde YENİ. Ters yönde de aynı kural geçerli: Türkçe sayfada lowercase, ISBN (International Standard Book Number; kitapların uluslararası kimlik numarası) kısaltmasını ısbn yapıyor. Türkçe arayüzde <html lang="tr"> zorunlu; kod, kısaltma ve marka adı gibi dönüşmemesi gereken parçalara ise text-transform uygulama ya da o parçaya kendi lang değerini ver. Büyük harf kullanımının ne zaman tercih edileceği UI/UX Tasarım İlkeleri serisinin Typography yazısında tartışılıyor; burada yalnız mekanizmayı görüyoruz. Ölçüm script'i metni innerText ile okuyor, çünkü innerText text-transform'u uygulanmış hâliyle döndürür; textContent ise markup'taki metni verir.
text-align'da fiziksel left/right yerine mantıksal start/end kullanmak, sayfa sağdan sola bir dile çevrildiğinde hizalamanın kendiliğinden dönmesini sağlar. text-align: justify yazdığında son satır yine başa yaslanır; bunu text-align-last belirler. text-indent ilk satırı içeri alır, hanging anahtar kelimesiyle de tersine çevrilir:
.indent { inline-size: 16em; text-indent: 2em; }
.indent.hanging { text-indent: 2em hanging; }
indent line 1 starts at 32.0px, line 2 at 0.0px
indent hanging line 1 starts at 0.0px, line 2 at 32.0px
Normal girintide ilk satır 32px (2em) içeriden başlıyor. hanging ile ilk satır kenarda kalıyor, sonraki satırlar içeri giriyor; kaynakça listeleri ve kitap künyesi için kullanışlı. hanging ve each-line anahtar kelimeleri Baseline newly available (Mart 2026, Chrome 146 ile).
text-shadow: Syntax, Çoklu Gölge ve Sınırları
text-shadow yatay ve dikey kayma, isteğe bağlı bulanıklık ve renkten oluşur; box-shadow'dan farklı olarak spread ve inset yoktur. Virgülle birden fazla gölge yazılabilir ve ilk yazılan en üstte çizilir:
.shadow { text-shadow: 0 1px 2px rgb(0 0 0 / 0.6), 0 0 8px #fbbf24; }
text-shadow: rgba(0, 0, 0, 0.6) 0px 1px 2px, rgb(251, 191, 36) 0px 0px 8px
Computed değerde renk başa alınmış ve uzunluklar px'e çevrilmiş; DevTools'ta gördüğün biçim bu. İlk gölge harflerin altına ince ve koyu bir gölge koyarak metni arka plandan ayırıyor, ikincisi sıfır kaymalı ve bulanık sarı bir "glow". Görsel bir banner üzerinde metni okunur kılmanın asıl yolu ise arka plana koyu bir gradient katmanı eklemek; bunu dördüncü yazıda kampanya banner'ında yapacağız. text-shadow'u yeterli contrast'ı sağlamak için kullanma: WCAG'nin (Web Content Accessibility Guidelines; web erişilebilirlik kuralları) contrast oranı yalnız metin ve arka plan rengiyle tanımlanır, otomatik araçların çoğu gölgeyi ya hiç hesaba katmaz ya da sonucu belirsiz sayar; ince bir gölge ise düşük çözünürlüklü ekranlarda metni daha da bulanık gösterir.
vertical-align: İkon Hizası ve img Altındaki Boşluk
vertical-align yalnız inline ve tablo hücresi seviyesindeki kutulara uygulanır; bir block'u ortalamak için kullanılmaz (bunun yolu Flexbox ya da Grid, sitedeki CSS serisinin (CSS3: Flexbox, Grid ve Modern CSS) 05 ve 06. yazıları). Inline bir ikon ya da görselde ise en sık iki sorunun kaynağıdır. Birincisi, bir div içindeki svg ya da img'nin altındaki açıklanamayan boşluk:
.icon-box { inline-size: max-content; background: #eee; }
.icon-box svg { inline-size: 24px; block-size: 24px; }
.icon-box.block svg { display: block; }
.icon-box.middle svg { vertical-align: middle; }
--- chromium
icon-box box height 31px (svg 24px)
icon-box block box height 24px (svg 24px)
icon-box middle box height 25.140625px (svg 24px)
--- webkit
icon-box box height 30.40625px (svg 24px)
icon-box block box height 24px (svg 24px)
icon-box middle box height 25.71875px (svg 24px)
24px'lik ikonun kutusu 31px yüksekliğinde. Sebep, inline bir görselin varsayılan olarak metnin baseline'ına oturması: altında, aynı satırda olabilecek g, p, y gibi harflerin aşağı uzanan kısmı için yer ayrılır. vertical-align: middle boşluğu küçültüyor ama sıfırlamıyor (25.1px); görsel tek başınaysa doğru çözüm display: block. Görsel reset'inin geri kalanı (max-inline-size, block-size: auto) bu serinin görseller yazısında.
İkinci sorun, fiyatın yanındaki ikonun metne göre yukarıda ya da aşağıda durması. 20px font ile dört değeri denedim; değerler ikonun üst ve alt kenarının baseline'a uzaklığı:
.price { font: 700 20px/1.5 system-ui, sans-serif; }
.price svg { inline-size: 1em; block-size: 1em; }
.price .baseline svg { vertical-align: baseline; }
.price .middle svg { vertical-align: middle; }
.price .text-bottom svg { vertical-align: text-bottom; }
.price .em svg { vertical-align: -0.125em; }
.probe { display: inline-block; inline-size: 0; block-size: 0; }
baseline icon top -20.0px bottom 0.0px from baseline
middle icon top -15.3px bottom 4.7px from baseline
text-bottom icon top -16.0px bottom 4.0px from baseline
em icon top -17.5px bottom 2.5px from baseline
baseline'da ikonun alt kenarı baseline'da, yani ikon rakamlardan daha yukarı uzanıyor. middle, ikonun ortasını baseline'ın x yüksekliğinin yarısı kadar yukarısına hizalar; rakamlar x yüksekliğinden uzun olduğu için ikon biraz aşağıda kalır. text-bottom alt kenarı font'un en alt noktasına indirir. -0.125em ise 1em'lik ikonu 2.5px aşağı kaydırıp ortasını baseline'ın 7.5px üstüne getirir. Bookstore'un katalog kartında aynı değeri kullandık:
.price svg {
inline-size: 1em;
block-size: 1em;
/* 1em'lik ikonu rakamların ortasına hizalar */
vertical-align: -0.125em;
fill: currentColor;
}
Yukarıdaki 02-pages.mjs çıktısında ikonun ortası baseline'ın 6.0px, "420" rakamlarının görünen ortası 5.8px üstünde; fark 0.2px. Rakamların yüksekliğini canvas'ın measureText method'u ile aynı font'tan okudum. -0.125em her font için doğru değil; bu ölçümde system-ui'nin rakam yüksekliği yaklaşık 0.73em olduğu için ortaya yakın düşüyor; farklı bir font'ta aynı ölçümle değeri yeniden kontrol et. Tablo hücrelerinde ise vertical-align: middle beklendiği gibi hücre içeriğini dikeyde ortalar; tablo stilleri serinin son yazısında.
::first-letter ve initial-letter
::first-letter bir block'un ilk harfini seçer ve font, renk, margin gibi property'leri kabul eder. Kitap detayındaki özet paragrafının ilk harfini büyük bir başlık harfine çeviriyoruz:
.book-summary::first-letter {
font-family: var(--font-display);
font-weight: 700;
color: var(--color-accent);
}
@supports (initial-letter: 2) {
.book-summary::first-letter {
initial-letter: 2;
margin-inline-end: 0.5rem;
}
}
initial-letter: 2 harfi iki satır yüksekliğinde büyütüp satırların içine oturtur; elle float ve font-size hesabı yapmaya gerek kalmaz. Ama Baseline değil: Chrome 110'dan beri prefix'siz var, Safari yalnız -webkit-initial-letter ile ve kısmen destekliyor, Firefox'ta yok. Ölçümde Chromium prefix'siz değeri, WebKit yalnız prefix'li değeri kabul etti. Bu yüzden @supports içine koyduk: desteklemeyen tarayıcı ilk harfi renkli ve kalın gösteriyor ama satırları bozmuyor.
Üretimde Dikkat
Bu ailenin property'leri ucuz: layout'u değiştirmeyen text-decoration-thickness, text-decoration-color ve text-shadow yalnız repaint gerektirir. letter-spacing, word-spacing ve text-transform ise metnin genişliğini değiştirdiği için layout tetikler; hover'da animasyonla değiştirmek satırların kaymasına yol açar. Animasyonu ölçmek ve compositor dostu property'leri seçmek CSS3 serisinin 13. yazısında anlatılıyor.
text-transform yalnız görünümü değiştirir. Büyük harfe çevrilmiş metnin ekran okuyucuda nasıl seslendirildiği ve kopyalanınca hangi hâliyle panoya gittiği tarayıcıya ve ekran okuyucuya göre değişir. Bir veriyi (ISBN, kupon kodu) büyük harfle göstermen gerekiyorsa veriyi kaynağında büyük harfle üret, CSS'e bırakma.
Link ve ikon hizası gibi görsel kararları otomatik test etmek zor görünür, ama bu yazıdaki script'ler gibi computed değerleri ve kutu ölçülerini okuyan küçük bir Playwright testi, bir refactoring'in hover'da kutuyu kaydırıp kaydırmadığını yakalar.
Sık Düşülen Tuzaklar
Birincisi, letter-spacing'i px ile vermek. Font büyüyünce aralık aynı kalır; em ile verip de iç içe farklı font boyutları olan bir component'te kalıtılan px değerine takılmak da aynı hatanın ikinci hâli. Ölçtüğümüz gibi yüzde bu iki sorunu da çözer.
İkincisi, okunmayan bir metni text-shadow ile kurtarmaya çalışmak. Contrast oranı metin ve arka plan rengiyle hesaplanır; gölge bu hesaba girmez. Arka planı değiştir ya da metnin arkasına yarı saydam bir katman koy.
Üçüncüsü, link'lerin alt çizgisini tamamen kaldırıp onları yalnız renkle ayırmak. Kart başlığında yaptığımız gibi alt çizgiyi silik bir renkte tutmak, link olduğunu belli ederken görsel olarak hafif kalır.
Dördüncüsü, hover'da border-bottom eklemek ya da font-weight'i değiştirmek. İkisi de kutunun boyutunu değiştirir ve komşu metni iter; text-decoration-thickness ve text-decoration-color bunu yapmaz.
Baseline Durumu
node scripts/baseline.mjs text-decoration text-decoration-skip-ink text-underline-offset letter-spacing text-shadow vertical-align text-transform text-align-last text-indent-hanging first-letter initial-letter
text-decoration widely 2018-01-29 Chrome 1, Firefox 1, Safari 1, iOS 1
text-decoration-skip-ink widely 2024-09-14 Chrome 64, Firefox 70, Safari 15.4, iOS 15.4
text-underline-offset widely 2023-05-19 Chrome 87, Firefox 70, Safari 12.1, iOS 12.2
letter-spacing widely 2018-01-29 Chrome 1, Firefox 1, Safari 1, iOS 1
text-shadow widely 2018-01-29 Chrome 2, Firefox 3.5, Safari 1.1, iOS 1
vertical-align widely 2018-01-29 Chrome 1, Firefox 1, Safari 1, iOS 1
text-transform widely 2018-01-29 Chrome 1, Firefox 1, Safari 1, iOS 1
text-align-last widely 2025-03-12 Chrome 47, Firefox 49, Safari 16, iOS 16
text-indent-hanging newly 2026-03-13 Chrome 146, Firefox 121, Safari 15, iOS 15
first-letter widely 2018-01-29 Chrome 1, Firefox 1, Safari 1, iOS 1
initial-letter not Baseline Chrome 110, Firefox -, Safari -, iOS -
| Özellik | Durum | Not |
|---|---|---|
text-decoration longhand'leri, text-underline-offset, text-decoration-skip-ink | Widely available | Shorthand'in kalınlığı içermesi Safari'de 26.2'den beri |
letter-spacing (uzunluk) | Widely available | Yüzde değer: Chrome 145, Firefox 133, Safari 17.4 |
text-shadow, vertical-align, text-transform | Widely available | text-transform lang'a göre çalışır |
text-align-last | Widely available | 2025-03'ten beri |
text-indent ile hanging ve each-line | Newly available | 2026-03'ten beri |
initial-letter | Baseline değil | Chrome; Safari yalnız -webkit- ile; @supports içinde kullan |
Küçük Bir Deneme
★☆☆ Katalogdaki etiketin letter-spacing'ini 8% yap ve etiketin içine 16px'lik bir span ekle. 02-pages.mjs'e span'in computed değerini yazdıran bir satır ekleyip ölç.
★★☆ Fiyat ikonunu Fraunces ile yazılmış bir başlığın yanına koy (örneğin kitap detayındaki h1'in yanına bir favori ikonu). 02-pages.mjs'teki ölçümü bu başlık için tekrarla ve -0.125em'in bu font için doğru değer olup olmadığını bul.
★★★ initial-letter için Safari'nin prefix'li değerini de kapsayan ikinci bir @supports bloğu yaz ve WebKit'te ilk harfin iki satır yüksekliğe oturduğunu, harfin ve paragrafın kutularını ölçerek doğrula.
Bir sonraki yazıda metnin nerede ve nasıl kırıldığına bakıyoruz: white-space ve longhand'leri, uzun ISBN ve URL'ler için overflow-wrap ile word-break farkı, hyphens'ın Türkçe'deki durumu, text-wrap: balance ve pretty, ve kitap açıklamasını üç satırda kesen line clamp.