HTML'de tablo oluşturmak için <table> öğesinin içine her satır için bir <tr>, her satırın içine de başlık hücreleri için <th>, veri hücreleri için <td> yazarsınız. Başlık satırlarını <thead>, asıl verileri <tbody>, toplam ve özet satırlarını da <tfoot> içine aldığınızda tablo; tarayıcı, ekran okuyucu ve arama motorları için anlamlı bir yapıya kavuşur.

Bu yazıda tablonun iskeletini kuran yedi öğeyi MDN belgelerine dayanarak adım adım anlatıyoruz: hangi öğenin nerede durabileceği, tarayıcının sizin yerinize eklediği görünmez <tbody>, satır gruplarının sırası ve en sık yapılan hatalar. Hücre birleştirme (colspan, rowspan), scope, headers ve <caption> ayrıntıları ise colspan, rowspan, scope ve caption yazısında ele alınıyor. Serinin tamamı için HTML öğrenme rehberine bakabilirsiniz.

Tablo ne zaman kullanılır?

Tablo ne zaman kullanılır?

MDN, <table> öğesini satır ve sütunlardan oluşan iki boyutlu veriyi, yani tablo biçimindeki bilgiyi temsil eden öğe olarak tanımlar. Fiyat listeleri, ders programları, ürün karşılaştırmaları, sınav sonuçları ve istatistikler bu tanıma tam uyar: bir hücredeki değerin anlamı hem bulunduğu satıra hem de bulunduğu sütuna bağlıdır. Okuyucu "bu sayı hangi ürünün hangi özelliği?" sorusunu satır ve sütun başlıklarına bakarak cevaplar; ekran okuyucular da aynı ilişkiyi başlık hücreleri üzerinden seslendirir.

Sayfayı sütunlara bölmek ya da bir görseli metnin yanına yerleştirmek gibi görsel düzen işleri bu tanımın dışında kalır. <table> öğesinin örtük ARIA rolü table olduğu için düzen amaçlı kurulmuş bir tablo, yardımcı teknolojilere aslında var olmayan satır ve sütun ilişkileri bildirir. Görünümü CSS ile kurun; sayfa iskeleti için semantik etiketler yazısına göz atın.

İlk tablonuz: satırlar ve hücreler

En basit tablo yalnızca üç öğeden oluşur. Her <tr> bir satırdır; satırın içindeki her <th> ya da <td> bir hücredir. Aşağıdaki örnekte ilk satır sütun başlıklarını, sonraki iki satır verileri taşıyor:

html
<table>
  <tr>
    <th>Ürün</th>
    <th>Stok</th>
    <th>Fiyat (TL)</th>
  </tr>
  <tr>
    <td>Kalem</td>
    <td>120</td>
    <td>25</td>
  </tr>
  <tr>
    <td>Defter</td>
    <td>45</td>
    <td>60</td>
  </tr>
</table>

Sütunları tanımlayan ayrı bir öğe yazmadığınıza dikkat edin. Tarayıcı satırları kaynak koddaki sırayla yukarıdan aşağıya dizer; her satırdaki ilk hücre birinci sütuna, ikinci hücre ikinci sütuna yerleşir. Sütunların soldan sağa mı yoksa sağdan sola mı dizileceğini dir özniteliği belirler (bkz. lang ve dir yazısı). Bu örnekte hiç CSS olmadığı için kenarlıklar ve boşluklar tamamen tarayıcının varsayılan stil sayfasından gelir.

Bir ayrıntı daha: bu tabloda <tbody> yazmadık, ama tarayıcı satırları otomatik olarak örtük bir <tbody> içine alır. Bunun neden önemli olduğunu aşağıda <tr> bölümünde göreceğiz.

th ve td: başlık hücresi mi, veri hücresi mi?

<td> veri taşıyan sıradan hücredir. <th> ise bir hücre grubunun başlığıdır; bu grubun tam olarak hangi hücreler olduğunu scope ve headers öznitelikleri belirler. İki öğenin örtük ARIA rolleri de farklıdır: <td> bir tablo içinde cell, grid rolündeki bir öğe içinde gridcell olarak sunulur; <th> ise konumuna göre columnheader ya da rowheader olur. Başlıkları <td> ile yazıp kalınlaştırmak görünüşte aynı sonucu verse de bu ilişkiyi tamamen kaybettirir.

