Data öznitelikleri
Deklaratif stillendirme ve bileşen tanımlaması için data özniteliklerinin kullanımı.
Data öznitelikleri, bileşen durumunu ve yapısını tüketicilere açmak için güçlü bir yol sağlar; bu sayede prop patlaması olmadan esnek stillendirme mümkün olur. Modern bileşen kütüphaneleri iki ana deseni kullanır: data-state görsel durumlar için ve data-slot bileşen tanımlaması için.
data-state ile durumları stillendirme
Bileşen stillendirmesinde en yaygın anti-pattern'lerden biri, farklı durumlar için ayrı className prop'larını açığa çıkarmaktır.
Daha az modern bileşenlerde genellikle şu tür API'lerle karşılaşırsınız:
<Dialog
openClassName="bg-black"
closedClassName="bg-white"
classes={{
open: "opacity-100",
closed: "opacity-0"
}}
/>Bu yaklaşımın birkaç sorunu vardır:
- Bileşenin dahili durumunu stillendirme API'sine bağlar
- Bileşenler karmaşıklaştıkça prop patlaması yaratır
- Bileşeni kullanmayı ve bakımını zorlaştırır
- Durum kombinasyonlarına dayalı stillendirmeyi engeller
Çözüm: data-state öznitelikleri
Bunun yerine, bileşen durumunu deklaratif olarak açığa çıkarmak için data-* özniteliklerini kullanın. Bu, tüketicilerin standart CSS seçicileriyle duruma göre bileşenleri stillendirmesine olanak tanır:
const Dialog = ({ className, ...props }: DialogProps) => {
const [isOpen, setIsOpen] = useState(false);
return (
<div
data-state={isOpen ? 'open' : 'closed'}
className={cn('transition-all', className)}
{...props}
/>
);
};Artık tüketiciler bileşeni dışarıdan duruma göre şu şekilde stillendirebilir:
<Dialog className="data-[state=open]:opacity-100 data-[state=closed]:opacity-0" />Bu yaklaşımın faydaları
- Tek className prop - Farklı durumlara özel birden fazla className prop'una gerek yok
- Bileşebilir - Karmaşık durumlar için birden fazla data özniteliğini birleştirebilirsiniz
- Standart CSS - Herhangi bir CSS-in-JS çözümü veya düz CSS ile çalışır
- Type-safe - TypeScript data özniteliği değerlerini çıkarabilir
- İncelenebilir - Durumlar DevTools'ta HTML öznitelikleri olarak görünür
Yaygın durum desenleri
Her türlü bileşen durumu için data özniteliklerini kullanın:
// Open/closed state
<Accordion data-state={isOpen ? 'open' : 'closed'} />
// Selected state
<Tab data-state={isSelected ? 'active' : 'inactive'} />
// Disabled state (in addition to disabled attribute)
<Button data-disabled={isDisabled} disabled={isDisabled} />
// Loading state
<Button data-loading={isLoading} />
// Orientation
<Slider data-orientation="horizontal" />
// Side/position
<Tooltip data-side="top" />Tailwind ile stillendirme
Tailwind, keyfi varyantları destekleyerek data özniteliği stillendirmesini zarif hale getirir:
<Dialog
className={cn(
// Base styles
'rounded-lg border p-4',
// State-based styles
'data-[state=open]:animate-in data-[state=open]:fade-in',
'data-[state=closed]:animate-out data-[state=closed]:fade-out',
// Multiple attributes
'data-[state=open][data-side=top]:slide-in-from-top-2'
)}
/>Sık kullanılan durumlar için Tailwind yapılandırmanızı genişletebilirsiniz:
module.exports = {
theme: {
extend: {
data: {
open: 'state="open"',
closed: 'state="closed"',
active: 'state="active"',
}
}
}
}Artık kısa yazımı kullanabilirsiniz:
<Dialog className="data-open:opacity-100 data-closed:opacity-0" />Radix UI ile entegrasyon
Bu desen, primitives'lerine otomatik olarak data öznitelikleri uygulayan Radix UI tarafından yoğun olarak kullanılır:
import * as Dialog from '@radix-ui/react-dialog';
<Dialog.Root>
<Dialog.Trigger />
<Dialog.Portal>
{/* Radix automatically adds data-state="open" | "closed" */}
<Dialog.Overlay className="data-[state=open]:animate-in data-[state=closed]:animate-out" />
<Dialog.Content className="data-[state=open]:fade-in data-[state=closed]:fade-out" />
</Dialog.Portal>
</Dialog.Root>Radix'in sağladığı diğer data öznitelikleri şunları içerir:
data-state- open/closed, active/inactive, on/offdata-side- top/right/bottom/left (konumlandırılmış ögeler için)data-align- start/center/end (konumlandırılmış ögeler için)data-orientation- horizontal/verticaldata-disabled- disabled olduğunda mevcut olurdata-placeholder- placeholder gösteriliyorken mevcut olur
data-slot ile bileşen tanımlama
data-state görsel durumları takip ederken, data-slot bileşen kompozisyonu içindeki türleri tanımlar. Bu desen, shadcn/ui tarafından popüler hale getirilmiştir ve üst bileşenlerin kırılgan class isimlerine veya element seçicilere güvenmeden belirli alt bileşenleri hedefleyip stillendirmesine olanak tanır.
Alt hedefleme sorunu
Alt bileşenleri stillendirmek için geleneksel yaklaşımların önemli sınırlamaları vardır:
// Relies on element types - breaks if implementation changes
<form className="[&_input]:rounded-lg [&_button]:mt-4" />
// Relies on class names - breaks if classes change
<form className="[&_.text-input]:rounded-lg" />
// Requires passing classes through props - verbose
<form>
<input className={inputClasses} />
<button className={buttonClasses} />
</form>Çözüm: data-slot öznitelikleri
Üst bileşenlerin hedefleyip stillendirebileceği stabil tanımlayıcılar vermek için data-slot kullanın:
function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
return (
<fieldset
data-slot="field-set"
className={cn(
"flex flex-col gap-6",
// Target specific child slots
"has-[>[data-slot=checkbox-group]]:gap-3",
"has-[>[data-slot=radio-group]]:gap-3",
className
)}
{...props}
/>
);
}function CheckboxGroup({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="checkbox-group"
className={cn("flex flex-col gap-2", className)}
{...props}
/>
);
}data-slot'un faydaları
- Stabil tanımlayıcılar - Uygulama detayları değişse bile bozulmaz
- Semantik hedefleme - Yapıya değil, bileşen amacına göre hedefleyin
- Kapsülleme - Dahili sınıflar özel kalır
- Bileşebilirlik - Herhangi bir iç içe geçme ve kompozisyonla çalışır
- Type-safe - Doğrulanabilir ve belgelenebilir
Ebeveyn farkındalıklı stillendirme için has-[] kullanımı
Tailwind'in has-[] seçicisi data-slot ile birleştiğinde güçlü ebeveyn-farkındalıklı stillendirme yaratır:
function Form({ className, ...props }: React.ComponentProps<"form">) {
return (
<form
data-slot="form"
className={cn(
"space-y-4",
// Adjust spacing when specific slots are present
"has-[>[data-slot=form-section]]:space-y-6",
"has-[>[data-slot=inline-fields]]:space-y-2",
// Style based on slot states
"has-[[data-slot=submit-button][data-loading=true]]:opacity-50",
className
)}
{...props}
/>
);
}Descendant hedefleme için [&_] kullanımı
Daha derin iç içe geçmeler için herhangi bir alt ögeyi hedeflemek üzere [&_selector] desenini kullanın:
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card"
className={cn(
"rounded-lg border p-4",
// Target any descendant with data-slot
"[&_[data-slot=card-header]]:mb-4",
"[&_[data-slot=card-title]]:text-lg [&_[data-slot=card-title]]:font-semibold",
"[&_[data-slot=card-description]]:text-sm [&_[data-slot=card-description]]:text-muted-foreground",
"[&_[data-slot=card-footer]]:mt-4 [&_[data-slot=card-footer]]:border-t [&_[data-slot=card-footer]]:pt-4",
className
)}
{...props}
/>
);
}data-slot ile global CSS
Data slot'lar, tema genelinde tutarlılık için global CSS ile mükemmel çalışır:
/* Style all buttons within forms */
[data-slot="form"] [data-slot="button"] {
@apply w-full sm:w-auto;
}
/* Style submit buttons specifically */
[data-slot="form"] [data-slot="submit-button"] {
@apply bg-primary text-primary-foreground;
}
/* Adjust inputs within inline layouts */
[data-slot="inline-fields"] [data-slot="input"] {
@apply flex-1;
}
/* Style based on state combinations */
[data-slot="dialog"][data-state="open"] [data-slot="dialog-content"] {
@apply animate-in fade-in;
}İsimlendirme kuralları
Tutarlı data-slot isimlendirmesi için şu kuralları izleyin:
- Kebab-case kullanın -
data-slot="form-field";data-slot="formField"değil - Spesifik olun -
data-slot="submit-button";data-slot="button"değil - Bileşen amacına uyun - İsim ne yaptığını yansıtmalı, nasıl göründüğünü değil
- Uygulama detaylarından kaçının -
data-slot="user-avatar";data-slot="rounded-image"değil
// Good examples
data-slot="search-input"
data-slot="navigation-menu"
data-slot="error-message"
data-slot="submit-button"
data-slot="card-header"
// Avoid
data-slot="input" // Too generic
data-slot="blueButton" // Includes styling
data-slot="div-wrapper" // Implementation detail
data-slot="mainContent" // Use camelCasedata özniteliklerini ne zaman props ile kullanmalısınız
Hangi deseni ne zaman kullanacağınızı anlamak temiz bir API için kritiktir:
data-state kullanım durumları
- Görsel durumlar - open/closed, active/inactive, loading vb.
- Yerleşim durumları - orientation, side, alignment
- Etkileşim durumları - hover, focus, disabled (çocukları stillendirmeniz gerektiğinde)
data-slot kullanım durumları
- Bileşen tanımlama - Hedefleme için stabil tanımlayıcılar
- Kompozisyon desenleri - Ebeveyn-çocuk ilişkileri
- Global stillendirme - Tema genelinde bileşen stillendirmesi
- Varyant bağımsız hedefleme - Bir bileşenin herhangi bir varyantını hedefleme
props kullanım durumları
- Varyantlar - Farklı görsel tasarımlar (primary, secondary, destructive)
- Boyutlar - sm, md, lg
- Davranış konfigürasyonu - controlled/uncontrolled, varsayılan değerler
- Event handler'lar - onClick, onChange vb.
Birleştirilmiş yaklaşım
İyi tasarlanmış bir bileşen, üç deseni de uygun şekilde kullanır:
type ButtonProps = {
variant?: 'primary' | 'secondary' | 'destructive';
size?: 'sm' | 'md' | 'lg';
loading?: boolean;
disabled?: boolean;
onClick?: () => void;
className?: string;
};
const Button = ({
variant = 'primary',
size = 'md',
loading,
disabled,
className,
...props
}: ButtonProps) => {
return (
<button
// Slot for targeting
data-slot="button"
// State for conditional styling
data-loading={loading}
data-disabled={disabled}
className={cn(
// Variant styles via props
buttonVariants({ variant, size }),
// Additional state styling allowed via className
className
)}
disabled={disabled}
{...props}
/>
);
};Artık buton birden fazla şekilde kullanılabilir ve stillendirilebilir:
// Basic usage with variants
<Button variant="primary" size="lg">Submit</Button>
// Parent targeting via data-slot
<form className="[&_[data-slot=button]]:w-full">
<Button>Submit</Button>
</form>
// State-based styling via data-state
<Button
loading={isLoading}
className="data-[loading=true]:opacity-50"
>
Submit
</Button>
// Global CSS can target any button
// [data-slot="button"][data-loading="true"] { ... }Data öznitelikleri, modern bileşen kütüphaneleri için sağlam bir stillendirme temeli sağlar. Görsel durumlar için data-state ve bileşen tanımlaması için data-slot kullanarak, basit bileşenlerden karmaşık tasarım sistemlerine kadar ölçeklenen esnek ve sürdürülebilir bir API oluşturursunuz.