TypeScript usage and type safety with Toro UI.
Toro UI is written in TypeScript and provides full type safety across all components. Types are inferred automatically — you rarely need to import or annotate them manually.
Components accept typed props via React.ComponentProps and Base UI's primitive types. This gives you autocompletion for every prop:
import { Button } from "@/components/ui/button"
// TypeScript knows about variant, size, className, onClick, etc.
<Button variant="outline" size="lg" onClick={() => {}}>
Click me
</Button>Components use class-variance-authority for variant definitions. Variant props are automatically inferred from the CVA config:
import { cva, type VariantProps } from "class-variance-authority"
const buttonVariants = cva("...", {
variants: {
variant: {
default: "...",
outline: "...",
ghost: "...",
},
size: {
default: "...",
sm: "...",
lg: "...",
},
},
})
// VariantProps extracts the types automatically
type ButtonProps = VariantProps<typeof buttonVariants>
// { variant?: "default" | "outline" | "ghost"; size?: "default" | "sm" | "lg" }When you add a new variant key, TypeScript picks it up immediately — no separate type updates needed.
Components that wrap Base UI primitives use their typed props directly:
import { Dialog as DialogPrimitive } from "@base-ui/react/dialog"
function Dialog(props: DialogPrimitive.Root.Props) {
return <DialogPrimitive.Root {...props} />
}
function DialogContent(props: DialogPrimitive.Popup.Props) {
return <DialogPrimitive.Popup {...props} />
}This ensures your components accept the same props as the underlying primitives, including event handlers, refs, and accessibility attributes.
To add custom props to a component, intersect with the base types:
import { Button as ButtonPrimitive } from "@base-ui/react/button"
import { type VariantProps } from "class-variance-authority"
function Button({
className,
variant,
size,
...props
}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
return (
<ButtonPrimitive
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}Event handlers are fully typed through the Base UI primitives:
<Dialog
onOpenChange={(open: boolean) => {
// TypeScript knows `open` is boolean
console.log(open)
}}
>
{/* ... */}
</Dialog>cn UtilityThe cn function accepts ClassValue from clsx, which covers strings, arrays, objects, and conditional expressions:
import { cn } from "@/lib/utils"
import { type ClassValue } from "clsx"
// All of these are valid
cn("px-4 py-2")
cn("px-4", isActive && "bg-primary")
cn("px-4", { "bg-primary": isActive })
cn(["px-4", "py-2"])Toro UI is built with TypeScript's strict mode enabled. This catches common issues at compile time:
strictNullChecks — No accidental undefined accessnoImplicitAny — Every value has a known typestrictFunctionTypes — Function parameters are checked correctlyComponents export both the component and its variant definitions, so you can reuse types in your own components:
import { buttonVariants } from "@/components/ui/button"
import { type VariantProps } from "class-variance-authority"
type ButtonVariant = VariantProps<typeof buttonVariants>["variant"]
// "default" | "outline" | "secondary" | "ghost" | "destructive" | "link"