Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
IT NachrichtenCrazy Taxi: World Tour zeigt Japan-Karte im neuen Trailer(21.09.2026 um 13:29 Uhr)
IT NachrichtenSo wollen Forscher alten Akkus neues Leben einhauchen(21.09.2026 um 13:42 Uhr)
IT NachrichtenCrazy Taxi: World Tour zeigt Japan-Karte im neuen Trailer(21.09.2026 um 13:29 Uhr)
IT NachrichtenSo wollen Forscher alten Akkus neues Leben einhauchen(21.09.2026 um 13:42 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Tailwind CSS Component Slots in React 19: Building Flexible, Reusable UI Without Prop Drilling Hell

Tailwind CSS Component Slots in React 19: Building Flexible, Reusable UI Without Prop Drilling Hell I've shipped nine AI features across CitizenApp, and every single one has a card, modal, or layout wrapper. By feature six, I realized I…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!




Tailwind CSS Component Slots in React 19: Building Flexible, Reusable UI Without Prop Drilling Hell



I've shipped nine AI features across CitizenApp, and every single one has a card, modal, or layout wrapper. By feature six, I realized I was writing the same component three times with slightly different style props. className, headerClassName, bodyClassName, footerClassName—it becomes a nightmare. The component API explodes. Tailwind becomes a liability instead of an asset.



Then I discovered the slot pattern, and it changed how I think about React component design entirely.



The slot pattern lets you define named regions in your component that consumers can fill with their own JSX. No style props. No wrapper divs. No prop drilling. Just composition. It's what Vue does natively, and React 19 makes it ergonomic enough that I now prefer it over render props or compound components for most UI.






Why Slots Beat Style Props (And Render Props)



Let's be honest: style props are a trap. Here's what happens:




// ❌ This is how it starts...
<Card className="..." headerClassName="..." bodyClassName="..." />

// ❌ Then you need one more customization
<Card
className="..."
headerClassName="..."
bodyClassName="..."
footerClassName="..."
headerIconClassName="..."
borderClassName="..."
/>

// Now the component has 15 props and zero flexibility






This is prop-drilling's uglier cousin. The component doesn't own its styling problem—it just pushes it onto consumers.



Render props help:




<Card
header={({ className }) => <div className={className}>Title</div>}
body={({ className }) => <div className={className}>Content</div>}
/>






But now you're passing className callbacks. It's overly functional and forces consumers to think about internals.



Slots are different. You define structural regions, and consumers fill them:




<Card>
<Card.Header>AI Response Title</Card.Header>
<Card.Body>Streaming response here</Card.Body>
<Card.Footer>Action buttons</Card.Footer>
</Card>






The component defines where things go and how much space they get. Consumers define what goes there. Clean separation.






Building a Slot-Based Card Component



Here's the Card from CitizenApp. It powers everything from AI feature outputs to settings panels:




// components/Card/Card.tsx
import React from 'react';

interface CardContextType {
isLoading?: boolean;
}

const CardContext = React.createContext<CardContextType>({});

export function useCardContext() {
return React.useContext(CardContext);
}

interface CardProps {
children: React.ReactNode;
isLoading?: boolean;
variant?: 'default' | 'elevated' | 'outlined';
className?: string;
}

function CardRoot({
children,
isLoading = false,
variant = 'default',
className = '',
}: CardProps) {
const variants = {
default: 'bg-white border border-gray-200 rounded-lg shadow-sm',
elevated: 'bg-white rounded-lg shadow-md',
outlined: 'bg-white border-2 border-blue-500 rounded-lg',
};

return (
<CardContext.Provider value={{ isLoading }}>
<div className={`${variants[variant]} ${className}`}>
{children}
</div>
</CardContext.Provider>
);
}

interface CardHeaderProps {
children: React.ReactNode;
className?: string;
icon?: React.ReactNode;
}

function CardHeader({ children, className = '', icon }: CardHeaderProps) {
return (
<div className={`px-6 py-4 border-b border-gray-100 flex items-center gap-3 ${className}`}>
{icon && <div className="flex-shrink-0">{icon}</div>}
<h3 className="text-lg font-semibold text-gray-900">{children}</h3>
</div>
);
}

interface CardBodyProps {
children: React.ReactNode;
className?: string;
isLoading?: boolean;
}

function CardBody({ children, className = '', isLoading }: CardBodyProps) {
const { isLoading: contextLoading } = useCardContext();
const loading = isLoading ?? contextLoading;

return (
<div
className={`px-6 py-4 ${loading ? 'opacity-50 pointer-events-none' : ''} ${className}`}
>
{children}
</div>
);
}

interface CardFooterProps {
children: React.ReactNode;
className?: string;
align?: 'left' | 'center' | 'right' | 'between';
}

function CardFooter({ children, className = '', align = 'right' }: CardFooterProps) {
const aligns = {
left: 'justify-start',
center: 'justify-center',
right: 'justify-end',
between: 'justify-between',
};

return (
<div
className={`px-6 py-3 border-t border-gray-100 flex ${aligns[align]} gap-3 ${className}`}
>
{children}
</div>
);
}

// Compound component assembly
export const Card = Object.assign(CardRoot, {
Header: CardHeader,
Body: CardBody,
Footer: CardFooter,
});






Now consumers use it without touching a single style prop:




// In an AI feature component
<Card variant="elevated">
<Card.Header icon={<SparklesIcon />}>
Claude 3.5 Sonnet Analysis
</Card.Header>
<Card.Body isLoading={isAnalyzing}>
<div className="prose prose-sm">
{analysisText}
</div>
</Card.Body>
<Card.Footer align="between">
<button className="text-gray-600 text-sm">Copy</button>
<button className="bg-blue-600 text-white px-4 py-2 rounded">
Use in Report
</button>
</Card.Footer>
</Card>






Notice: zero style props on the Card itself. The consumer composes the content. The Card handles structure and spacing.






The Context Hook Pattern



I use useCardContext throughout my components. It lets child slots read Card-level state (like loading) without prop drilling through components:




function CardBody({ children, className = '', isLoading }: CardBodyProps) {
const { isLoading: contextLoading } = useCardContext();
const loading = isLoading ?? contextLoading; // Child can override or inherit
// ...
}






This is crucial. Without it, you'd have to pass isLoading through every slot manually. Context solves that elegantly.






Advanced Slot Example: Modal with Scrollable Body



Here's how slots shine with complex layouts:




interface ModalProps {
children: React.ReactNode;
isOpen: boolean;
onClose: () => void;
}

function Modal({ isOpen, onClose, children }: ModalProps) {
if (!isOpen) return null;

return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center">
<div className="bg-white rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] flex flex-col">
{children}
</div>
</div>
);
}

