Tailwind CSS v4'te kendi utility sınıfınızı @utility direktifiyle tanımlarsınız. @utility content-auto { content-visibility: auto; } yazdığınızda content-auto sınıfı hazır sınıflar gibi davranır: yalnızca kullanıldığında üretilir, utilities katmanına girer ve hover:, md: gibi bütün varyantlarla kendiliğinden çalışır. Adın sonuna -* eklediğinizde ise sekme-2, sekme-[12], sekme-github gibi değer alan fonksiyonel bir sınıf ailesi kurarsınız; değeri --value(), bölü işaretinden sonraki kısmı --modifier() okur.
Bu yazıda basit ve iç içe utility'leri, fonksiyonel utility'lerdeki tema anahtarı, çıplak değer, sabit kelime ve keyfi değer türlerini, varsayılan değeri, modifier'ı, negatif değerleri ve oranları v4.3.5 ile derleyerek gösteriyoruz. Derleme sırasında bulduğumuz bir ayrıntıyı da paylaşıyoruz: belgedeki tab-* örneğinin adı v4.3.5'te zaten yerleşik bir sınıfa ait. Serinin tamamı için Tailwind öğrenme rehberine bakabilirsiniz.
Basit utility: tek sınıf, tek kural
v3'te özel sınıflar @layer utilities içine yazılırdı. Yükseltme rehberine göre v4 yerel CSS katmanlarını kullandığı için bu yöntemin yerini @utility aldı. Aşağıdaki dosyada bir basit, bir de iç içe seçici içeren utility tanımladık:
@import "tailwindcss" source(none);
@source inline("content-auto hover:content-auto md:scrollbar-gizli");
@utility content-auto {
content-visibility: auto;
}
@utility scrollbar-gizli {
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}v4.3.5 ile derlenen CSS:
@layer utilities {
.content-auto {
content-visibility: auto;
}
@media (hover: hover) {
.hover\:content-auto:hover {
content-visibility: auto;
}
}
@media (width >= 48rem) {
.md\:scrollbar-gizli {
scrollbar-width: none;
}
.md\:scrollbar-gizli::-webkit-scrollbar {
display: none;
}
}
}Hiçbir varyant tanımlamadığımız hâlde hover:content-auto ve md:scrollbar-gizli doğru biçimde üretildi. İç içe &::-webkit-scrollbar bloğu da varyantla birlikte taşındı: .md\:scrollbar-gizli::-webkit-scrollbar seçicisi medya sorgusunun içinde. Bu, @utility'nin @layer components içindeki sıradan bir sınıftan temel farkıdır; bileşen katmanındaki bir sınıf varyant almaz (@apply ve bileşen sınıfları).
Utility adı kuralları
Ad, nokta ile başlayan bir seçici değil, sınıfın kendisidir. @utility .kart { padding: 1rem; } yazdığımızda derleme şu mesajla durdu:
Error: `@utility .kart` defines an invalid utility name. Utilities should be alphanumeric and start with a lowercase letter.Mesaja göre ad küçük harfle başlamalı ve harf ile rakamlardan oluşmalıdır; tire ile ayrılmış adlar (scrollbar-gizli) sorunsuz derlendi.
Fonksiyonel utility ve --value()

