Dataattribut
Använda dataattribut för deklarativ styling och komponentidentifiering.
Dataattribut ger ett kraftfullt sätt att exponera komponenttillstånd och struktur för konsumenter, vilket möjliggör flexibel styling utan props-explosion. Moderna komponentbibliotek använder två primära mönster: data-state för visuella tillstånd och data-slot för komponentidentifiering.
Styla tillstånd med data-state
Ett av de vanligaste antipatterns i komponentstyling är att exponera separata className-props för olika tillstånd.
I mindre moderna komponenter ser du ofta API:er som detta:
<Dialog
openClassName="bg-black"
closedClassName="bg-white"
classes={{
open: "opacity-100",
closed: "opacity-0"
}}
/>Denna metod har flera problem:
- Den kopplar komponentens interna tillstånd till dess styling-API
- Den skapar en explosion av props i takt med att komponenter blir mer komplexa
- Den gör komponenten svårare att använda och underhålla
- Den förhindrar styling baserat på kombinationer av tillstånd
Lösningen: data-state-attribut
Använd istället data-*-attribut för att deklarativt exponera komponenttillstånd. Detta tillåter konsumenter att styla komponenter baserat på tillstånd med standard CSS-selektorer:
const Dialog = ({ className, ...props }: DialogProps) => {
const [isOpen, setIsOpen] = useState(false);
return (
<div
data-state={isOpen ? 'open' : 'closed'}
className={cn('transition-all', className)}
{...props}
/>
);
};Nu kan konsumenter styla komponenten baserat på tillstånd utifrån:
<Dialog className="data-[state=open]:opacity-100 data-[state=closed]:opacity-0" />Fördelar med detta angreppssätt
- Enkel className-prop - Ingen behov av flera state-specifika className-props
- Komponerbart - Kombinera flera dataattribut för komplexa tillstånd
- Standard CSS - Fungerar med alla CSS-in-JS-lösningar eller vanlig CSS
- Typ-säkert - TypeScript kan härleda värden för dataattribut
- Inspekterbart - Tillstånd är synliga i DevTools som HTML-attribut
Vanliga tillståndsmönster
Använd dataattribut för alla typer av komponenttillstånd:
// 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" />Styling med Tailwind
Tailwind stödjer godtyckliga varianter, vilket gör styling med dataattribut elegant:
<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'
)}
/>För vanliga tillstånd kan du utöka Tailwinds konfiguration:
module.exports = {
theme: {
extend: {
data: {
open: 'state="open"',
closed: 'state="closed"',
active: 'state="active"',
}
}
}
}Nu kan du använda kortformer:
<Dialog className="data-open:opacity-100 data-closed:opacity-0" />Integration med Radix UI
Detta mönster används flitigt av Radix UI, som automatiskt applicerar dataattribut på sina primitives:
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>Andra dataattribut som Radix tillhandahåller inkluderar:
data-state- open/closed, active/inactive, on/offdata-side- top/right/bottom/left (för positionerade element)data-align- start/center/end (för positionerade element)data-orientation- horizontal/verticaldata-disabled- närvarande när disabled är pådata-placeholder- närvarande när placeholder visas
Komponentidentifiering med data-slot
Medan data-state spårar visuella tillstånd, identifierar data-slot komponenttyper inom en komposition. Detta mönster, populärgjort av shadcn/ui, låter föräldrakomponenter rikta in sig på och styla specifika barnkomponenter utan att förlita sig på ömtåliga klasser eller elementselektorer.
Problemet med att rikta in sig på barnkomponenter
Traditionella sätt att styla barnkomponenter har betydande begränsningar:
// 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>Lösningen: data-slot-attribut
Använd data-slot för att ge komponenter stabila identifierare som föräldrar kan rikta in sig på:
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}
/>
);
}Fördelar med data-slot
- Stabila identifierare - Bryts inte när implementationsdetaljer ändras
- Semantisk målning - Rikta in dig baserat på komponentens syfte, inte struktur
- Inkapsling - Interna klasser förblir privata
- Komponerbart - Fungerar med godtycklig nestning och komposition
- Typ-säkert - Kan valideras och dokumenteras
Använda has-[] för föräldrakännedom i styling
Tailwinds has-[]-selektor kombinerat med data-slot skapar kraftfull föräldrakännedom i styling:
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}
/>
);
}Använda [&_] för efterkommande målning
För djupare nestning, använd [&_selector]-mönstret för att rikta in dig på vilken efterkommande som helst:
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}
/>
);
}Global CSS med data-slot
Data slots fungerar utmärkt med global CSS för temabredd konsekvens:
/* 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;
}Namngivningskonventioner
Följ dessa konventioner för konsekvent data-slot-namngivning:
- Använd kebab-case -
data-slot="form-field"intedata-slot="formField" - Var specifik -
data-slot="submit-button"intedata-slot="button" - Matcha komponentens syfte - Namnet speglar vad den gör, inte hur den ser ut
- Undvik implementationsdetaljer -
data-slot="user-avatar"intedata-slot="rounded-image"
// 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 camelCaseNär man ska använda dataattribut vs props
Att förstå när man ska använda varje mönster är nyckeln till ett rent API:
data-state användningsfall
- Visuella tillstånd - open/closed, active/inactive, loading, osv.
- Layouttillstånd - orientation, side, alignment
- Interaktionstillstånd - hover, focus, disabled (när du behöver styla barn)
data-slot användningsfall
- Komponentidentifiering - Stabila identifierare för målning
- Kompositionsmönster - Föräldra-barn-relationer
- Global styling - Temabred komponentstyling
- Variant-oberoende målning - Rikta in sig på vilken variant som helst av en komponent
props användningsfall
- Varianter - Olika visuella designer (primary, secondary, destructive)
- Storlekar - sm, md, lg
- Beteendekonfiguration - controlled/uncontrolled, standardvärden
- Event handlers - onClick, onChange, osv.
Kombinerat angreppssätt
En väl utformad komponent använder alla tre mönstren lämpligt:
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}
/>
);
};Nu kan knappen användas och stylas på flera sätt:
// 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"] { ... }Dataattribut ger en robust grund för att styla moderna komponentbibliotek. Genom att använda data-state för visuella tillstånd och data-slot för komponentidentifiering skapar du ett flexibelt, underhållbart API som skalar från enkla komponenter till komplexa designsystem.