Başlıklar yalnızca ilk satırda olmak zorunda değildir. MDN'nin genişletilmiş örneğinde her satırın ilk hücresi de <th> olarak yazılır; böylece hem sütun başlıkları hem satır başlıkları oluşur:

html
<table>
  <tr>
    <th>Ad</th>
    <th>Üye no</th>
    <th>Üyelik tarihi</th>
    <th>Bakiye</th>
  </tr>
  <tr>
    <th>Ayşe Demir</th>
    <td>427311</td>
    <td><time datetime="2010-06-03">3 Haziran 2010</time></td>
    <td>0,00</td>
  </tr>
  <tr>
    <th>Mehmet Kaya</th>
    <td>533175</td>
    <td><time datetime="2011-01-13">13 Ocak 2011</time></td>
    <td>37,00</td>
  </tr>
</table>

Tarih hücrelerinde kullanılan <time> öğesi, insanın okuduğu tarihin yanına makinenin okuyabileceği bir değer ekler; ayrıntısı time ve data etiketi yazısında. Hücrelerin içerik kurallarını da bilmek gerekir: <td> akış içeriği (flow content) kabul eder, yani paragraf, liste, bağlantı, görsel hatta başka bir tablo bile içerebilir. <th> de akış içeriği alır ama içinde <header>, <footer>, bölümleme içeriği (<article>, <aside>, <nav>, <section>) ve başlık içeriği (<h1>-<h6>, <hgroup>) bulunamaz. Yani bir sütun başlığını büyük göstermek için içine <h3> koyamazsınız; boyutu CSS ile ayarlarsınız.

Kapanış etiketleri konusunda HTML esnektir: bir <td> ya da <th> hücresinden hemen sonra yeni bir hücre geliyorsa veya üst öğede başka içerik kalmadıysa kapanış etiketi yazılmayabilir. Aynı esneklik <tr> için de geçerlidir. Yine de kapanış etiketlerini yazmak kodu okumayı ve otomatik denetimi kolaylaştırır; bu yazıdaki tüm örneklerde açıkça yazıyoruz.

tr: satırın kuralları ve görünmez tbody

<tr> yalnızca <thead>, <tbody> veya <tfoot> içinde geçerlidir. Tek bir istisna vardır: tabloda hiç <tbody> yoksa <tr> doğrudan <table> içine, ama <caption>, <colgroup> ve <thead> öğelerinden sonra yazılabilir. Satırın içine de yalnızca sıfır veya daha fazla <td> ve <th> ile betik destekleyen öğeler (<script> ve <template>) girebilir. Bir <div> ya da çıplak metin satırın doğrudan çocuğu olamaz.

<tr> doğrudan <table> içine yazıldığında tarayıcı belgeye kendisi bir <tbody> ekler. Yazdığınız kod ile tarayıcının oluşturduğu yapı arasındaki fark şöyledir:

html
<!-- Yazdığınız kod -->
<table>
  <tr>
    <td>1</td>
  </tr>
</table>

<!-- Tarayıcının belgede oluşturduğu yapı -->
<table>
  <tbody>
    <tr>
      <td>1</td>
    </tr>
  </tbody>
</table>

Bunun pratik sonucu: CSS'te table > tr gibi "tablonun doğrudan çocuğu olan satır" seçicileri beklendiği gibi çalışmayabilir ya da hiç çalışmaz, çünkü satırın gerçek üst öğesi artık <tbody>'dir. JavaScript ile tabloyu gezerken de aynı ara katmanla karşılaşırsınız. MDN bu yüzden <tbody> öğesini açıkça yazmanın tarayıcının istenen yapıyı kurmasına yardım ettiğini ve istenmeyen sonuçları önlediğini belirtir.

thead, tbody ve tfoot: satır grupları

Bu üç öğe satırları anlamlı gruplara ayırır ve üçünün de örtük ARIA rolü rowgroup'tur. <thead> sütun başlıklarını taşıyan satırları, <tbody> asıl verileri, <tfoot> ise toplam ve özet satırlarını içerir. MDN bu grupların üç faydasını sayar: yardımcı teknolojilere ve arama motorlarına bağlam sağlarlar, CSS ile ayrı ayrı biçimlendirilebilirler ve yazdırmada işe yararlar. Birden çok sayfaya taşan bir tablo yazdırıldığında <thead> genellikle her sayfada aynı kalan bilgiyi, <tfoot> de her sayfada ara özet olarak çıkan bilgiyi belirtir. Ekran tabloyu tamamen göstermeye yetmediğinde tarayıcı bu blokların ayrı ayrı kaydırılmasına da izin verebilir.

