Mert Özen Her Satırda Daha İleri
Frontend Tailwind CSS — #03

Theme ve Design Token'lar: @theme Namespace'leri, Özel Renk/Font/Breakpoint ve Dark Mode

Mert Özen 15 Kas 2026 12 dk 4 görüntülenme
Theme ve Design Token'lar: @theme Namespace'leri, Özel Renk/Font/Breakpoint ve Dark Mode

@theme bloğundaki her değişken hem bir utility hem de :root'ta bir CSS değişkeni üretir; değişkenin namespace'i hangi utility'nin doğacağını belirler. Bookstore.Web'de marka token'larını tanımlıyor, varsayılan temayı genişletmek ile --color-*: initial ile sıfırlamayı karşılaştırıyor, @theme inline'ın kurum bazlı renkte neden gerektiğini ve @theme static'i ölçüyor, semantic değişkenler ve head script'iyle FOUC'suz dark mode kuruyor, DTCG token'larından Style Dictionary ile @theme üretiyoruz.

Sürüm notu: Bu yazıdaki örnekler Tailwind CSS 4.3.5, Style Dictionary 5.8, Node.js 24, .NET 10 ve Playwright 1.65 (Chromium 158) ile denenmiştir.

Önce Tahmin Et

Sitedeki serilerin ortak örnek uygulaması Bookstore (bir online kitapçı), Kurumsal Sipariş tarafında her kuruma kendi vurgu rengini veriyor. Okulların portalı yeşil, varsayılan tema indigo. Bunu bir CSS değişkeniyle kurduk ve Tailwind'e bir renk olarak tanıttık:

:root {
    --tenant-accent: var(--color-indigo-600);
}
.tenant-school {
    --tenant-accent: var(--color-emerald-600);
}

@theme {
    --color-accent: var(--tenant-accent);
}

Okul portalındaki "Order for the school" butonu class="bg-accent" taşıyor ve .tenant-school element'inin içinde duruyor. Buton yeşil mi olur, indigo mu?

Bu yazıda Tailwind'in tema sistemini açıyoruz: @theme bloğunun hem utility hem CSS değişkeni üretmesini, namespace'leri, varsayılan temayı genişletmek ile sıfırlamak arasındaki farkı, @theme inline ve @theme static'i, iki stratejiyle dark mode'u ve design token'ları bir JSON dosyasından Style Dictionary ile @theme bloğuna dönüştürmeyi. Token kavramının kendisini ve DTCG (Design Tokens Community Group; token dosya formatını belirleyen, web standartları kuruluşu bünyesindeki topluluk grubu) formatını UI/UX Tasarım İlkeleri serisinin design system yazısında anlatmıştık; burada Tailwind tarafına bakıyoruz.

@theme Ne Üretir?

Bookstore.Web'in markasını üç renk, bir font ve bir radius ile tanımlayalım:

@theme {
    --color-brand-50: oklch(0.97 0.014 264);
    --color-brand-600: oklch(0.54 0.178 264);
    --color-brand-700: oklch(0.47 0.16 264);
    --color-ink-900: oklch(0.21 0.03 264);
    --radius-card: 0.875rem;
    --font-display: Georgia, Cambria, serif;
}

@theme içindeki her değişken iki şey üretiyor. Birincisi bir utility: --color-brand-600 tanımlandığı an bg-brand-600, text-brand-600, border-brand-600 ve renk alan bütün utility'ler kullanılabilir oluyor. İkincisi :root üzerinde gerçek bir CSS değişkeni. Kart partial'ında bg-brand-600 ve rounded-card kullanınca build çıktısı şöyle:

@layer theme {
  :root, :host {
    --color-brand-600: oklch(0.54 0.178 264);
    --color-brand-700: oklch(0.47 0.16 264);
    --color-ink-900: oklch(0.21 0.03 264);
    --radius-card: 0.875rem;
    --font-display: Georgia, Cambria, serif;
    /* ... */
  }
}
@layer utilities {
  .rounded-card {
    border-radius: var(--radius-card);
  }
  .bg-brand-600 {
    background-color: var(--color-brand-600);
  }
}

