Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Sichere ProgrammierungBreeze TTS 2 vs ElevenLabs: Open Source TTS Verdict(23.09.2026 um 05:44 Uhr)
Sichere ProgrammierungAgentic AI vs Generative AI: The 2026 Verdict(23.09.2026 um 05:44 Uhr)
Sichere ProgrammierungI made my agent prove every quote against the source document(23.09.2026 um 05:45 Uhr)
Sichere Programmierung8mb.video Alternative: Skip the Line, Skip the Upsell(23.09.2026 um 05:47 Uhr)
Sichere ProgrammierungBuilding a GTA 6 JSON API for entities and current status(23.09.2026 um 05:52 Uhr)
Sichere ProgrammierungEvery filter needs a documented exception(23.09.2026 um 06:01 Uhr)
Sichere ProgrammierungBreeze TTS 2 vs ElevenLabs: Open Source TTS Verdict(23.09.2026 um 05:44 Uhr)
Sichere ProgrammierungAgentic AI vs Generative AI: The 2026 Verdict(23.09.2026 um 05:44 Uhr)
Sichere ProgrammierungI made my agent prove every quote against the source document(23.09.2026 um 05:45 Uhr)
Sichere Programmierung8mb.video Alternative: Skip the Line, Skip the Upsell(23.09.2026 um 05:47 Uhr)
Sichere ProgrammierungBuilding a GTA 6 JSON API for entities and current status(23.09.2026 um 05:52 Uhr)
Sichere ProgrammierungEvery filter needs a documented exception(23.09.2026 um 06:01 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Type-Safe Compound Components in 2026 – The Generic Factory Pattern I Actually Use

How I finally got real type safety on children values without losing the beautiful dot API Compound components are awesome for building flexible UI primitives — think <Tabs>, <Accordion>, <RadioGroup>, or &…

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

How I finally got real type safety on children values without losing the beautiful dot API



react-typescript



Compound components are awesome for building flexible UI primitives — think <Tabs>, <Accordion>, <RadioGroup>, or <Popover> — where the consumer controls the layout and composition.



But flexibility often meant zero type safety on children or values.



I used to write stuff like this:




<RadioGroup value={selected} onChange={setSelected}>
<RadioItem value="draft">Draft</RadioItem>
<RadioItem value="published">Published</RadioItem>
<RadioItem value={123}>Wait what? {/* ← TypeScript is silent 😭 */}
</RadioItem>
</RadioGroup>






I wanted TypeScript to actually yell at me when the value type was wrong, while keeping the clean dot-notation DX everyone loves:




<StatusRadio.RadioGroup value={status} onValueChange={setStatus}>
<StatusRadio.RadioItem value="draft">...</StatusRadio.RadioItem>
{/* value={123} → red squiggle! */}
</StatusRadio.RadioGroup>






After reading TkDodo’s excellent post “Building Type-Safe Compound Components” (Jan 2, 2026), I extended the ideas into a tiny generic factory. It’s now my go-to for typed item groups (radios, tabs, segmented controls, steppers…).



Two Flavors of Compound Components




  1. Max layout freedom (Card/Dialog style)
    Free-form children, arbitrary order:




<Card>
<CardHeader>Title</CardHeader>
<CardContent>Anything goes</CardContent>
<CardFooter>Actions</CardFooter>
</Card>






→ Use simple context + sub-components. Super common in shadcn/ui, Radix, etc. (Not the focus here).




  1. Strict typed children (Radio/Tabs/Steps style)
    Predictable items + strong value typing — factory time!




type Status = "draft" | "published" | "archived";

const StatusRadio = createTypedRadioGroup<Status>();

<StatusRadio.RadioGroup value={status} onValueChange={setStatus}>
<StatusRadio.RadioItem value="draft">
<StatusRadio.RadioIndicator />
Draft
</StatusRadio.RadioItem>
{/* value={123} → compile error */}
</StatusRadio.RadioGroup>






The Reusable Factory Pattern




import { createContext, useContext } from "react";
import {
RadioGroup as BaseRadioGroup,
Radio,
mergeProps,
} from "@base-ui/react";
import { radioGroupRootVariants, radioItemVariants, RadioIndicator } from "../ui/radio";

type RadioGroupContextValue = {
size?: "sm" | "md" | "lg" | null;
variant?: "default" | "outline" | null;
};

export function createTypedRadioGroup<T>() {
const RadioGroupContext = createContext<RadioGroupContextValue | null>(null);

function TypedRadioGroup({
size,
variant,
orientation,
className,
children,
...props
}: Omit<React.ComponentProps<typeof BaseRadioGroup>, "children" | "className"> & {
size?: "sm" | "md" | "lg";
variant?: "default" | "outline";
orientation?: "horizontal" | "vertical";
className?: string;
children: React.ReactNode;
}) {
const ctxValue = { size: size ?? null, variant: variant ?? null };

return (
<RadioGroupContext.Provider value={ctxValue}>
<BaseRadioGroup
className={radioGroupRootVariants({ size, orientation })}
{...props}
>
{children}
</BaseRadioGroup>
</RadioGroupContext.Provider>
);
}

function TypedRadioItem({
value,
size: itemSize,
className,
children,
...props
}: { value: T; size?: "sm" | "md" | "lg" } & Omit<
React.ComponentProps<typeof Radio.Root>,
"value" | "children"
>) {
const context = useContext(RadioGroupContext);
if (!context) {
throw new Error("RadioItem must be used inside RadioGroup");
}

const finalSize = itemSize ?? context.size ?? "md";

const merged = mergeProps({ className }, {
className: radioItemVariants({ size: finalSize }),
});

// Adapt value to string/number as needed by your base library
return (
<Radio.Root value={String(value)} className={merged.className} {...props}>
{children}
</Radio.Root>
);
}

return {
RadioGroup: TypedRadioGroup,
RadioItem: TypedRadioItem,
RadioIndicator,
} as const;
}






Note: If your headless base (Radix/Ark/Base UI) supports generic values natively, drop the String(value) cast.



Real-World Examples:




  1. Status Picker (string literals)




type Status = "draft" | "published" | "archived";

const StatusRadio = createTypedRadioGroup<Status>();

export function StatusPicker() {
const [status, setStatus] = useState<Status>("draft");

return (
<StatusRadio.RadioGroup
value={status}
onValueChange={setStatus}
size="md"
variant="default"
>
<StatusRadio.RadioItem value="draft">
<StatusRadio.RadioIndicator />
Draft
</StatusRadio.RadioItem>
<StatusRadio.RadioItem value="published">
<StatusRadio.RadioIndicator />
Published
</StatusRadio.RadioItem>
<StatusRadio.RadioItem value="archived">
<StatusRadio.RadioIndicator />
Archived
</StatusRadio.RadioItem>
</StatusRadio.RadioGroup>
);
}







  1. Star Rating (numbers)




const RatingRadio = createTypedRadioGroup<number>();

<RatingRadio.RadioGroup value={rating} onValueChange={setRating} orientation="horizontal">
{[1, 2, 3, 4, 5].map((n) => (
<RatingRadio.RadioItem key={n} value={n}>
<RatingRadio.RadioIndicator />
{n} ★
</RatingRadio.RadioItem>
))}
</RatingRadio.RadioGroup>







  1. Object-based (real-world use case)




interface Option {
id: string;
label: string;
description: string;
}

const OptionRadio = createTypedRadioGroup<Option>();

export function OptionPicker() {
const options: Option[] = [
{ id: "1", label: "Option 1", description: "First option" },
{ id: "2", label: "Option 2", description: "Second option" },
{ id: "3", label: "Option 3", description: "Third option" },
];

const [selected, setSelected] = useState<Option>(options[0]);

return (
<OptionRadio.RadioGroup value={selected} onValueChange={setSelected}>
{options.map((option) => (
<OptionRadio.RadioItem key={option.id} value={option}>
<OptionRadio.RadioIndicator />
<div>
<div className="font-medium">{option.label}</div>
<div className="text-sm text-gray-500">{option.description}</div>
</div>
</OptionRadio.RadioItem>
))}
</OptionRadio.RadioGroup>
);
}






Trade-offs – When to Use (or Skip) This

Use the factory when:




  • Strong value type safety is non-negotiable

  • Children order is predictable (list/group items)

  • You want that sweet dot-notation DX



Skip it (use plain context + sub-components) when:




  • 1. You need a completely free-form layout/order

  • 2. Building low-level primitives (just use Radix/Ark/Base UI directly)



Final Thoughts

This little factory has been my default for typed groups since 2026 simple, type-safe, minimal boilerplate.

Ideas to level it up:



Propagate disabled from group → items




  • Add defaultValue support

  • Generalize for <Tabs>, <Accordion>, <RadioGroup> etc.

  • Package it as a tiny npm lib



Steal it, tweak it, ship with it.

What’s your go-to pattern for typed compound components in 2026? Drop a comment — or hit me up on X: @sambalicious_ 🚀

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Type-Safe Compound Components in 2026 – The Generic Factory Pattern I Actually Use

Thematisch verwandte Begriffe: TypeSafe, Compound, Components, 2026 · 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-17636 | IBM Financial Transaction Manager (FTM) for RedHat OpenShift could allow…
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