Adın sonundaki -*, sınıfın bir değer alacağını söyler. Değerin nereden geleceğini bildirimin içindeki --value() belirler. Belgedeki örnekler tab-* adını kullanır, fakat derlerken fark ettik ki v4.3.5'te tab-2, tab-4 ve tab-[12] hiçbir özel tanım olmadan tab-size kuralı üretiyor; tema ad alanı tablosunda da --tab-size-* yer alıyor. Aynı adla bir @utility tab-* eklediğimizde çıktıda aynı seçicinin iki kez yazıldığı kurallar gördük. Bu yüzden örneklerde çakışmayan sekme-* adını kullanıyoruz.
1. Tema anahtarı: --value(--ad-alani-*)
@import "tailwindcss" source(none);
@source inline("sekme-github sekme-genis sekme-2");
@theme {
--sekme-github: 8;
--sekme-genis: 12;
}
@utility sekme-* {
tab-size: --value(--sekme-*);
}@layer utilities {
.sekme-genis {
tab-size: var(--sekme-genis);
}
.sekme-github {
tab-size: var(--sekme-github);
}
}Yalnızca temada karşılığı olan sekme-github ve sekme-genis üretildi; sekme-2 için kural yok, çünkü --sekme-2 diye bir değişken tanımlanmadı. Değer doğrudan kopyalanmıyor, var(--sekme-genis) olarak değişkene bağlanıyor; böylece değişkeni değiştirdiğinizde sınıf da değişir. Tema ad alanlarının genel mantığı @theme direktifi yazısında.
2. Çıplak değer, sabit kelime ve keyfi değer bir arada
--value(integer) köşeli parantezsiz tam sayıları (sekme-2), --value([integer]) köşeli parantezli değerleri (sekme-[12]), --value("inherit") ise yalnızca yazılan kelimeyi kabul eder. Belge, birden çok kaynağı tek --value() içinde soldan sağa deneyebileceğinizi söyler. --default() ise hiç değer verilmediğinde (çıplak sekme) kullanılacak değeri belirler:
@import "tailwindcss" source(none);
@source inline("sekme sekme-2 sekme-[12] sekme-github sekme-yok sekme-inherit md:sekme-6 hover:sekme-2");
@theme {
--sekme-github: 8;
}
@utility sekme-* {
tab-size: --value(--sekme-*, integer, [integer], "inherit", "initial");
tab-size: --value(--default(4));
}@layer utilities {
.sekme {
tab-size: 4;
}
.sekme-2 {
tab-size: 2;
}
.sekme-\[12\] {
tab-size: 12;
}
.sekme-github {
tab-size: var(--sekme-github);
}
.sekme-inherit {
tab-size: inherit;
}
@media (hover: hover) {
.hover\:sekme-2:hover {
tab-size: 2;
}
}
@media (width >= 48rem) {
.md\:sekme-6 {
tab-size: 6;
}
}
}Çıktı her türün nasıl eşleştiğini gösteriyor. sekme-yok hiçbir türe uymadığı için hata vermeden atlandı; belgedeki ifadeyle çözülemeyen bildirimler çıktıya konmaz ve hiçbir bildirimi çözülemeyen sınıf hiç üretilmez. md:sekme-6 ve hover:sekme-2 yine kendiliğinden çalıştı. Çıplak değer türleri olarak belge number, integer, ratio ve percentage sayar; keyfi değer türleri ise length, color, angle, url gibi daha geniş bir listedir ve [*] her şeyi kabul eder.
| Yazım | Kabul ettiği | Örnek sınıf |
|---|---|---|
| --value(--sekme-*) | Temada tanımlı anahtar | sekme-github |
| --value(integer) | Köşeli parantezsiz tam sayı | sekme-2 |
| --value([integer]) | Köşeli parantezli tam sayı | sekme-[12] |
| --value("inherit") | Yalnızca bu kelime | sekme-inherit |
| --value([length]) | Köşeli parantezli uzunluk | kaydir-[2rem] |
| --value(ratio) | Kesir biçimi, değer ve modifier birlikte | oran-3/4 |
| --default(4) | Değer yokken kullanılacak sabit | sekme |
3. Tür seçimi: integer mi, number mı?
Çıplak değer türleri birbirinin yerine geçmez. Aynı dosyada biri integer, diğeri number kabul eden iki utility tanımlayıp ondalık değer verdik:
@import "tailwindcss" source(none);
@source inline("sekme-1.5 sekme-2 seyrek-1.5");
@utility sekme-* { tab-size: --value(integer); }
@utility seyrek-* { line-height: --value(number); }@layer utilities {
.seyrek-1\.5 {
line-height: 1.5;
}
.sekme-2 {
tab-size: 2;
}
}sekme-1.5 üretilmedi, çünkü 1.5 bir tam sayı değil; seyrek-1.5 ise number türü ondalığı kabul ettiği için line-height: 1.5 aldı. Sınıf adındaki noktanın seçicide \. olarak kaçışlandığına da dikkat edin. Değerin CSS'te anlamlı olduğu aralığı düşünerek türü seçin: sekme genişliği gibi sayılabilir değerler için integer, oran ya da çarpan gibi ondalık alabilecek değerler için number, yüzde için percentage.
Modifier: bölü işaretinden sonrası