İki ayrıntı önemli. Utility değeri kopyalamıyor, değişkene bağlanıyor; değişkeni bir alt ağaçta değiştirirsen utility de değişir. Dark mode'u bunun üzerine kuracağız. İkincisi, :root'ta yalnız kullanılan değişkenler var: --color-brand-50 hiçbir yerde kullanılmadığı için CSS'e girmedi. --color-ink-900 ise doğrudan bir class'ta geçmediği hâlde orada, çünkü aşağıda tanımlayacağımız --color-fg ona başvuruyor ve Tailwind bu bağımlılığı takip ediyor.

:root'taki değişkenler dışarıdan da kullanılabilir: @apply olmadan yazılan bir component CSS'inde background: var(--color-brand-600), Razor'da style="border-color: var(--color-line)" ya da JavaScript'te getComputedStyle(document.documentElement).getPropertyValue("--color-brand-600"). Tailwind'in kendi varsayılan teması da aynı mekanizmayla yazılmış; paketteki index.css dosyası paleti @theme default bloğu içinde tanımlıyor ve senin @theme bloğundaki aynı isimli değişken onu eziyor.

Namespace'ler: Değişkenin Adı Hangi Utility'yi Doğurur?

Değişkenin hangi utility'yi ürettiğini adının ön eki, yani namespace'i belirliyor. Her namespace'ten bir örnek tanımlayıp ürettikleri CSS'i bir script'le okuduk:

@theme {
    --color-brand-600: oklch(0.54 0.178 264);
    --font-display: Georgia, Cambria, serif;
    --text-hero: 2.75rem;
    --text-hero--line-height: 1.1;
    --font-weight-heavy: 850;
    --tracking-tightest: -0.04em;
    --leading-cozy: 1.4;
    --breakpoint-3xl: 120rem;
    --container-shelf: 72rem;
    --spacing-gutter: 1.25rem;
    --radius-card: 0.875rem;
    --shadow-card: 0 1px 2px oklch(0 0 0 / 0.06), 0 4px 12px oklch(0 0 0 / 0.08);
    --ease-snappy: cubic-bezier(0.2, 0, 0, 1);
    --animate-pulse-once: pulse 1s ease-in-out 1;
}
node scripts/03-namespaces.mjs
bg-brand-600        → background-color: var(--color-brand-600);
font-display        → font-family: var(--font-display);
text-hero           → font-size: var(--text-hero); line-height: var(--tw-leading, var(--text-hero--line-height));
font-heavy          → --tw-font-weight: var(--font-weight-heavy); font-weight: var(--font-weight-heavy);
tracking-tightest   → --tw-tracking: var(--tracking-tightest); letter-spacing: var(--tracking-tightest);
leading-cozy        → --tw-leading: var(--leading-cozy); line-height: var(--leading-cozy);
3xl:hidden          → @media (width >= 120rem) display: none;
max-w-shelf         → max-width: var(--container-shelf);
p-gutter            → padding: var(--spacing-gutter);
rounded-card        → border-radius: var(--radius-card);
shadow-card         → --tw-shadow: 0 1px 2px var(--tw-shadow-color, oklch(0 0 0 / 0.06)), 0 4px 12px var(--tw-shadow-color, oklch(0 0 0 / 0.08)); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
ease-snappy         → --tw-ease: var(--ease-snappy); transition-timing-function: var(--ease-snappy);
animate-pulse-once  → animation: var(--animate-pulse-once);

Çıktıdan okunabilecek birkaç kural var. --text-hero--line-height gibi çift tireli ek, font boyutuyla birlikte gelen satır yüksekliğini tanımlıyor ve leading-* ile ezilebiliyor (var(--tw-leading, …)). --breakpoint-* bir utility değil bir variant üretiyor: 3xl: artık 120rem ve üstünde çalışan bir media query. --container-* hem max-w-* boyutunu hem de container query variant'larını besliyor. --spacing-gutter gibi isimli bir spacing değeri p-gutter, gap-gutter, w-gutter gibi spacing alan bütün utility'lerde kullanılabiliyor. --shadow-card ise değeri olduğu gibi kopyalamıyor; renk kısmını --tw-shadow-color ile sarıyor ki shadow-brand-600/20 gibi bir renk utility'si gölgenin rengini değiştirebilsin.

Varsayılan Temayı Genişletmek mi, Sıfırlamak mı?

