Box Model, Sizing ve Logical Property'ler: box-sizing, Margin Collapse, Intrinsic Size ve Birimler
box-sizing: border-box ile width kutunun tamamını ölçer; normal akışta alt alta gelen dikey margin'ler toplanmaz, büyük olan kalır, flex ve grid'de ise toplanır. Bookstore'un kitap kartı ve detay sayfasında box model'i, margin collapse'ın olduğu ve olmadığı durumları, min-content/max-content/fit-content'i, aspect-ratio ile kapak oranını, rem/em/ch ve svh/lvh/dvh birimlerini ve RTL'ye hazır logical property'leri Chromium ve WebKit'te ölçüyoruz.
Sürüm notu: Bu yazı CSS Box Model, Box Sizing, Values and Units ve Logical Properties spesifikasyonlarına ve Baseline'ın Kasım 2026 durumuna göre yazıldı; örnekler Chrome 158 ve WebKit 27.2 (Playwright 1.65) ile denenmiştir, Firefox desteği web-features verisinden alınmıştır.
Önce Tahmin Et
Sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı) için kitap kartlarını tasarlarken iki küçük soru çıktı. Birincisi: aşağıdaki iki kutunun ikisine de width: 200px verdik. Ekranda ikisi de 200 piksel mi görünür?
@layer components {
.box { width: 200px; padding: 16px; border: 4px solid #1f2937; }
.content-box { box-sizing: content-box; }
.stack > p { margin-block: 0; }
.stack > .first { margin-block-end: 24px; }
.stack > .second { margin-block-start: 16px; }
.stack > .pull-up { margin-block-start: -8px; }
.stack.as-flex { display: flex; flex-direction: column; }
.promo { background: #fef3c7; }
.promo > h2 { margin-block-start: 32px; }
.promo.flow-root { display: flow-root; }
.shelf { width: 300px; }
.shelf > p { margin: 0; background: #e0f2fe; }
.min { width: min-content; }
.max { width: max-content; }
.fit { width: fit-content; }
}
İkincisi: alt alta iki paragraf var. Üsttekinin margin-block-end'i 24px, alttakinin margin-block-start'ı 16px. Aralarında kaç piksel boşluk olur: 40 mı, 24 mü, 16 mı? Peki aynı iki paragrafı display: flex; flex-direction: column olan bir kutuya koyarsak?
<div class="box">border-box</div>
<div class="box content-box">content-box</div>
<div class="stack" id="siblings"><p class="first">Dune</p><p class="second">Emma</p></div>
<div class="stack" id="negative"><p class="first">Dune</p><p class="pull-up">Emma</p></div>
<div class="stack as-flex" id="flex"><p class="first">Dune</p><p class="second">Emma</p></div>
<section class="promo" id="escape"><h2>Summer sale</h2></section>
<section class="promo flow-root" id="contained"><h2>Summer sale</h2></section>
<div class="shelf">
<p class="min">The Count of Monte Cristo</p>
<p class="max">The Count of Monte Cristo and Twenty Thousand Leagues Under the Seas</p>
<p class="fit">The Count of Monte Cristo</p>
<p class="fit">The Count of Monte Cristo and Twenty Thousand Leagues Under the Seas</p>
</div>
Cevapları not et. Önceki iki yazıda tarayıcının hangi kuralı seçtiğini konuştuk; bu yazıda seçilen değerlerin bir kutuya nasıl dönüştüğüne bakıyoruz. Box model, CSS'te layout'un en alt katmanı: Flexbox ve Grid de sonunda bu kutuları yerleştiriyor.
Box Model ve box-sizing
Sayfadaki her element bir kutu üretir ve bu kutunun dört katmanı vardır: içerik (content), içerikle kenar arasındaki iç boşluk (padding), kenar çizgisi (border) ve kutunun dışında komşularla arasında bıraktığı boşluk (margin). background padding'in sonuna kadar boyanır; margin her zaman şeffaftır.

Asıl soru, width'in bu katmanlardan hangisini ölçtüğü. Bunu box-sizing belirler. Varsayılan değer content-box: width yalnız içeriğin genişliğidir, padding ve border bunun üstüne eklenir. border-box'ta ise width border dahil kutunun tamamıdır, içerik geriye kalan alanı alır. Ölçüm script'ini iki motorda çalıştırdım:
node scripts/03-box.mjs
node scripts/03-book.mjs
--- chromium
border-box rendered width: 200px
content-box rendered width: 240px
gap (siblings): 24px
gap (negative): 16px
gap (flex): 40px
escape space between section top and h2: 0px
contained space between section top and h2: 32px
min width: 46.0 lines: 5
max width: 537.4 lines: 1
fit width: 192.1 lines: 1
fit width: 300.0 lines: 2
100vh=844px 100svh=844px 100lvh=844px 100dvh=844px
--- webkit
border-box rendered width: 200px
content-box rendered width: 240px
gap (siblings): 24px
gap (negative): 16px
gap (flex): 40px
escape space between section top and h2: 0px
contained space between section top and h2: 32px
min width: 46.0 lines: 5
max width: 537.4 lines: 1
fit width: 192.1 lines: 1
fit width: 300.0 lines: 2
100vh=844px 100svh=844px 100lvh=844px 100dvh=844px
İlk iki satır birinci sorunun cevabı. border-box kutusu tam 200px, içeriği 200 − 2×16 − 2×4 = 160px. content-box kutusu ise 240px: 200px içerik, iki yanda 16px padding ve 4px border. Tasarımcı "kart 200 piksel" dediğinde kastettiği hemen her zaman ikincisi değil birincisidir. Bu yüzden ilk yazıda kurduğumuz reset layer'ında bütün element'ler ve pseudo-element'ler için box-sizing: border-box var. Bu satır neredeyse her modern CSS reset'inde bulunur ve sebebi bu ölçüm.
Margin Collapse: Ne Zaman Olur, Ne Zaman Olmaz?
Sonraki üç satır ikinci sorunun cevabı. Normal block akışında alt alta gelen iki element'in dikey margin'leri toplanmaz, birleşir: büyük olan kalır. 24 ile 16 yan yana gelince boşluk 24px oldu. Margin'lerden biri negatifse en büyük pozitif ile en küçük negatif toplanır; 24 ile −8, 16px verdi. Ama aynı iki paragraf flex container'ın içindeyken boşluk 40px: flex ve grid item'larının margin'leri hiçbir zaman collapse etmez.
Margin collapse yalnız iki kardeş arasında olmaz. Parent'ın üstünde border, padding ya da satır içi içerik yoksa ve parent kendi block formatting context'ini kurmuyorsa, ilk child'ın üst margin'i parent'ın dışına taşar. Çıktıdaki escape satırında "Summer sale" başlığının 32px'lik margin'i section'ın içinde değil dışında kaldı: section'ın üstü ile başlığın üstü arasında 0px var ve section'ın sarı zemini başlığın hemen üstünden başlıyor. Banner'ın üstünde beklenmedik bir boşluk görüp padding'i aradığın klasik durum bu. contained satırında aynı section'a yalnız display: flow-root ekledim; section kendi formatting context'ini kurdu, margin içeride kaldı ve 32px'lik boşluk zeminin içinde göründü. flow-root'un tam olarak ne yaptığını bir sonraki yazıda göreceğiz; şimdilik "kutunun içindekini kutuda tutar" demek yeterli.
Collapse'ın olmadığı durumları da bilmek gerekiyor: yatay margin'ler hiçbir zaman collapse etmez; flex ve grid item'ları, float'lar, absolute konumlanmış element'ler ve inline-block'lar da etmez. Arada border, padding ya da içerik varsa da etmez. Pratik bir kural olarak ben dikey boşluğu tek yönden veriyorum: element'lerin yalnız margin-block-end'i olur, margin-block-start kullanmam. Flex ve Grid layout'larında ise margin yerine gap kullanmak bu soruyu tamamen ortadan kaldırıyor; onu Flexbox yazısında göreceğiz.
Intrinsic Size'lar: min-content, max-content ve fit-content
Bir kutunun genişliğini sabit bir sayı yerine içeriğine göre de belirleyebilirsin. 300px'lik bir rafın içinde dört paragrafı ölçtüm. min-content, içeriğin taşmadan alabileceği en dar genişlik: metin için en uzun kelime. "The Count of Monte Cristo" 46px'e indi ve beş satıra bölündü. max-content, içeriğin hiç satır kırmadan istediği genişlik; uzun başlık rafı aşıp 537.4px oldu. fit-content ise ikisinin akıllı ortası: içerik sığıyorsa max-content kadar (kısa başlık 192.1px, tek satır), sığmıyorsa mevcut alan kadar (uzun başlık 300px, iki satır). Fiyat etiketi ya da "In stock" rozeti gibi, içeriği kadar yer kaplaması gereken ama taşmaması da gereken kutular için fit-content doğru seçim.
Bu keyword'lerin yanında min(), max() ve clamp() function'ları da sınır koymak için var. Kitap kapağında inline-size: min(100%, 20rem) yazdım: kapak hiçbir zaman 20rem'den büyük olmuyor ama dar bir ekranda kutusundan da taşmıyor. clamp() ile akışkan font boyutunu serinin responsive design yazısında konuşacağız.
aspect-ratio ile Kapak Oranı, overflow ve text-overflow
Kitap detay sayfasını bu yazının konularıyla güncelledim:
.book-page {
max-inline-size: 60rem;
margin-inline: auto;
padding-inline: var(--space);
}
/* tek satır, sonu üç noktayla kesilir */
.breadcrumb {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--color-muted);
}
/* kapak her genişlikte 2:3 oranını korur */
.book-cover {
inline-size: min(100%, 20rem);
block-size: auto;
aspect-ratio: 2 / 3;
object-fit: cover;
border-radius: var(--radius);
}
.book-page .price {
padding-inline-start: 0.75rem;
border-inline-start: 4px solid var(--color-accent);
font-size: 1.25rem;
}
/* özet en fazla üç satır, okunabilir satır uzunluğu */
.book-summary {
max-inline-size: 65ch;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
overflow: hidden;
}
--- chromium 320px
cover: 288 x 432
breadcrumb: client 288px, content 632px
summary: 72px tall (line-height 24px), max 655.078px
price border left/right: 4px / 0px, padding left/right: 12px / 0px
--- chromium 1280px
cover: 320 x 480
breadcrumb: client 928px, content 928px
summary: 72px tall (line-height 24px), max 655.078px
price border left/right: 4px / 0px, padding left/right: 12px / 0px
dir=rtl -> price border left/right: 0px / 4px, padding left/right: 0px / 12px
--- webkit 320px
cover: 288 x 432
breadcrumb: client 288px, content 632px
summary: 72px tall (line-height 24px), max 634.765625px
price border left/right: 4px / 0px, padding left/right: 12px / 0px
--- webkit 1280px
cover: 320 x 480
breadcrumb: client 928px, content 928px
summary: 72px tall (line-height 24px), max 634.765625px
price border left/right: 4px / 0px, padding left/right: 12px / 0px
dir=rtl -> price border left/right: 0px / 4px, padding left/right: 0px / 12px
Kapak 320px genişliğindeki telefonda 288 × 432, masaüstünde 320 × 480: genişlik değişse de oran hep 2:3. aspect-ratio Eylül 2021'den beri Baseline. Bir ayrıntı: img'e width ve height attribute'larını yazdığında tarayıcı oranı onlardan kendisi çıkarır ve görsel yüklenmeden önce yer ayırır. aspect-ratio'yu yine de açıkça yazıyorum, çünkü yükleme sırasında sayfanın kaymaması attribute'lara, farklı oranda gelen bir görselin de 2:3'e oturması object-fit: cover'a bağlı. Bu yer ayırmanın Core Web Vitals'taki karşılığı ilerideki Web Performansı serisinde.
Breadcrumb satırı tek satırda kalıp sonu üç noktayla kesiliyor. Bunun için üç property birlikte gerekiyor: white-space: nowrap satırın kırılmasını, overflow: hidden taşan kısmın görünmesini engelliyor, text-overflow: ellipsis kesilen yere üç nokta koyuyor. Çıktıda telefonda içerik 632px ama kutu 288px; masaüstünde ikisi eşit, yani hiçbir şey kesilmedi.
Özet paragrafı en fazla üç satır gösteriyor: çıktıda 24px'lik satır yüksekliğiyle 72px. Çok satırlı kesme için standart line-clamp property'si henüz Baseline değil; bugün çalışan yol display: -webkit-box, -webkit-box-orient: vertical ve -webkit-line-clamp üçlüsü. Prefix'li olmasına rağmen Chromium ve WebKit'te sonuç aynı, MDN'ye (Mozilla'nın web geliştirici dokümantasyonu) göre Firefox da destekliyor.
overflow'un değerleri arasında hidden ile clip farkını da bilmek iyi olur. İkisi de taşanı gizler, ama hidden kutuyu bir scroll container yapar (script ile kaydırılabilir), clip yapmaz. Bu fark, bir sonraki yazıda position: sticky'nin neden çalışmadığını ararken önem kazanacak.
Birimler: rem, em, ch ve Viewport Birimleri
rem kök element'in (html) font boyutuna göredir; tarayıcının varsayılanı 16px olduğu için 20rem 320px. Kullanıcı tarayıcı ayarlarından varsayılan font boyutunu büyütürse rem ile verilen her ölçü onunla büyür; bu yüzden font boyutu, boşluk ve kutu sınırları için ilk tercihim rem. em ise element'in kendi font boyutuna göredir ve iç içe kullanıldığında çarpılır; bir butonun padding'ini butonun font'una göre ölçeklemek gibi bilinçli durumlar için iyi.
ch, font'taki "0" karakterinin genişliği. Özet paragrafında max-inline-size: 65ch ile satır uzunluğunu yaklaşık 65 karakterde tuttum. Çıktıya dikkat: Chromium'da 655.078px, WebKit'te 634.766px. İki motor system-ui için farklı font kullanıyor ve ch font'a bağlı olduğu için sonuç da farklı. ch bir piksel ölçüsü değil, okunabilir bir satır uzunluğu ifadesi.
Viewport birimleri mobilde en çok hata yapılan yer. 100vh "ekran yüksekliği" gibi görünür, ama mobil tarayıcılarda adres çubuğu kaydırınca gizlenip gösterildiği için ekranın yüksekliği değişir. Spec'e göre vh büyük viewport'a, yani çubuklar gizliyken olan yüksekliğe eşit olmalı. Sonuç: 100vh ile yapılan tam ekran bir checkout özeti, sayfa ilk açıldığında alttan adres çubuğunun altına kayar. Aralık 2022'den beri Baseline olan üç yeni birim bunu çözüyor: svh küçük viewport'a (çubuklar görünürken), lvh büyük viewport'a, dvh ise o anki gerçek yüksekliğe göre hesaplanıyor. Ölçümün son satırında dördü de 844px; headless bir tarayıcıda gizlenip açılan bir adres çubuğu yok, yani farkı burada gösteremiyorum. Gerçek bir telefonda svh değeri lvh'den küçük olur. Kuralım şu: ekranı doldurması gereken bir alan için min-block-size: 100svh; dvh'yi ise kaydırma sırasında yeniden layout'a yol açabileceği için yalnız gerçekten gerektiğinde kullanırım. Container'a göre ölçen cqi gibi birimler serinin container query yazısında.
Logical Property'ler ve RTL'ye Hazır Stil
Bu yazıdaki kodda width, margin-left, padding-top yerine inline-size, margin-inline, padding-block gibi isimler kullandım. Bunlar logical property'ler ve Eylül 2021'den beri Baseline. Fiziksel property'ler ekranın yönüne bağlıdır (sol, sağ, üst, alt); logical olanlar ise metnin akış yönüne. Inline ekseni satırın yazıldığı yön, block ekseni satırların alt alta dizildiği yön. Türkçe ve İngilizce'de inline soldan sağa, ama Arapça ve İbranice'de sağdan sola.
Fiyatın solundaki kırmızı çizgiyi border-inline-start ile verdim. Çıktının son satırında aynı sayfayı dir="rtl" ile ölçtüm: çizgi ve padding kendiliğinden sağa geçti, CSS'te tek satır değiştirmedim. Bookstore bugün yalnız Türkçe ve İngilizce, ama yeni yazdığın CSS'te logical property kullanmanın bir maliyeti yok ve ileride RTL (sağdan sola yazılan) bir dil eklendiğinde bütün stil dosyalarını elden geçirmekten kurtarıyor. Benim kuralım şu: metin akışıyla ilgili her şeyde (margin, padding, border, hizalama) logical; gerçekten fiziksel olan şeylerde, örneğin bir gölgenin ışık yönünde, fiziksel property.
Üretimde Dikkat
box-sizing: border-box'ı mevcut bir projeye sonradan eklemek, content-box'a göre hesaplanmış her genişliği değiştirir. Bunu yeni bir projede ilk gün yap; eski bir projede ise önce reset layer'ına koyup sayfaları tek tek kontrol et. Üçüncü parti bir component content-box'a güveniyorsa, ona özel box-sizing: content-box yazmak gerekebilir.
Viewport birimleri için gerçek cihazda test şart. Bu yazıdaki headless ölçümde olduğu gibi, gizlenip açılan bir adres çubuğu olmayan bir ortamda dört birim aynı değeri verir ve farkı göremezsin. svh ile dvh arasındaki farkı gerçek bir telefonda, sayfayı kaydırarak gör.
-webkit-line-clamp ile kesilen metin DOM'da (tarayıcının sayfadan kurduğu element ağacında) ve ekran okuyucuda tam haliyle durur; yalnız görünüm kısalır. Kesilen kısım önemliyse kullanıcıya tamamını görmenin bir yolunu ver ("Read more" gibi).
Sık Düşülen Tuzaklar
Birincisi, banner'ın üstündeki açıklanamayan boşluk. Çoğu zaman sebep, ilk child'ın margin'inin parent'ın dışına collapse etmesi. Parent'a padding vermek ya da display: flow-root yazmak çözer.
İkincisi, iki element arasındaki boşluğun iki tarafa da margin vererek "toplanacağını" sanmak. Normal akışta toplanmaz, büyük olan kalır; flex ve grid'de toplanır. Aynı component'i iki farklı layout'a koyduğunda boşluk değişiyorsa sebep bu.
Üçüncüsü, tam ekran alanlar için height: 100vh. Mobilde adres çubuğu görünürken içerik ekranın altına taşar. min-block-size: 100svh kullan, ve height yerine min- ile başlayanı tercih et ki içerik büyüdüğünde kutu da büyüsün.
Dördüncüsü, text-overflow: ellipsis'in tek başına çalışmasını beklemek. white-space: nowrap ve overflow: hidden olmadan üç nokta görünmez; flex item'larında ise bunlara ek olarak min-width: 0 gerekir. Bu son tuzağı Flexbox yazısında ölçeceğiz.
Küçük Bir Deneme
★☆☆ lab/03-box.html'de .second'ın margin'ini 40px yap ve node scripts/03-box.mjs'i çalıştır. Boşluk ne oldu? Sonra #siblings'e display: flow-root ekle; kardeşler arasındaki collapse değişti mi, neden?
★★☆ Kitap detay sayfasındaki .price'ı fiziksel property'lerle (padding-left, border-left) yeniden yaz ve node scripts/03-book.mjs'teki dir=rtl satırını karşılaştır.
★★★ Telefonunda Bookstore'un checkout özetini taklit eden bir sayfa yap: tam ekran bir kutu ve altta sabit bir "Place order" butonu. Kutuyu önce 100vh, sonra 100svh ve 100dvh ile dene; sayfayı kaydırırken butonun nerede durduğunu not et.
Bir sonraki yazıda kutuların sayfaya nasıl yerleştiğine geçiyoruz: normal flow, display'in iki değerli syntax'ı, flow-root, position: sticky'nin çalışmama sebepleri ve z-index'in neden bazen işe yaramadığını açıklayan stacking context.