Sıra kuralı kesindir: önce isteğe bağlı <caption>, sonra sıfır veya daha fazla <colgroup>, sonra isteğe bağlı <thead>, ardından ya sıfır veya daha fazla <tbody> ya da bir veya daha fazla <tr>, en sonda da isteğe bağlı <tfoot>. <tfoot>'un gövdeden sonra gelmesi bugünkü HTML'in kuralıdır; MDN, HTML4'te durumun tam tersi olduğunu, yani <tfoot>'un gövdeden önce yazıldığını hatırlatır. Eski bir kaynaktan kopyaladığınız kodda bu farkla karşılaşabilirsiniz.

html
<table>
  <caption>Ekim ayı sipariş özeti</caption>
  <thead>
    <tr>
      <th scope="col">Ürün</th>
      <th scope="col">Adet</th>
      <th scope="col">Tutar (TL)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row">Kalem</th>
      <td>40</td>
      <td>1.000</td>
    </tr>
    <tr>
      <th scope="row">Defter</th>
      <td>15</td>
      <td>900</td>
    </tr>
  </tbody>
  <tfoot>
    <tr>
      <th scope="row" colspan="2">Toplam</th>
      <td>1.900</td>
    </tr>
  </tfoot>
</table>

Bu örnekte <caption> tablonun ilk çocuğu olarak tablonun ne anlattığını söylüyor. scope="col" ve scope="row" başlığın sütuna mı satıra mı ait olduğunu açıkça belirtiyor; alt satırdaki colspan="2" ise "Toplam" başlığını iki sütun genişliğine yayıyor. Bu üç konunun ayrıntıları ikinci tablo yazımızda.

Birden fazla tbody ile gruplama

Bir tabloda yalnızca bir <thead> bulunabilir, ama birden fazla <tbody> kullanabilirsiniz; tek koşul, gövdelerin art arda gelmesidir. Böylece uzun bir tabloyu kategorilere bölebilir, her bölümü ayrı biçimlendirebilirsiniz. Her gövdenin kendi başlık satırına ihtiyacı varsa ikinci bir <thead> yerine gövdenin ilk satırına <th> içeren bir satır koyarsınız. MDN'nin örneğindeki öğrenci listesini kategorili bir stok listesine uyarlayalım:

html
<table>
  <thead>
    <tr>
      <th>Kod</th>
      <th>Ürün</th>
      <th>Stok</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th colspan="3">Kırtasiye</th>
    </tr>
    <tr>
      <td>K-101</td>
      <td>Kalem</td>
      <td>120</td>
    </tr>
    <tr>
      <td>K-102</td>
      <td>Defter</td>
      <td>45</td>
    </tr>
  </tbody>
  <tbody>
    <tr>
      <th colspan="3">Elektronik</th>
    </tr>
    <tr>
      <td>E-201</td>
      <td>Kulaklık</td>
      <td>12</td>
    </tr>
  </tbody>
</table>

Gövdeler art arda yazılmazsa, örneğin araya bir <tfoot> girerse, tarayıcı bu yazım hatasını kendisi düzeltir ve <thead> ile <tfoot>'un tablonun sırasıyla ilk ve son bölümü olarak çizilmesini sağlar. Düzeltmeye güvenmek yerine sırayı baştan doğru yazmak, kaynak kod ile ekrandaki görüntünün aynı kalmasını sağlar.

Tablo öğeleri bir bakışta

Tablo öğeleri bir bakışta: table, caption, thead, tbody

MDN her öğenin "Technical summary" kutusunda izin verilen üst öğeyi ve örtük ARIA rolünü listeler. Yedi temel öğe ile <caption> için bu bilgileri bir araya topladık:

ÖğeGöreviNerede durabilirÖrtük ARIA rolü
tableTablonun kendisiAkış içeriği kabul eden her öğetable
captionTablonun başlığıtable öğesinin ilk çocuğu olarakcaption
theadBaşlık satırları grubucaption ve colgroup sonrası, tbody, tr ve tfoot öncesirowgroup
tbodyGövde satırları grubucaption, colgroup ve thead sonrasırowgroup
tfootÖzet ve toplam satırlarıDiğer tüm tablo öğelerinden sonrarowgroup
trSatırthead, tbody, tfoot (tbody yoksa doğrudan table)row
thBaşlık hücresitrcolumnheader veya rowheader
tdVeri hücresitrcell (grid içinde gridcell)

Tablonun tamamının akış içeriği olduğunu, satır ve hücrelerin ise hiçbir içerik kategorisine girmediğini de not edin: <tr> ya da <td> yalnızca tablo yapısının içinde anlam taşır, bir paragrafın veya listenin içine konamaz. İçerik kategorilerinin genel mantığı için iç içe kullanım kuralları yazısına bakabilirsiniz.

Eski öznitelikler ve yerlerine ne kullanılır?

Eski tablo kodlarında sıkça görülen pek çok öznitelik MDN'de kullanımdan kalkmış (deprecated) ya da geçersiz (obsolete) olarak işaretlenir ve yeni kodda kullanılmamalıdır. Hepsinin görsel karşılığı CSS'tedir; summary özniteliğinin yerini ise bir HTML öğesi olan <caption> alır:

Eski öznitelikHangi öğedeYerine
bordertableCSS border
cellpaddingtableth ve td üzerinde CSS padding
cellspacingtabletable üzerinde CSS border-spacing
bgcolortable, tr, td, thCSS background-color
widthtable, td, thCSS width
aligntableCSS margin-inline-start ve margin-inline-end
align, valigntr, td, thCSS text-align ve vertical-align
summarytablecaption öğesi
scopetdscope yalnızca th üzerinde kullanılır

<table> için hiçbir HTML belirtiminde height özniteliği bulunmadığını, bazı tarayıcıların bunu standart dışı biçimde yorumladığını da MDN ayrıca not eder; yerine CSS min-height önerilir. Kullanımdan kalkan diğer öğe ve öznitelikler için kullanımdan kalkan etiketler yazısına göz atın.

div ile tablo kurmak yerine neden table?

ARIA'da role="table" diye bir rol vardır ve <div> öğeleriyle tablo benzeri bir yapı kurmanıza izin verir. Ancak MDN'nin ARIA table rolü sayfası bunu güçlü bir şekilde önermez: bu yolu seçerseniz satır grupları için rowgroup, satırlar için row, hücreler için cell, columnheader ve rowheader rollerini tek tek eklemeniz, tablonun erişilebilir adını aria-label veya aria-labelledby ile vermeniz, gizli satır ve sütunlar varsa aria-rowcount ve aria-colcount gibi öznitelikleri de yönetmeniz gerekir. Yerel <table> öğesi ise bunların hepsini kendiliğinden sağlar.

Kullanıcının hücre seçebildiği, ok tuşlarıyla iki boyutlu gezinebildiği ya da hücrelerin sırasını değiştirebildiği etkileşimli bir yapı kuruyorsanız MDN table yerine grid veya treegrid rollerini işaret eder. ARIA'nın "yerel öğe varsa onu kullan" ilkesini ARIA nedir yazısında ayrıntılı anlattık.

Küçük ekranlarda büyük tablolar

MDN, çok sütunlu tabloların küçük ekranlarda doğal olarak iyi çalışmadığını ve kaydırılabilir hâle getirmenin her zaman açık olmadığını kabul eder. Belgedeki örnek çözüm tamamen CSS ile yapılır: tablo kaydırılabilir bir blok gibi davranacak şekilde ayarlanır, başlık hücreleri de kaydırma sırasında üstte sabit kalır. MDN bu yöntemde tablonun bütünlüğünden bir miktar ödün verildiğini de belirtir. HTML tarafında yapmanız gereken, yapıyı doğru kurmaktır; görünüm ayarları CSS'in işidir. Mobil uyumluluğun genel ilkeleri için mobil uyumluluk yazısına bakabilirsiniz.

Uygulama: haftalık ders programı

Öğrendiklerimizi tek bir örnekte birleştirelim. Haftalık bir ders programında günler sütun başlığı, ders saatleri satır başlığıdır. Her hücrenin anlamı "hangi gün, hangi saat" sorusuna bağlı olduğu için bu içerik tam bir tablo örneğidir:

html
<table>
  <caption>9-A sınıfı haftalık ders programı</caption>
  <thead>
    <tr>
      <th scope="col">Saat</th>
      <th scope="col">Pazartesi</th>
      <th scope="col">Salı</th>
      <th scope="col">Çarşamba</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row"><time datetime="08:30">08.30</time></th>
      <td>Matematik</td>
      <td>Fizik</td>
      <td>Tarih</td>
    </tr>
    <tr>
      <th scope="row"><time datetime="09:20">09.20</time></th>
      <td>Türkçe</td>
      <td>Kimya</td>
      <td>Biyoloji</td>
    </tr>
  </tbody>
</table>

Adım adım bakarsak: <caption> tablonun konusunu ilk satırda söylüyor, böylece ekran okuyucu kullanan biri tabloya girmeden işine yarayıp yaramadığına karar verebiliyor. <thead> içindeki satır günleri, <tbody> içindeki her satırın ilk hücresi saati başlık olarak taşıyor. Saat hücrelerinde <time> kullanmak, <th> içine akış içeriği konabildiği için geçerlidir. Her satırda dört hücre var; satırlar arasında hücre sayısı tutarlı olduğu için tarayıcının boşluk doldurmasına gerek kalmıyor.

Sık yapılan hatalar

Sık yapılan hatalar: Başlıkları td ile yazmak, table > tr seçicisine güvenmek, tr içine başka öğe koymak, İkinci bir thead eklemek
  • Başlıkları td ile yazmak: Kalın yazılmış bir <td> görsel olarak başlığa benzer ama columnheader ya da rowheader rolü taşımaz.
  • table > tr seçicisine güvenmek: Tarayıcının eklediği örtük <tbody> yüzünden bu seçici satırları yakalamayabilir. Gövdeyi açıkça yazın.
  • tr içine başka öğe koymak: Satırın doğrudan çocukları yalnızca <td>, <th>, <script> ve <template> olabilir.
  • İkinci bir thead eklemek: Tabloda tek bir <thead> olabilir; bölüm başlıkları için gövde içinde <th> içeren bir satır kullanın.
  • tfoot'u eski HTML4 sırasıyla yazmak: Bugünkü kurala göre <tfoot> gövdeden sonra gelir.
  • th içine başlık öğesi koymak: <h2> ya da <hgroup> gibi başlık içeriği <th> içinde izinli değildir.
  • Satırlarda hücre sayısını tutturamamak: Eksik hücrenin yeri MDN'ye göre anonim bir hücre varmış gibi çizilir; tablo hata vermez ama sütunlar kayabilir. Her satırdaki hücreleri (birleştirmeleri hesaba katarak) sayın.
  • Tabloyu sayfa düzeni için kullanmak: Veri olmayan içeriği tabloya koymak, yardımcı teknolojilere yanlış bir yapı bildirir.

Sık Sorulan Sorular

tbody yazmak zorunlu mu?

Hayır. <tr> öğelerini doğrudan <table> içine yazarsanız tarayıcı bir <tbody> ekler. Ancak bu örtük ekleme CSS seçicilerini ve betikleri şaşırtabileceği için MDN gövdeyi açıkça yazmanın istenmeyen sonuçları önlediğini belirtir.

Bir tabloda kaç thead ve tbody olabilir?

<thead> ve <tfoot> en fazla birer tane olabilir. <tbody> ise art arda geldikleri sürece birden fazla olabilir ve uzun tabloları gruplamak için kullanılır.

Tablonun içine başka bir tablo koyabilir miyim?

<td> akış içeriği kabul ettiği ve <table> akış içeriği olduğu için iç içe tablo sözdizimi açısından geçerlidir. Yine de MDN karmaşık tabloların yardımcı teknolojiler için zor olduğunu, mümkünse içeriği daha küçük ve ilişkili tablolara bölmeyi önerir.

Tabloya kenarlık ve boşluğu nasıl veririm?

border, cellpadding ve cellspacing kullanımdan kalkmıştır. Kenarlık, iç boşluk ve hücre aralığı CSS'in border, padding ve border-spacing özellikleriyle verilir.

Hücreleri nasıl birleştiririm?

<td> ve <th> üzerindeki colspan ve rowspan öznitelikleriyle. Değer sınırları, rowspan="0" davranışı ve hücre sayma yöntemi colspan ve rowspan yazısında anlatılıyor.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: HTML öğrenme rehberi · colspan, rowspan, scope ve caption · Semantik etiketler · time ve data etiketi · ARIA nedir · Kullanımdan kalkan etiketler · Sık yapılan HTML hataları