Şu ana kadar yaptığımız genişletmekti: Tailwind'in paleti duruyor, üstüne brand ekledik. Tek bir değeri değiştirmek için de aynı isimle yeniden tanımlamak yetiyor; --breakpoint-sm: 30rem yazarsan sm: artık 480px'te başlar.

Bir design system'in bütün renkleri belliyse, ekibin bg-red-500 gibi sistem dışı bir renge uzanmasını engellemek isteyebilirsin. Bunun için namespace'i initial ile sıfırlıyorsun:

@theme {
    /* yalnız Bookstore'un kendi renkleri: varsayılan palet tamamen kalkıyor */
    --color-*: initial;
    --color-white: oklch(1 0 0);
    --color-brand-600: oklch(0.54 0.178 264);
}

Aynı HTML'de bg-brand-600 bg-red-500 bg-black bg-transparent bg-current text-white border-gray-200 class'ları varken üretilen utility'ler:

npx tailwindcss -i 03/reset.css | grep -E '^  \.'
  .bg-brand-600 {
  .bg-current {
  .bg-transparent {
  .text-white {

bg-red-500, bg-black ve border-gray-200 hiç üretilmedi ve Tailwind bunun için uyarı da vermedi; class HTML'de durdu ama hiçbir şey yapmadı. white ve black da --color-* namespace'inde olduğu için sıfırlamayla gitti; white'ı elle geri ekledik. transparent ve current ise palete değil doğrudan utility'nin kendisine ait, sıfırlamadan etkilenmedi. Bütün varsayılan temayı kaldırmak için --*: initial var; o zaman spacing, font ve breakpoint'leri de baştan tanımlaman gerekir.

Hangisini seçmeli? Var olan bir ürün tasarımını Tailwind'e taşıyorsan sıfırlamak, design token'larını Tailwind'in paletinin üstüne kuruyorsan genişletmek daha doğal. Sıfırlamanın bir bedeli var: üçüncü parti bir Tailwind component'i ya da dokümandan kopyalanan bir örnek, artık var olmayan renk class'larını kullanır ve sessizce renksiz kalır.

@theme inline: Tahminin Cevabı

Tahmindeki butonu, yanına @theme inline ile tanımlanmış ikinci bir renkle birlikte ölçtük:

@theme inline {
    --color-accent-inline: var(--tenant-accent);
}
node scripts/03-inline.mjs
plain   oklch(0.511 0.262 276.966)
inline  oklch(0.596 0.145 163.225)

bg-accent indigo kaldı (indigo-600'ün değeri), bg-accent-inline yeşil oldu (emerald-600). Sebep üretilen CSS'te:

  .bg-accent {
    background-color: var(--color-accent);
  }
  .bg-accent-inline {
    background-color: var(--tenant-accent);
  }

Normal @theme'de --color-accent: var(--tenant-accent) tanımı :root'a yazılıyor ve var(--tenant-accent) orada, yani :root'ta çözülüyor; :root'ta --tenant-accent indigo. Çözülmüş değer miras yoluyla aşağı iniyor. .tenant-school içinde --tenant-accent yeşile dönse de --color-accent çoktan indigo olarak hesaplanmış. @theme inline ise utility'ye değişkenin adını değil değerini yazıyor: var(--tenant-accent) doğrudan butonun üzerinde çözülüyor ve en yakın tanım okulun yeşili. Custom property'lerin hangi element'te hesaplandığını CSS3 serisinin custom property ve theming yazısında ayrıntılı ölçmüştük.

Kural kısa: @theme içinde bir değişken başka bir değişkene başvuruyorsa ve o değişken sayfanın farklı yerlerinde farklı değer alabiliyorsa inline kullan. Bir font yükleyicisinin ya da bir component kütüphanesinin sayfaya koyduğu değişkenlere başvururken de durum aynı.

@theme static: Kullanılmayan Değişkenler de Gelsin

Bazen değişkene hiçbir class dokunmaz ama yine de lazım olur. Bookstore'un yönetim panelindeki satış grafiği, serilerin renklerini getComputedStyle ile CSS'ten okuyor; grafik kütüphanesi Tailwind class'ı kullanmadığı için scanner bu renkleri hiç görmüyor. Aynı üç rengi normal ve static blokta tanımlayıp, HTML'de yalnız text-white varken ürettik:

@theme static {
    --color-brand-50: oklch(0.97 0.014 264);
    --color-brand-600: oklch(0.54 0.178 264);
    --color-brand-700: oklch(0.47 0.16 264);
}
npx tailwindcss -i 03/static-off.css | grep -- --color-
    --color-white: #fff;
    color: var(--color-white);
npx tailwindcss -i 03/static-on.css | grep -- --color-
    --color-white: #fff;
    --color-brand-50: oklch(0.97 0.014 264);
    --color-brand-600: oklch(0.54 0.178 264);
    --color-brand-700: oklch(0.47 0.16 264);
    color: var(--color-white);

Normal blokta brand renkleri CSS'e hiç girmedi; static blokta üçü de :root'ta. static yalnız kendi bloğundaki değişkenleri etkiliyor; Tailwind'in bütün paletini zorla basmıyor. JavaScript'in okuyacağı token'ları ayrı bir @theme static bloğunda toplamak, CSS'i gereksiz büyütmeden sorunu çözüyor.

Dark Mode: Media Query ve Class Stratejisi

Varsayılan dark: variant'ı işletim sisteminin tercihine bakıyor:

<body class="bg-white text-slate-900 dark:bg-slate-900 dark:text-slate-100">
  @media (prefers-color-scheme: dark) {
    .dark\:bg-slate-900 {
      background-color: var(--color-slate-900);
    }
node scripts/03-dark.mjs
light rgb(255, 255, 255)
dark  oklch(0.208 0.042 265.755)

Bu, kullanıcıya bir tema düğmesi sunmayacaksan yeterli. Bookstore.Web'de ise kullanıcı seçimini değiştirebilmeli ve seçim hatırlanmalı. Bunun için dark variant'ını <html> üzerindeki bir class'a bağlıyoruz:

/* dark mode, yalnız işletim sistemine değil <html>'deki .dark class'ına bakar */
@custom-variant dark (&:where(.dark, .dark *));

Artık dark:shadow-none şu selector'ı üretiyor; :where() specificity eklemediği için dark kuralları normal utility'lerle aynı ağırlıkta kalıyor:

  .dark\:shadow-none:where(.dark, .dark *) {
    --tw-shadow: 0 0 #0000;

İkinci karar, her element'e dark: yazmak mı yoksa renkleri tek yerde değiştirmek mi. Bookstore.Web'de component'ler paletin değil anlamın adını kullanıyor: bg-surface, text-fg, border-line. Bu semantic değişkenleri @theme'de açık tema değerleriyle tanımlayıp .dark altında eziyoruz:

/* semantic renkler: component'ler paleti değil bu isimleri kullanır */
@theme {
    --color-canvas: oklch(0.985 0.003 264);
    --color-surface: oklch(1 0 0);
    --color-fg: var(--color-ink-900);
    --color-fg-muted: oklch(0.45 0.03 264);
    --color-line: oklch(0.92 0.01 264);
}

@layer base {
    .dark {
        color-scheme: dark;
        --color-canvas: oklch(0.18 0.025 264);
        --color-surface: oklch(0.24 0.03 264);
        --color-fg: oklch(0.96 0.01 264);
        --color-fg-muted: oklch(0.75 0.02 264);
        --color-line: oklch(0.34 0.03 264);
    }
}

Kart artık rounded-card border border-line bg-surface taşıyor ve iki tema için tek class listesi yetiyor. .dark kuralı base layer'ında; theme layer'ından sonra geldiği için :root'taki açık değerleri eziyor. dark: variant'ı yine var ama istisnalar için: gölgeyi kapatmak (dark:shadow-none) ya da stok rozetinde paletten gelen renkleri değiştirmek (dark:bg-emerald-950 dark:text-emerald-300). color-scheme: dark satırı da scrollbar ve form kontrolleri gibi tarayıcının çizdiği parçaları koyu temaya alıyor.

Son parça FOUC (Flash of Unstyled Content; sayfanın bir an yanlış stille görünmesi). Tema seçimi sayfanın sonundaki bir script'te uygulanırsa tarayıcı önce açık temayı boyar, sonra koyuya geçer. Bunu önlemek için seçimi <head> içinde, stylesheet'ten önce çalışan küçük bir inline script'le uyguluyoruz:

<script>
    // ilk boyamadan önce çalışır: sayfa hiçbir an yanlış temada görünmez
    (function () {
        var stored = null;
        try { stored = localStorage.getItem("theme"); } catch (e) { }
        var dark = stored ? stored === "dark" : matchMedia("(prefers-color-scheme: dark)").matches;
        document.documentElement.classList.toggle("dark", dark);
    })();
</script>
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true">
// tema düğmesi: .dark class'ını çevirir ve seçimi hatırlar
document.querySelector("[data-theme-toggle]")?.addEventListener("click", () => {
    const dark = document.documentElement.classList.toggle("dark");
    try { localStorage.setItem("theme", dark ? "dark" : "light"); } catch { }
});

Inline script parser'ı durdurduğu için <body> boyanmadan önce .dark class'ı yerinde oluyor. localStorage gizli pencerede ya da kapatılmış site verisinde hata fırlatabildiği için çağrılar try içinde. Dört durumu yayınlanmış uygulamada ölçtük:

node scripts/03-theme.mjs
OS light, nothing stored   dark=false body oklch(0.985 0.003 264)  card oklch(1 0 0)  stored=null
OS dark, nothing stored    dark=true  body oklch(0.18 0.025 264)  card oklch(0.24 0.03 264)  stored=null
OS dark, stored light      dark=false body oklch(0.985 0.003 264)  card oklch(1 0 0)  stored=light
OS light, toggle clicked   dark=true  body oklch(0.18 0.025 264)  card oklch(0.24 0.03 264)  stored=dark

Kayıtlı seçim yoksa işletim sistemi kazanıyor, varsa kullanıcının seçimi. Düğme class'ı çevirip seçimi yazıyor. Bu sitenin kendisi de aynı kurguyla çalışıyor: @custom-variant dark ve head'de render'ı bekleten bir inline script.

DTCG Token'larından @theme Üretmek

Bookstore'un renkleri tasarım ekibinin araçlarından bir DTCG JSON dosyası olarak geliyor. Bu dosyayı elle @theme'e kopyalamak yerine Style Dictionary ile üretiyoruz:

03-token-flow-tr.png

{
  "color": {
    "$type": "color",
    "brand": {
      "600": { "$value": { "colorSpace": "oklch", "components": [0.54, 0.178, 264], "hex": "#3766d4" } }
    }
  },
  "radius": {
    "$type": "dimension",
    "card": { "$value": { "value": 0.875, "unit": "rem" } }
  },
  "font": {
    "$type": "fontFamily",
    "display": { "$value": ["Georgia", "Cambria", "serif"] }
  }
}

DTCG 2025.10'da renk bir object: renk uzayı, bileşenler ve isteğe bağlı bir hex yedeği. Style Dictionary 5 $value ve $type alanlarını okuyor; bizim yazdığımız iki parça bir değer transform'u ve bir format:

// DTCG token'ları → Tailwind @theme bloğu (Styles/theme.css)
import StyleDictionary from "style-dictionary";

StyleDictionary.registerTransform({
    name: "value/dtcg-css",
    type: "value",
    transitive: true,
    transform: (token) => {
        const v = token.$value;
        if (token.$type === "color") return `oklch(${v.components[0]} ${v.components[1]} ${v.components[2]})`;
        if (token.$type === "dimension") return `${v.value}${v.unit}`;
        if (token.$type === "fontFamily") return v.map((f) => (f.includes(" ") ? `"${f}"` : f)).join(", ");
        return v;
    },
});

StyleDictionary.registerFormat({
    name: "css/tailwind-theme",
    format: ({ dictionary }) =>
        "/* generated from tokens/bookstore.tokens.json, do not edit */\n@theme {\n" +
        dictionary.allTokens.map((t) => `    --${t.name}: ${t.$value};`).join("\n") +
        "\n}\n",
});

export default {
    source: ["tokens/bookstore.tokens.json"],
    platforms: {
        tailwind: {
            transforms: ["name/kebab", "value/dtcg-css"],
            buildPath: "Styles/",
            files: [{ destination: "theme.css", format: "css/tailwind-theme" }],
        },
    },
};
npm run tokens
tailwind
✔︎ Styles/theme.css
/* generated from tokens/bookstore.tokens.json, do not edit */
@theme {
    --color-brand-50: oklch(0.97 0.014 264);
    --color-brand-100: oklch(0.93 0.033 264);
    --color-brand-500: oklch(0.62 0.17 264);
    --color-brand-600: oklch(0.54 0.178 264);
    --color-brand-700: oklch(0.47 0.16 264);
    --color-ink-900: oklch(0.21 0.03 264);
    --radius-card: 0.875rem;
    --font-display: Georgia, Cambria, serif;
}

name/kebab transform'u token yolunu (color.brand.600) değişken adına (color-brand-600) çeviriyor. Tailwind'in namespace'leri color-, radius-, font- ile başladığı için JSON'daki üst grupların adını namespace'lerle aynı seçmek yetiyor; ayrıca bir eşleme tablosu gerekmiyor. input.css bu dosyayı @import "./theme.css"; ile alıyor. Üretilen dosyayı repo'ya koyduk; böylece CSS build'i Style Dictionary'ye bağımlı değil ve token değişikliği code review'da theme.css diff'i olarak görünüyor.

Üretimde Dikkat

Head'deki inline script, Content Security Policy kullanan bir uygulamada script-src'ye takılır. Ya script'in hash'ini (SHA-256) policy'ye eklersin ya da her istekte üretilen bir nonce verirsin; script'i harici dosyaya taşırsan render'ı bekletmesi için defer ya da async ekleyemezsin, yine head'de senkron yüklenmesi gerekir. Policy'nin kurulumu bu serinin değil, Web Güvenliği (OWASP Top 10) serisinin konusu.

Semantic değişkenleri .dark altında ezmek, sayfanın tamamında tek bir yeniden hesaplama demek; her element'teki dark: class'ları ise CSS'i büyütür. Bookstore.Web'de dark: yalnız üç yerde geçiyor. İkinci bir tema (kurumsal portalın yüksek kontrastlı teması gibi) gerektiğinde yalnız bir değişken bloğu daha yazıyorsun.

Token'ları JavaScript'ten okuyorsan, değişkenin CSS'te olduğundan emin ol. @theme static yazmayı unutursan değişken boş string döner; grafikteki seriler varsayılan renge düşer ve hata mesajı görmezsin.

Sık Düşülen Tuzaklar

İlki tahmindeki tuzak: başka bir değişkene başvuran theme değişkenini inline olmadan yazmak. Değer :root'ta donar ve alt ağaçtaki override'lar utility'ye ulaşmaz.

İkincisi token'lara değerin adını vermek. --color-blue-600 bir palet rengi olarak doğru, ama component'ler onu kullanırsa dark mode'da "koyu temada blue-600 aslında blue-400" gibi çelişkiler başlar. Palet ve semantic katmanı ayır; component'ler semantic isimleri kullansın.

Üçüncüsü v3 alışkanlığıyla tailwind.config.js'te theme.extend aramak. v4'te o dosya varsayılan olarak okunmuyor; eski bir config'i @config ile yükleyebilirsin, ama yeni token'ların yeri @theme. Geçişi serinin bakım yazısında npx @tailwindcss/upgrade ile yapacağız.

Dördüncüsü --color-*: initial ile sıfırlayıp white, black gibi temel renkleri geri eklemeyi unutmak. text-white yazılmış her yerde metin sessizce body'nin rengine döner.

Küçük Bir Deneme

★☆☆ tokens/bookstore.tokens.json'a color.accent.500 adında yeni bir token ekle, npm run tokens ve npm run css:build çalıştır ve bg-accent-500'ün kartta kullanılabildiğini doğrula.

★★☆ 03/inline.css'te @theme inline bloğunu silip --color-accent'i @theme'de bırak, sonra .tenant-school kuralına --color-accent: var(--tenant-accent); satırını da ekle. Butonun rengi değişiyor mu? Bu çözümün @theme inline'a göre bedeli ne?

★★★ Bookstore.Web'e ikinci bir tema ekle: <html class="contrast"> iken --color-fg siyah, --color-line koyu gri olsun. @custom-variant contrast tanımla ve 03-theme.mjs script'ini bu temayı da ölçecek şekilde genişlet.

Bir sonraki yazıda variant'lara geçiyoruz: group-* ve peer-*, has-* ve not-*, aria-* ve data-* ile headless component'leri stillemek, built-in container query'ler, starting: ile giriş animasyonu ve kendi utility'ni yazmak için @utility ile @variant.