CSS Grid 1: Track'ler, fr, repeat(auto-fill, minmax()) ve Named Grid Area'lar
repeat(auto-fill, minmax(14rem, 1fr)) container'a sığdığı kadar kolon açarak media query'siz responsive bir ızgara kurar; auto-fit ise boş kolonları sıfıra indirip item'ları yayar. Bookstore'un katalog ızgarasını ve sayfa iskeletini Grid ile kuruyor, fr ile minmax(0, 1fr) farkını, implicit grid'i, named line'ları, grid-template-areas'ı ve dense'in DOM sırasını nasıl bozduğunu Chromium ve WebKit'te ölçüyoruz.
Sürüm notu: Bu yazı CSS Grid Layout Level 2 spesifikasyonuna 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 katalog ızgarasını Grid ile kuruyoruz. Arama sonucunda yalnız iki kitap döndü: Dune ve Emma. Liste 992px genişliğinde ve iki farklı kolon tanımı deniyoruz; aradaki tek fark auto-fill ile auto-fit:
/* kendi başına çalışır: bu lab main.css'i yüklemez */
body { margin: 0; }
ul { list-style: none; margin: 0; padding: 0; }
.fr-demo { display: grid; grid-template-columns: 200px 1fr 2fr; inline-size: 800px; }
.results { display: grid; gap: 16px; inline-size: 992px; }
.results.fill { grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); }
.results.fit { grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); }
.downloads { display: grid; gap: 16px; inline-size: 600px; }
.downloads.fr { grid-template-columns: 1fr 1fr; }
.downloads.zero { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
.fr-demo p, .downloads p { margin: 0; }
İki listede de kartlar kaç piksel genişliğinde olur? İki tanım da "sığdığı kadar en az 14rem'lik kolon" diyor; sonuç aynı mı?
Önceki yazıda Flexbox'la kurduğumuz katalog listesinin bir sınırına takıldık: 768px'te son satırda tek kalan kart bütün satıra yayıldı, çünkü Flexbox her satırı ayrı hesaplıyor. Kartların her satırda aynı kolonlara oturması iki boyutlu bir problem ve bu yazıda onu Grid ile çözüyoruz. Track'ler, fr birimi, media query'siz responsive ızgara, implicit grid, line'lar ve sayfa iskeletini isimle tarif eden grid-template-areas konularımız. Subgrid ve hizalama serinin bir sonraki yazısında.
Track'ler ve fr Birimi
Grid, container'ı satır ve kolonlardan oluşan bir ızgaraya böler. Her satır ya da kolon bir track, aralarındaki çizgiler line. grid-template-columns kolon track'lerini, grid-template-rows satır track'lerini tanımlar. fr ise Grid'e özel bir birim: sabit boyutlu track'ler yerleştikten sonra kalan alanın payı. Lab sayfasındaki ilk örnekte 800px'lik bir container'a 200px 1fr 2fr verdim:
<div class="fr-demo" id="fr"><p>filters</p><p>results</p><p>details</p></div>
<ul class="results fill" id="fill"><li>Dune</li><li>Emma</li></ul>
node scripts/06-grid.mjs
node scripts/06-catalog.mjs
--- chromium
#fr: columns 200px 200px 400px
filters x=0 y=0 w=200.0
results x=200 y=0 w=200.0
details x=400 y=0 w=400.0
#fill: columns 236px 236px 236px 236px
Dune x=0 y=0 w=236.0
Emma x=252 y=0 w=236.0
#fit: columns 488px 488px 0px 0px
Dune x=0 y=0 w=488.0
Emma x=504 y=0 w=488.0
#downloads-fr: columns 402.625px 181.375px
https://downloads.bookst x=0 y=0 w=402.6
Emma, EPUB x=419 y=0 w=181.4
#downloads-zero: columns 292px 292px
https://downloads.bookst x=0 y=0 w=292.0
Emma, EPUB x=308 y=0 w=292.0
#sparse: columns [full-start] 100px 100px 100px 100px [full-end]
Dune x=0 y=0 w=100.0
Ulysses x=110 y=0 w=100.0
Emma x=0 y=90 w=320.0
Middlemarch x=330 y=90 w=100.0
Summer sale x=0 y=180 w=430.0
#dense: columns [full-start] 100px 100px 100px 100px [full-end]
Dune x=0 y=0 w=100.0
Ulysses x=110 y=0 w=100.0
Emma x=0 y=90 w=320.0
Middlemarch x=220 y=0 w=100.0
Summer sale x=0 y=180 w=430.0
--- webkit
#fr: columns 200px 200px 400px
filters x=0 y=0 w=200.0
results x=200 y=0 w=200.0
details x=400 y=0 w=400.0
#fill: columns 236px 236px 236px 236px
Dune x=0 y=0 w=236.0
Emma x=252 y=0 w=236.0
#fit: columns 488px 488px 0px 0px
Dune x=0 y=0 w=488.0
Emma x=504 y=0 w=488.0
#downloads-fr: columns 402.625px 181.375px
https://downloads.bookst x=0 y=0 w=402.6
Emma, EPUB x=419 y=0 w=181.4
#downloads-zero: columns 292px 292px
https://downloads.bookst x=0 y=0 w=292.0
Emma, EPUB x=308 y=0 w=292.0
#sparse: columns [full-start] 100px 100px 100px 100px [full-end]
Dune x=0 y=0 w=100.0
Ulysses x=110 y=0 w=100.0
Emma x=0 y=90 w=320.0
Middlemarch x=330 y=90 w=100.0
Summer sale x=0 y=180 w=430.0
#dense: columns [full-start] 100px 100px 100px 100px [full-end]
Dune x=0 y=0 w=100.0
Ulysses x=110 y=0 w=100.0
Emma x=0 y=90 w=320.0
Middlemarch x=220 y=0 w=100.0
Summer sale x=0 y=180 w=430.0
İlk blok: 800px'ten 200px'lik sabit kolon çıktı, kalan 600px üç paya bölündü; 1fr 200px, 2fr 400px oldu. %'den farkı şu: fr, gap'leri ve sabit track'leri düştükten sonra kalan alanla hesaplanır. Yüzdeyle yazılan kolonlar gap eklendiğinde container'dan taşar; fr'li olanlar taşmaz.
repeat(auto-fill, minmax()) ile Media Query'siz Responsive Izgara
Katalog ızgarasının kalbi tek bir satır: repeat(auto-fill, minmax(14rem, 1fr)). Parça parça okuyalım. minmax(14rem, 1fr) bir track'in en az 14rem, en fazla bir pay olmasını söylüyor. repeat(auto-fill, …) ise bu track'ten container'a sığdığı kadar oluşturuyor. Sonuç: container genişledikçe kolon sayısı kendiliğinden artıyor, her kolon da kalan alanı eşit paylaşıyor. Media query yok.
Tahmin sorusunun cevabı ikinci ve üçüncü bloklarda. 992px'e 16px'lik gap'lerle dört tane 14rem'lik (224px) kolon sığıyor. auto-fill'de dört kolon da oluşturuldu ve her biri 236px; iki kitap ilk iki kolona oturdu, son iki kolon boş ama yer kaplıyor. auto-fit'te ise boş track'ler 0px'e çöktü (488px 488px 0px 0px) ve iki kart 488px'e yayıldı. Fark yalnız item sayısı kolon sayısından az olduğunda ortaya çıkıyor. Bir katalogda kartların sonuç sayısına göre şişmesini istemezsin; bu yüzden katalog için auto-fill seçtim. Az sayıda öğenin satırı doldurması istenen yerlerde, örneğin iki ya da üç adımlı bir checkout göstergesinde, auto-fit daha uygun.
Bookstore'un katalog listesini Flexbox'tan Grid'e taşıdım:
.book-list {
display: grid;
/* sığdığı kadar en az 14rem'lik kolon; min() çok dar ekranda tek kolonun taşmasını önler */
grid-template-columns: repeat(auto-fill, minmax(min(14rem, 100%), 1fr));
gap: var(--space);
list-style: none;
margin: 0;
padding: 0;
/* listenin içindeki z-index değerleri sticky header ile hiçbir zaman yarışmaz */
isolation: isolate;
}
Önceki yazıdaki .book-card'ın flex: 1 1 14rem'i artık gerekmiyor; kartın genişliğini ızgara belirliyor. 14rem yerine min(14rem, 100%) yazmamın sebebi şu: container 14rem'den darsa, örneğin 200px'lik bir yan panelde, tek bir kolon bile 14rem olmaya çalışıp taşardı; min() bu durumda kolonu container genişliğine indiriyor.
grid-template-areas ile Sayfa İskeleti
Katalog sayfasının iskeletini de Grid ile kurdum: header, filtreler, sonuçlar ve footer. grid-template-areas, layout'u isimlerle, neredeyse bir çizim gibi tarif etmeyi sağlıyor. Her tırnak bir satır, her kelime bir hücre; aynı ismi birden fazla hücreye yazmak o alanı genişletiyor:
/* sayfa iskeleti: telefonda tek kolon, 48rem'den itibaren filtreler sonuçların yanında */
.page {
display: grid;
grid-template-columns: minmax(0, 1fr);
grid-template-rows: auto auto 1fr auto;
grid-template-areas:
"header"
"filters"
"main"
"footer";
min-block-size: 100svh;
}
@media (width >= 48rem) {
.page {
grid-template-columns: 14rem minmax(0, 1fr);
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"filters main"
"footer footer";
}
}
.site-header {
grid-area: header;
}
.filters {
grid-area: filters;
padding-inline: var(--space);
}
.catalog {
grid-area: main;
}
.site-footer {
grid-area: footer;
padding-inline: var(--space);
border-block-start: 1px solid var(--color-border);
color: var(--color-muted);
}
--- chromium
1280px areas: filters x=0 y=56 w=224 | main x=224 y=56 w=1056
book-list columns: 244px 244px 244px 244px
The Hobbit: x=1020 y=177 w=244
1024px areas: filters x=0 y=56 w=224 | main x=224 y=56 w=800
book-list columns: 245.328px 245.328px 245.328px
The Hobbit: x=240 y=496 w=245
768px areas: filters x=0 y=56 w=224 | main x=224 y=56 w=544
book-list columns: 248px 248px
The Hobbit: x=504 y=496 w=248
360px areas: filters x=0 y=56 w=360 | main x=0 y=213 w=360
book-list columns: 328px
The Hobbit: x=16 y=1106 w=328
--- webkit
1280px areas: filters x=0 y=56 w=224 | main x=224 y=56 w=1056
book-list columns: 244px 244px 244px 244px
The Hobbit: x=1020 y=178 w=244
1024px areas: filters x=0 y=56 w=224 | main x=224 y=56 w=800
book-list columns: 245.328125px 245.328125px 245.34375px
The Hobbit: x=240 y=497 w=245
768px areas: filters x=0 y=56 w=224 | main x=224 y=56 w=544
book-list columns: 248px 248px
The Hobbit: x=504 y=497 w=248
360px areas: filters x=0 y=56 w=360 | main x=0 y=213 w=360
book-list columns: 328px
The Hobbit: x=16 y=1107 w=328
Telefonda (360px) her şey tek kolonda alt alta: filtreler 56. pikselde başlıyor, sonuçlar onların altında, 213. pikselde. 48rem ve üstünde iskelet iki kolona geçiyor: filtreler 224px'lik sol kolonda, sonuçlar yanında. @media (width >= 48rem) media query'lerin yeni range syntax'ı; Mart 2023'ten beri Baseline ve ayrıntısı serinin responsive design yazısında. Layout değişse de HTML'e dokunmadım; her element'in nerede duracağını yalnız grid-area ismi belirliyor.
Aynı çıktı katalog ızgarasının davranışını da gösteriyor. 1280px'te sonuç alanı 1056px ve dört kolon var; 1024px'te üç, 768px'te iki, 360px'te bir. Önceki yazının sorunu da çözüldü: 1024px'te ikinci satıra düşen The Hobbit, üstündeki kartlarla aynı genişlikte (245px) ve aynı kolonda; satıra yayılmadı. Kolonlar artık satırlardan bağımsız, bütün ızgara için tanımlı.
Area'ları ekran boyutuna göre yeniden dizmek de mümkün. Örneğin telefonda önce sonuçları, sonra filtreleri göstermek için yalnız grid-template-areas'ı değiştirmek yeterli:
/* projede değil: telefonda önce sonuçlar, sonra filtreler */
.page {
grid-template-areas:
"header"
"main"
"filters"
"footer";
}
Bunu projeye koymadım, çünkü görsel sırayı DOM (tarayıcının sayfadan kurduğu element ağacı) sırasından koparıyor. DOM'da filtreler sonuçlardan önce; Tab tuşu ve ekran okuyucu filtrelerden başlar, ama ekranda sonuçlar üstte görünür. Flexbox'taki order ile aynı sorun. Telefonda sonuçları öne almak istiyorsan HTML'de sırayı değiştir ve filtreleri bir details içinde kapalı başlat; Grid'i yalnız görsel düzen için kullan.
Explicit ve Implicit Grid, Line'lar ve dense
grid-template-columns ve grid-template-rows ile tanımladığın track'ler explicit grid'i oluşturur. Item'lar bu ızgaraya sığmazsa tarayıcı yeni track'ler ekler; bunlar implicit grid ve boyutlarını grid-auto-rows ile grid-auto-columns belirler. Katalog ızgarasında hiç satır tanımlamadım: bütün satırlar implicit ve yükseklikleri içeriğe göre. İkinci lab sayfasında dört kolonlu bir raf, sabit yükseklikli implicit satırlar, üç kolona yayılan öne çıkan bir kitap ve bütün satırı kaplayan bir kampanya şeridi var:
/* kendi başına çalışır: bu lab main.css'i yüklemez */
body { margin: 0; }
ul { list-style: none; margin: 0; padding: 0; }
.shelf {
display: grid;
grid-template-columns: [full-start] repeat(4, 100px) [full-end];
grid-auto-rows: 80px;
gap: 10px;
inline-size: 430px;
}
.shelf.dense { grid-auto-flow: dense; }
.featured { grid-column: span 3; }
.promo { grid-column: full-start / full-end; }
<ul class="shelf" id="sparse">
<li>Dune</li><li>Ulysses</li><li class="featured">Emma</li><li>Middlemarch</li><li class="promo">Summer sale</li>
</ul>
Line'lar 1'den başlayarak numaralanır, sondan saymak için negatif sayı kullanılır; grid-column: 1 / -1 "ilk line'dan son line'a kadar" demek. Line'lara isim de verebilirsin: [full-start] ve [full-end] ile, kampanya şeridi grid-column: full-start / full-end diyerek bütün kolonları kaplıyor. Kolon sayısı ileride değişse de isimli line'lar doğru yeri gösterir. span 3 ise başlangıcı tarayıcıya bırakıp "üç kolon kapla" diyor.
Çıktının son iki bloğu yerleştirme algoritmasını gösteriyor. #sparse'ta Dune ve Ulysses ilk satırın ilk iki kolonunu aldı. Emma'nın üç kolona ihtiyacı var, ilk satırda iki kolon kalmıştı; Emma ikinci satıra indi (y=90) ve ilk satırın son iki hücresi boş kaldı. Middlemarch da Emma'dan sonraya, ikinci satırın son hücresine yerleşti (x=330). Tarayıcı varsayılan olarak geri dönüp boşlukları doldurmaz. grid-auto-flow: dense ile bunu yapar: #dense'te Middlemarch ilk satırdaki boşluğa (x=220, y=0) geri döndü. Izgara daha sıkı ama ekrandaki sıra artık Dune, Ulysses, Middlemarch, Emma; DOM sırası ise Dune, Ulysses, Emma, Middlemarch. dense'i yalnız sıranın anlam taşımadığı, tamamen görsel galerilerde kullan.
fr ile minmax(0, 1fr) Farkı: Taşan İçerik
Şimdi en sık karşılaşılan Grid tuzağı. 1fr aslında minmax(auto, 1fr)'nin kısaltması ve bu auto minimum, bir önceki yazıdaki flex item'ların min-width: auto'suna benziyor: track, içeriğinin min-content genişliğinden daha dar olmuyor. Lab sayfasında iki eşit kolonun birine bir e-kitap indirme adresi koydum. Çıktıdaki #downloads-fr bloğunda kolonlar eşit değil: 402.625px ve 181.375px. Uzun adresin kırılamayan en uzun parçası ilk kolonu genişletti, ikincisi küçüldü. minmax(0, 1fr) yazınca (#downloads-zero) iki kolon da 292px oldu: minimum artık sıfır, içerik track'i büyütemiyor.
Bu yüzden sayfa iskeletinde de ana kolonu 1fr yerine minmax(0, 1fr) ile yazdım. Sonuç alanında geniş bir tablo ya da uzun bir kod satırı varsa 1fr bütün sayfayı yatayda kaydırılabilir yapar; minmax(0, 1fr) ise taşmayı o element'in içinde bırakır. Bir ayrıntı: track'in genişlemesini engellemek, içeriğin hücreden dışarı taşmasını engellemez. Uzun adresin hücre içinde kırılması için ona overflow-wrap: anywhere vermek gerekir.
Üretimde Dikkat
Grid 2017'den beri Baseline ve bu yazıdaki her şey (fr, repeat(), minmax(), auto-fill/auto-fit, area'lar, dense) o ilk sürümün parçası; tarayıcı desteği konusunda düşünmene gerek yok. Kullandığım tek yeni şey media query range syntax'ı (Mart 2023). Grid'in bugün hâlâ Baseline olmayan kısmı masonry düzeni; onun Kasım 2026 durumu serinin bir sonraki yazısında.
Izgarayı gerçek veriyle test et: bir sonuç, iki sonuç, bir sayfa dolusu sonuç, en uzun başlık ve en dar ekran. Bu yazıdaki ölçüm script'i gibi birkaç genişlikte kolon sayısını ve bir kartın konumunu okuyan bir kontrol, bir CSS değişikliğinin ızgarayı bozup bozmadığını görsel karşılaştırma aracı kurmadan söylüyor. Chrome DevTools'un Elements panelinde Grid container'ın yanındaki "grid" rozetine tıklamak da line numaralarını, area isimlerini ve track boyutlarını sayfanın üstüne çiziyor; Grid hatası ararken ilk bakacağın yer orası.
Sık Düşülen Tuzaklar
Birincisi, 1fr'in her zaman eşit kolon verdiğini sanmak. İçerik büyükse 1fr kolonu genişler; eşitlik istiyorsan minmax(0, 1fr) yaz.
İkincisi, auto-fill ile auto-fit'i karıştırmak. Fark yalnız item'lar satırı doldurmadığında görünür, bu yüzden genellikle canlıda, az sonuç dönen bir aramada fark edilir.
Üçüncüsü, görsel sırayı DOM sırasından koparmak. grid-template-areas ile yer değiştirmek, grid-row/grid-column ile item taşımak ve dense hepsi aynı sonucu doğurur: Tab sırası ekrandaki sırayı izlemez.
Dördüncüsü, yüzdelik kolonlarla gap kullanmak. grid-template-columns: 50% 50% ile gap: 1rem container'dan 1rem taşar; 1fr 1fr taşmaz.
Küçük Bir Deneme
★☆☆ lab/06-tracks.html'deki .fr-demo'ya gap: 20px ekle ve kolon genişliklerini önce hesapla, sonra node scripts/06-grid.mjs ile kontrol et. Aynısını 200px 25% 50% ile dene.
★★☆ Katalog ızgarasında auto-fill'i auto-fit yap ve catalog.html'den iki kartı sil. node scripts/06-catalog.mjs'in 1280px satırındaki kolonları karşılaştır.
★★★ Kitap detay sayfası için grid-template-areas ile iki kolonlu bir düzen kur: solda kapak, sağda başlık, yazar, fiyat ve buton, altta tam genişlikte özet. Telefonda tek kolona insin. Uzun bir breadcrumb ya da geniş bir tablo eklendiğinde sayfanın yatay kaydırılabilir hale gelmediğini bir Playwright script'iyle doğrula.
Bir sonraki yazıda Grid'in ikinci kısmına geçiyoruz: farklı uzunlukta başlıkları olan kartlarda satırları hizalayan subgrid, Grid'in hizalama property'leri ve "Grid mi Flexbox mı?" sorusunun karar kuralı.