function ModalHeader({ children }: { children: React.ReactNode }) {
return <div className="px-6 py-4 border-b">{children}</div>;
}

function ModalBody({ children }: { children: React.ReactNode }) {
return <div className="px-6 py-4 overflow-y-auto flex-1">{children}</div>;
}

function ModalFooter({ children }: { children: React.ReactNode }) {
return <div className="px-6 py-4 border-t flex gap-3 justify-end">{children}</div>;
}

export const Modal = Object.assign(Modal, {
Header: ModalHeader,
Body: ModalBody,
Footer: ModalFooter,
});






Usage:




<Modal isOpen={open} onClose={onClose}>
<Modal.Header>Generate Report</Modal.Header>
<Modal.Body>
<form className="space-y-4">
{/* Your form content - no Modal knows about it */}
</form>
</Modal.Body>
<Modal.Footer>
<button onClick={onClose}>Cancel</button>
<button className="bg-blue-600 text-white">Generate</button>
</Modal.Footer>
</Modal>






The Modal doesn't care what goes in the body. It just manages the scrolling, spacing, and layout. The consumer fills it.






Gotcha: TypeScript and Compound Components



The Object.assign pattern loses TypeScript information:




// ❌ TypeScript doesn't know Card.Header exists
<Card.Header /> // Error: property 'Header' does not exist






Fix it with explicit typing:




interface CardComponent extends React.FC<CardProps> {
Header: typeof CardHeader;
Body: typeof CardBody;
Footer: typeof CardFooter;
}

export const Card = Object.assign(CardRoot, {
Header: CardHeader,
Body: CardBody,
Footer: CardFooter,
}) as CardComponent;






Now TypeScript knows exactly what methods exist. I learned this the hard way after merging half a feature before realizing autocomplete was broken.






When NOT to Use Slots



Slots are powerful, but they're not universal:





  • Simple buttons/badges: A single variant prop is fine. Don't over-engineer.

  • **Deeply

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Tailwind CSS Component Slots in React 19: Building Flexible, Reusable UI Without Prop Drilling Hell

Thematisch verwandte Begriffe: Tailwind, Component, Slots, React · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-94097 | A vulnerability was determined in Netcore NBR200V2 1.3.241127.071246. Th…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick