CSS özgüllüğü (specificity), aynı öğeye aynı özellik için değer veren ve aynı kaynakla katmanda bulunan kurallardan hangisinin kazanacağını belirleyen ağırlıktır. Ağırlık üç sütunlu bir sayı olarak yazılır: ID-CLASS-TYPE. ID seçicileri birinci sütuna, class, attribute ve pseudo-class seçicileri ikinci sütuna, tür seçicileri ve pseudo-element'ler üçüncü sütuna birer puan ekler. Karşılaştırma soldan sağa yapılır; birinci sütunda büyük olan, diğer sütunlar ne olursa olsun kazanır.

Bu yazıda her seçici türünün hangi sütuna sayıldığını, :is(), :not(), :has() ve :where() gibi istisnaları ve adım adım çözülmüş hesap örneklerini bulacaksınız. Özgüllüğün cascade içindeki yeri, yani neden kaynak ve katman sırasından sonra devreye girdiği cascade ve kalıtım yazısında anlatılıyor.

Özgüllük ne zaman devreye girer?

MDN önemli bir sınırı baştan vurgular: tarayıcı özgüllüğe ancak cascade kaynağını ve önemini belirledikten sonra bakar. Yani özgüllük yalnızca o özellik için öncelikli olan aynı kaynak ve aynı katmandaki seçiciler arasında karşılaştırılır. Kullanıcı stil sayfasındaki çok özgül bir seçici, yazar stil sayfasındaki basit bir seçiciyi yenemez; katmansız bir kural da katmanlı ve daha özgül bir kuralı normal bildirimlerde yener. Özgüllükler eşitse önce @scope yakınlığına, sonra görünüm sırasına bakılır.

Üç sütun: hangi seçici nereye sayılır?

Üç sütun: hangi seçici nereye sayılır? #ornek gibi ID seçicileri, CLASS, TYPE, Değer yok
SütunSayılan seçicilerHer biri için
ID#ornek gibi ID seçicileri1-0-0
CLASSclass (.ad), attribute ([type="radio"]) ve pseudo-class (:hover, :nth-of-type(3n), :required)0-1-0
TYPEtür seçicileri (p, h1, td) ve pseudo-element (::before, ::placeholder)0-0-1
Değer yokevrensel seçici (*) ve :where() ile parametreleri0-0-0

Birleştiriciler (+, >, ~, boşluk ve ||) seçimi daraltsa da ağırlığa hiçbir şey eklemez. CSS nesting'deki & seçicisi de kendi başına ağırlık getirmez, ama iç içe yazılan kurallar getirir. Evrensel seçici ve :where() öğelerle eşleşir, sadece ağırlıkları sıfırdır.

Adım adım hesap örnekleri

Bir seçicinin özgüllüğünü hesaplamak için seçiciyi parçalarına ayırın, her parçayı ilgili sütuna yazın ve sütunları ayrı ayrı toplayın. Aşağıdaki örneklerde bu işlemi açıkça gösteriyoruz:

css
#nav .menu li a:hover { }
/* ID:    #nav            = 1
   CLASS: .menu, :hover   = 2
   TYPE:  li, a           = 2
   sonuç: 1-2-2 */

ul li::before { }
/* ID: 0   CLASS: 0
   TYPE: ul, li, ::before = 3
   sonuç: 0-0-3 */

input[type="password"]:required { }
/* ID: 0
   CLASS: [type="password"], :required = 2
   TYPE:  input = 1
   sonuç: 0-2-1 */

* > p { }
/* * ve > ağırlık eklemez, yalnızca p sayılır
   sonuç: 0-0-1 */

Seçici listesi içeren kurallarda ağırlık, öğeyle eşleşen seçiciden gelir. MDN'in örneğinde [type="password"], input:focus, :root #myApp input:required listesi için bir şifre alanı odakta değilse ağırlık 0-1-0, odaktaysa 0-1-1 olur. Alan id="myApp" olan bir öğenin içinde ve zorunluysa üçüncü seçici de eşleşir ve en yüksek ağırlık olan 1-2-1 kullanılır.

Soldan sağa karşılaştırma

Ağırlıklar ondalık bir sayı gibi toplanmaz; sütunlar sırayla karşılaştırılır. Birinci sütunda fark varsa karar orada verilir. MDN'in vurguladığı gibi, TYPE sütununda 150 tane bile olsa bu değer CLASS sütunundaki tek bir puanı geçemez.

css
#myElement {
  color: green;   /* 1-0-0: kazanır */
}
.bodyClass .sectionClass .parentClass [id="myElement"] {
  color: yellow;  /* 0-4-0 */
}

:root input {
  color: green;   /* 0-1-1: CLASS sütunu büyük olduğu için kazanır */
}
html body main input {
  color: yellow;  /* 0-0-4 */
}

input.myClass {
  color: yellow;  /* 0-1-1 */
}
:root input {
  color: green;   /* 0-1-1: eşit, sonra yazıldığı için kazanır */
}

Uygulamalı örnek: bir menü bağlantısının rengi

Aşağıdaki yapıda aynı bağlantıya üç kural renk veriyor. Hangisinin kazanacağını adım adım bulalım.

html
<nav id="nav">
  <ul class="menu">
    <li><a class="aktif" href="#">Ana sayfa</a></li>
  </ul>
</nav>
css
#nav a {
  color: red;     /* 1-0-1 */
}
.menu .aktif {
  color: blue;    /* 0-2-0 */
}
nav ul li a.aktif {
  color: green;   /* 0-1-4 */
}
/* sonuç: kırmızı */

Birinci sütunu karşılaştırın: yalnızca #nav a kuralında bir ID var (1 ile 0 ve 0). Karar burada verilir ve bağlantı kırmızı olur; diğer sütunlara bakılmaz. Şimdi ilk kuralı [id="nav"] a olarak yeniden yazdığımızı düşünelim. ID seçici attribute seçicisine dönüştüğü için bu kuralın ağırlığı 0-1-1 olur. Artık birinci sütunlar eşit (hepsi 0); ikinci sütunda değerler 1, 2 ve 1'dir. En büyük değer .menu .aktif kuralında olduğu için bağlantı mavi olur. Üçüncü kuralın dört tür seçicisi, CLASS sütunundaki farkı kapatamaz.

:is(), :not(), :has() ve nesting istisnası

:is(), :not() ve :has() pseudo-class olmalarına rağmen kendileri ağırlık eklemez. Ağırlık, parantez içine verilen seçici listesindeki en özgül seçiciden gelir.

css
p              { } /* 0-0-1 */
:is(p)         { } /* 0-0-1 */
h2:has(~ h2)   { } /* 0-0-2 */
div.outer p    { } /* 0-1-2 */
div:not(.inner) p { } /* 0-1-2 */

/* listedeki en ağır parametre sayılır */
:is(p, #fakeId) { }        /* 1-0-0 */
p:not(#fakeId) { }         /* 1-0-1 */
div:not(.inner, #fakeId) p { } /* 1-0-2 */

Örnek bir hesap: :is(.kart, #ozel) p. Parantezin içindeki adaylar .kart (0-1-0) ve #ozel (1-0-0); en ağırı 1-0-0'dır. Buna p için 0-0-1 eklenir ve sonuç 1-0-1 olur. Dikkat edin: öğe aslında .kart ile eşleşse bile :is() listedeki en yüksek ağırlığı kullanır. Bu davranış, kuralın beklediğinizden güçlü çıkmasına yol açabilir.

CSS nesting de aynı şekilde çalışır. p, #fakeId { span { } } yazımı hem p span hem #fakeId span için 1-0-1 ağırlığı üretir; bu, :is(p, #fakeId) span ile aynıdır. Nesting'in ayrıntıları CSS nesting yazısında.

:where() ile ağırlığı sıfırlamak

:where() içine ne yazarsanız yazın ağırlığı her zaman 0-0-0 olur. Bu sayede bir seçiciyi hedef açısından çok kesin, ağırlık açısından çok hafif yapabilirsiniz. MDN, başkalarının düzenleyemeyeceği CSS yazarken (örneğin bir tema ya da bileşen kütüphanesi) özgüllüğü olabildiğince düşük tutmayı iyi bir uygulama olarak gösterir.

css
/* tema CSS'i */
:where(#defaultTheme) a {
  color: red;     /* 0-0-1 */
}

/* temayı kullanan geliştirici kolayca ezer */
footer a {
  color: blue;    /* 0-0-2 */
}

:is() ile :where() arasındaki temel fark budur: ikisi de aynı öğeleri seçer, ama :is() en ağır argümanının ağırlığını taşır, :where() hiçbir ağırlık taşımaz. Bu iki pseudo-class'ın diğer kullanım desenleri :has(), :is(), :where() ve :not() yazısında anlatılıyor.

Satır içi stiller ve !important

Bir öğeye style niteliğiyle yazılan stiller, yazar stil sayfalarındaki normal bildirimlerin hepsini ezer. MDN bunları 1-0-0-0 ağırlığında, yani üç sütunun da önüne geçen dördüncü bir sütun gibi düşünmeyi önerir. Normal bir satır içi stili ezmenin tek yolu !important kullanmaktır.

!important teknik olarak özgüllüğün parçası değildir, ama onunla doğrudan etkileşir: aynı kaynak ve katmandaki çakışmada önemli bildirim, seçicinin ağırlığı ne olursa olsun kazanır. Çakışan iki bildirim de önemliyse bu kez aralarında yine özgüllük karar verir. MDN, özgüllüğü aşmak için !important kullanmayı kötü bir uygulama olarak niteler; zorunlu kaldığınızda nedenini açıklayan bir yorum eklemenizi önerir.

css
#myElement#myElement#myElement .myClass.myClass p:hover {
  color: blue;    /* 3-3-1 ama normal bildirim */
}
p {
  color: red !important;
}
/* sonuç: paragraf her durumda kırmızı */

Örnekteki ilk seçicinin ağırlığını da hesaplayalım: üç kez yinelenen #myElement ID sütununa 3, iki kez yinelenen .myClass ile :hover CLASS sütununa 3, p TYPE sütununa 1 ekler; sonuç 3-3-1 olur. Bu çok yüksek bir ağırlıktır; yine de normal bir bildirim olduğu için aynı kaynak ve katmandaki önemli bildirimi yenemez. MDN bu örneği, seçiciyi gereğinden fazla özgül yazmanın (over-specifying) işe yaramadığını göstermek için kullanır.

JavaScript ile eklenen satır içi stilleri ezmek

MDN, birçok JavaScript çerçevesinin ve kütüphanesinin öğelere satır içi stil eklediğini hatırlatır. Bu stilleri değiştiremiyorsanız, satır içi stili hedefleyen bir attribute seçicisiyle birlikte !important kullanmak bir çözüm yoludur:

html
<p style="color: purple">...</p>
css
/* satır içi mor rengi ezmek için: bir kütüphane bu stili ekliyor */
p[style*="purple"] {
  color: rebeccapurple !important;
}

Seçicideki [style*="purple"] parçası, style niteliğinin değerinde purple geçen paragrafları seçer; böylece kural yalnızca sorunlu öğeleri hedefler. MDN, önemli işaretini her kullandığınızda kodu sonradan okuyacak kişiler için neden bu yola başvurduğunuzu açıklayan bir yorum eklemenizi özellikle önerir.

!important bayrağından kurtulmanın yolları

!important bayrağından kurtulmanın yolları: Önceden önemli olan bildirimin seçicisini, rakip bildirimlerden daha özgül..., Aynı özgüllükte yazıp ezmek istediğiniz bildirimden...

En iyi yaklaşım !important kullanmamaktır. Stil dosyanızda bu bayrağı görüp kaldırmak istediğinizde MDN üç seçenek sunar:

  • Önceden önemli olan bildirimin seçicisini, rakip bildirimlerden daha özgül olacak biçimde güçlendirin.
  • Aynı özgüllükte yazıp ezmek istediğiniz bildirimden sonra konumlandırın; eşitlikte sonra gelen kazanır.
  • Ezmeye çalıştığınız seçicinin özgüllüğünü düşürün; örneğin bir ID seçiciyi :where() içine alın.

Başka bir yazarın stil dosyasındaki !important bayraklarını kaldıramıyorsanız, onları ezmenin tek yolu sizin de önemli bildirim kullanmanızdır. MDN bunun için önemli bildirimleri bir cascade katmanında toplamayı önerir. Önemli bildirimlerde katman sırası tersine döndüğü için ilk tanımlanan katmandaki önemli bildirim en güçlüsüdür:

css
/* stil dosyasının en başında: önce bu katman tanımlanır */
@layer importantOverrides;

[id="myElement"] p {
  /* normal stiller burada */
}

@layer importantOverrides {
  [id="myElement"] p {
    /* yalnızca önemli (!important) bildirimler burada */
  }
}

Katmanın içindeki seçicinin özgüllüğü düşük olabilir; yeter ki ezmek istediğiniz öğeyle eşleşsin. Aynı sonuca, yalnızca önemli bildirimlerden oluşan kısa bir dosyayı ilk @import satırı olarak layer() ile içe aktararak da ulaşabilirsiniz.

Alıştırma: bu seçicilerin ağırlığı nedir?

Alıştırma: bu seçicilerin ağırlığı nedir? .btn, a:hover, nav > ul li, #app .kart h2

Aşağıdaki tabloyu önce sağ sütunu kapatarak kendiniz doldurun, sonra yanıtları karşılaştırın. Her satırda ID, CLASS ve TYPE parçalarını ayrı ayrı saydık.

SeçiciSayılan parçalarAğırlık
.btnbir class0-1-0
a:hoverbir pseudo-class, bir tür0-1-1
nav > ul liüç tür; birleştiriciler sayılmaz0-0-3
#app .kart h2bir ID, bir class, bir tür1-1-1
li:nth-child(2n)::markerbir pseudo-class; li ve ::marker tür sütununa0-1-2
a[href$=".pdf"]bir attribute, bir tür0-1-1
:where(.tema) .btn:where sıfır, bir class0-1-0
:not(#a, .b) span:not içindeki en ağır parça #a, bir tür1-0-1

Neden özgüllüğü düşük tutmalı?

MDN'in özgüllük sorunlarına dair önerisi açıktır: !important yerine cascade katmanlarını kullanın ve stil dosyanızın genelinde düşük ağırlıklı seçiciler yazın; böylece bir stili ezmek gerektiğinde biraz daha özgül bir kural yeterli olur. Anlamlı (semantik) HTML de buna yardımcı olur, çünkü stil verebileceğiniz doğal bağlam noktaları sağlar.

Aynı öğeyi farklı ağırlıklarla hedefleyebileceğinizi şu örnek gösterir. Üç kural da id="myContent" olan main öğesinin içindeki başlığı seçer:

css
#myContent h1 {
  color: green;   /* 1-0-1 */
}
[id="myContent"] h1 {
  color: yellow;  /* 0-1-1 */
}
:where(#myContent) h1 {
  color: blue;    /* 0-0-1 */
}
/* sonuç: kuralların sırası ne olursa olsun başlık yeşil */

Üç seçici de hedef açısından aynı kesinliktedir, ama ağırlıkları 1-0-1, 0-1-1 ve 0-0-1 olarak üç farklı düzeydedir. Hangisini seçeceğiniz, o kuralın ileride ne kadar kolay ezilebilmesini istediğinize bağlıdır.

Özgüllüğü bilinçli yönetmek

  • ID yerine attribute seçicisi: [id="icerik"] h1 aynı öğeyi seçer ama ağırlığı 0-1-1'dir; #icerik h1 ise 1-0-1.
  • Seçiciyi yinelemek: .btn.btn yazımı ağırlığı 0-2-0'a çıkarır. MDN bunu kontrolünüz dışındaki çok özgül seçicileri ezmek için son çare olarak gösterir ve mutlaka yorum eklemenizi önerir.
  • Katman kullanmak: Üçüncü taraf stillerini bir cascade katmanına almak, özgüllük yarışına girmeden onların önüne geçmenizi sağlar. Bkz. @layer.
  • Ağaç yakınlığının önemsiz olduğunu bilmek: body h1 ile html h1 eşittir; öğeye ağaçta daha yakın olan seçici kazanmaz, sonra yazılan kazanır.

@scope bloğu içine yazılan kurallar da seçicinin özgüllüğünü değiştirmez. Ancak kuralın içinde açıkça :scope yazarsanız bu pseudo-class her pseudo-class gibi 0-1-0 ekler; örneğin :scope img ağırlığı 0-1-1 olur. Ayrıntılar @scope yazısında.

Sık yapılan hatalar

  • Ağırlıkları tek bir sayı gibi toplamak: 0-0-11 hiçbir zaman 0-1-0'ı geçmez.
  • Kalıtımı özgüllükle karıştırmak: Atadan kalıtılan bir değer, öğeyi doğrudan hedefleyen en zayıf kurala bile yenilir.
  • :is() içine ID koyup ağırlığı unutmak: Listedeki tek bir ID bütün kuralı 1-x-x düzeyine çıkarır.
  • Farklı katmanlardaki kuralları özgüllükle kıyaslamak: Katman sırası özgüllükten önce gelir.

Sık Sorulan Sorular

Evrensel seçici * özgüllüğe katkı yapar mı?

Hayır, * ağırlığı 0-0-0'dır. Öğelerle eşleşir ama hesaba hiçbir puan eklemez.

Pseudo-class ile pseudo-element aynı sütuna mı sayılır?

Hayır. Pseudo-class'lar (:hover) CLASS sütununa, pseudo-element'ler (::before) TYPE sütununa sayılır. İstisnalar :is(), :not(), :has() ve :where()'dır.

İki kuralın ağırlığı eşitse ne olur?

Tarayıcı bir sonraki adıma geçer: iki kural da @scope içindeyse kapsam köküne daha yakın olan kazanır; yakınlık da eşitse ya da kapsam kullanılmıyorsa belgede sonra gelen bildirim uygulanır. Bu yüzden aynı ağırlıktaki kurallarda dosyadaki sıralama doğrudan sonucu belirler.

!important özgüllüğü artırır mı?

Hayır. Bildirimi önemli kategorisine taşır ve cascade sırasını değiştirir; özgüllük yalnızca iki önemli bildirim çakıştığında yeniden devreye girer.

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: CSS öğrenme rehberi · Cascade ve kalıtım · CSS seçiciler · :is() ve :where() · @layer katmanları