TypeScript
Every primitive ships with types. Controlled/uncontrolled props narrow to the same value type, so you rarely need to annotate.
Generic value types
Primitives with a string value (Select, Tabs, RadioGroup, Accordion) accept a string literal type inferred from usage. You can narrow further with a generic union:
type Fruit = "apple" | "banana" | "cherry";
const [fruit, setFruit] = useState<Fruit>("apple");
<Select value={fruit} onValueChange={setFruit}>
...
</Select>onValueChange will infer Fruit here — no cast required.
Controlled vs uncontrolled props
Every stateful primitive exposes matched pairs: value + onValueChange, open + onOpenChange, checked + onCheckedChange. In TypeScript, you can pass either the controlled pair or the default* variant, but not both — the types are set up as a discriminated union.
Rendering as a different element
Some parts accept an asChild boolean that merges props onto their child instead of rendering their own element. TypeScript keeps the child’s ref types:
<Dialog.Trigger asChild>
<Link href="/next">Go</Link>
</Dialog.Trigger>Behavior handlers and ARIA are still applied.