--modifier(), --value() gibi çalışır ama sınıftaki / işaretinden sonraki kısmı okur. Modifier verilmezse ona bağlı bildirim düşer. Yerleşik text-lg/7 kalıbını taklit eden bir örnek:
@import "tailwindcss" source(none);
@source inline("yazi-lg yazi-lg/7 yazi-[15px]/[20px] yazi-lg/sikisik");
@utility yazi-* {
font-size: --value(--text-*, [length]);
line-height: --modifier(--leading-*, integer, [length]);
}@layer utilities {
.yazi-\[15px\]\/\[20px\] {
font-size: 15px;
line-height: 20px;
}
.yazi-lg\/7 {
font-size: var(--text-lg);
line-height: 7;
}
.yazi-lg {
font-size: var(--text-lg);
}
}yazi-lg modifier'sız olduğu için yalnızca font-size aldı. yazi-lg/7 çıplak tam sayıyı satır yüksekliğine yazdı. yazi-lg/sikisik ise hiç üretilmedi: --leading-sikisik diye bir tema anahtarı yok ve kelime diğer türlere de uymuyor. Tek bir geçersiz parça sınıfın tamamını düşürdü; bu, çıktıda bir sınıf aradığınızda akılda tutulması gereken bir davranıştır. Burada --text-* varsayılan temadan geldiği için --text-lg değişkeni (1.125rem) tema katmanına eklendi.
Negatif değerler ve oranlar
Belgeye göre negatif değerler için ayrı bir -ad-* utility'si tanımlanır. Aşağıdaki örnekte mantıksal başlangıç kenarına boşluk ölçeğinden ya da keyfi değerden margin veren bir aile ve bir en boy oranı sınıfı var. --spacing() fonksiyonu sayıyı boşluk ölçeğine çevirir; ayrıntısı --spacing() ve --alpha() yazısında.
@import "tailwindcss" source(none);
@source inline("kaydir-4 -kaydir-4 kaydir-[10%] -kaydir-[2rem] oran-3/4 oran-[7/9] oran-kare");
@theme {
--oran-kare: 1 / 1;
}
@utility kaydir-* {
margin-inline-start: --spacing(--value(integer));
margin-inline-start: --value([percentage], [length]);
}
@utility -kaydir-* {
margin-inline-start: --spacing(--value(integer) * -1);
margin-inline-start: calc(--value([percentage], [length]) * -1);
}
@utility oran-* {
aspect-ratio: --value(--oran-*, ratio, [ratio]);
}@layer utilities {
.-kaydir-4 {
margin-inline-start: calc(var(--spacing) * 4 * -1);
}
.-kaydir-\[2rem\] {
margin-inline-start: calc(2rem * -1);
}
.kaydir-4 {
margin-inline-start: calc(var(--spacing) * 4);
}
.kaydir-\[10\%\] {
margin-inline-start: 10%;
}
.oran-3\/4 {
aspect-ratio: 3 / 4;
}
.oran-\[7\/9\] {
aspect-ratio: 7/9;
}
.oran-kare {
aspect-ratio: var(--oran-kare);
}
}ratio türü değeri ve modifier'ı tek parça sayar: oran-3/4 modifier olarak değil 3 / 4 oranı olarak okundu. Köşeli parantezli oran-[7/9] ise yazıldığı gibi 7/9 kaldı. Negatif aile ayrı tanımlandığı için -kaydir-4 kendi calc(... * -1) bildirimini aldı.
Uygulamalı örnek: metin konturu ailesi
Şimdi öğrendiklerimizi bir araya getiren küçük bir aile kuralım. Amaç, başlıklara kontur (dış çizgi) ekleyen kontur-* sınıfları: kalınlık temadaki adlandırılmış değerlerden ya da keyfi bir uzunluktan, renk ise modifier olarak varsayılan renk paletinden ya da keyfi bir renkten gelsin. Bu sefer modifier'ın kaynağı olarak hazır --color-* ad alanını kullanıyoruz; yani /sky-500 yazdığınızda paletteki gök mavisi okunur.
@import "tailwindcss" source(none);
@source inline("kontur-ince kontur-kalin/sky-500 kontur-[2px]/[#ff0000] md:kontur-kalin kontur-orta");
@theme {
--kontur-ince: 1px;
--kontur-kalin: 3px;
}
@utility kontur-* {
-webkit-text-stroke-width: --value(--kontur-*, [length]);
-webkit-text-stroke-color: --modifier(--color-*, [color]);
}v4.3.5 ile derlenen CSS:
@layer theme {
:root, :host {
/* (kısaltıldı: --font-sans, --font-mono ve varsayılan font değişkenleri) */
--color-sky-500: oklch(68.5% 0.169 237.323);
--kontur-ince: 1px;
--kontur-kalin: 3px;
}
}
@layer utilities {
.kontur-\[2px\]\/\[\#ff0000\] {
-webkit-text-stroke-width: 2px;
-webkit-text-stroke-color: #ff0000;
}
.kontur-kalin\/sky-500 {
-webkit-text-stroke-width: var(--kontur-kalin);
-webkit-text-stroke-color: var(--color-sky-500);
}
.kontur-ince {
-webkit-text-stroke-width: var(--kontur-ince);
}
@media (width >= 48rem) {
.md\:kontur-kalin {
-webkit-text-stroke-width: var(--kontur-kalin);
}
}
}Sonucu adım adım okuyalım. kontur-ince modifier almadığı için yalnızca kalınlık bildirimini aldı; renk satırı düştü ve öğe kendi metin rengini korur. kontur-kalin/sky-500 iki tema değişkenine bağlandı ve kullanılan --color-sky-500 tema katmanına eklendi. Tamamen keyfi kontur-[2px]/[#ff0000] değerleri olduğu gibi kopyaladı. md:kontur-kalin varyantla çalıştı. Temada --kontur-orta olmadığı için kontur-orta ise hiç üretilmedi. Bu tek tanım, beş farklı yazımı doğru biçimde karşıladı; aynı işi @layer components içinde düz sınıflarla yapmaya kalksaydınız her kalınlık ve renk birleşimi için ayrı bir kural yazmanız gerekirdi.
Kendi ad alanınız mı, hazır ad alanı mı?
Örnekte kalınlık için yeni bir --kontur-* ad alanı açtık, renk için ise hazır --color-* ad alanını okuduk. İki yaklaşımın sonuçları farklıdır. Hazır ad alanını okuyan bir utility, temaya eklediğiniz her yeni değeri kendiliğinden tanır: @theme içine --color-marka: #5b21b6; eklediğimizde kontur-ince/marka sınıfı -webkit-text-stroke-color: var(--color-marka) ile derlendi. Buna karşılık --color-*: initial ile paleti boşaltırsanız utility'niz de etkilenir. Kendi ad alanınız ise yalnızca o utility'ye hizmet eder; değerler birbirine karışmaz, ama her değeri ayrıca tanımlamanız gerekir. Pratik bir kural olarak, renk, boşluk ve yazı boyutu gibi tasarım sisteminin ortak değerlerini hazır ad alanlarından, yalnızca o sınıfa özgü ölçüleri kendi ad alanınızdan okuyun.
v3 alışkanlığı: @layer utilities içine sınıf yazmak
v3'ten taşınan bir dosyada @layer utilities { .eski-sinif { tab-size: 4; } } gibi bir blok kalmış olabilir. Bunu v4.3.5 ile eski-sinif hover:eski-sinif md:eski-sinif kullanımıyla derlediğimizde çıktıda yalnızca düz .eski-sinif kuralı vardı; hover: ve md: sürümleri hiç üretilmedi. v4 yerel CSS katmanlarını kullandığı için bu blok artık sıradan bir katman bloğudur: kural yazılır ama Tailwind onu utility olarak kaydetmez. Varyant desteği isteyen her sınıfı @utility biçimine çevirin. Geçişin diğer adımları Tailwind v4 yenilikleri yazısında.
Sıralama: özel utility'ler nasıl ezilir?
Yükseltme rehberi, v4'te özel utility'lerin tanımladıkları özellik sayısına göre sıralandığını, böylece .btn gibi bileşen benzeri utility'lerin başka utility'lerle ek ayar gerekmeden ezilebildiğini söyler. Üç özellik yazan bir dugme utility'sini p-8 ve rounded-none ile birlikte derledik:
@import "tailwindcss" source(none);
@source inline("dugme p-8 rounded-none");
@utility dugme {
padding: 1rem;
border-radius: 0.5rem;
font-weight: 600;
}@layer utilities {
.dugme {
padding: 1rem;
border-radius: 0.5rem;
font-weight: 600;
}
.rounded-none {
border-radius: 0;
}
.p-8 {
padding: calc(var(--spacing) * 8);
}
}Üç bildirimli .dugme çıktıda en önde, tek bildirimli rounded-none ve p-8 ondan sonra yazıldı. Aynı katmanda ve aynı özgüllükte sonra gelen kural kazandığı için class="dugme p-8" yazdığınızda padding 2rem olur. Sıralamanın genel kuralları çakışan sınıflar yazısında.
Sık yapılan hatalar

- Yerleşik bir adı yeniden kullanmak:
tab-*gibi var olan bir aile adıyla tanım yapmak, aynı seçicinin birden çok kez yazıldığı karışık bir çıktı üretebilir. Kendi ön ekinizi seçin. - Tema anahtarını tanımlamadan --value(--ad-*) kullanmak: sınıf hata vermeden üretilmez.
- Yanlış tür seçmek:
--value(integer)sekme-1.5değerini kabul etmez; ondalık içinnumbertürü gerekir. - Negatif aileyi unutmak:
-kaydir-4ancak@utility -kaydir-*tanımlıysa üretilir. - Utility'yi seçiciyle adlandırmak:
@utility .kartgeçersizdir; ad nokta olmadan yazılır. - Dinamik ad birleştirmek:
sekme-${n}gibi adlar kaynak taramada bulunmaz (@source direktifi).
Sık Sorulan Sorular
@utility ile @apply arasındaki fark nedir?
@utility yeni bir sınıf kaydeder; @apply ise var olan sınıfların bildirimlerini başka bir kurala kopyalar. İkisi birlikte de kullanılabilir: @utility ile kaydettiğiniz sınıfı başka bir kuralda @apply edebilirsiniz.
Özel utility'm varyantlarla çalışır mı?
Evet, ek bir ayar gerekmeden. Yukarıdaki çıktılarda hover: ve md: örnekleri var. Kendi varyantınızı tanımlamak için @custom-variant yazısına bakın.
Utility kullanılmazsa çıktıya girer mi?
Hayır. @utility ile tanımlanan sınıflar da hazır sınıflar gibi yalnızca kaynakta kullanıldığında üretilir. Tarama mantığı class tarama yazısında.
@utility içinde @apply kullanabilir miyim?
Evet. @utility kart-temel { @apply rounded-lg p-4 shadow-sm; } tanımını kart-temel md:kart-temel ile derlediğimizde üç sınıfın bildirimleri .kart-temel kuralına açıldı ve aynı bildirimler @media (width >= 48rem) içindeki .md\:kart-temel kuralında da tekrarlandı. Yani hazır sınıflardan oluşan bir kısayolu, varyant desteğini kaybetmeden tek bir ada toplayabilirsiniz. Yine de aynı birleşimi HTML'de yazmanın daha okunaklı olup olmadığını tartın; kısayol sayısı arttıkça hangi adın ne yaptığını hatırlamak zorlaşır.
Özel utility'ler hangi katmana yazılır?
Belgeye göre @utility ile tanımlanan sınıflar utilities katmanına eklenir; yukarıdaki bütün çıktılarda kurallar @layer utilities bloğunun içinde. Bu yüzden özel sınıflarınız, bileşen katmanındaki kuralları hazır utility'ler gibi ezer ve aynı katmanda özellik sayısına göre sıralanır. Katman düzeninin bütün resmi katmanlar ve özel CSS yazısında.
Tek seferlik bir özellik için de @utility mi yazmalıyım?
Gerekmez. Tek bir yerde kullanılacak bir bildirim için [content-visibility:auto] gibi keyfi özellik sınıfı yeterlidir (arbitrary values). Aynı bildirimi birçok yerde tekrarlıyorsanız @utility daha okunaklıdır.
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: Tailwind öğrenme rehberi · @apply ve bileşen sınıfları · Özel varyantlar · @theme direktifi · --spacing() fonksiyonu · Arbitrary values