ToroUI

TypeScript

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.

Component Props

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>

Variant Types with CVA

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.

Base UI Primitive Types

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.

Extending Component Props

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}
    />
  )
}

Typing Event Handlers

Event handlers are fully typed through the Base UI primitives:

<Dialog
  onOpenChange={(open: boolean) => {
    // TypeScript knows `open` is boolean
    console.log(open)
  }}
>
  {/* ... */}
</Dialog>

The cn Utility

The 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"])

Strict Mode

Toro UI is built with TypeScript's strict mode enabled. This catches common issues at compile time:

  • strictNullChecks — No accidental undefined access
  • noImplicitAny — Every value has a known type
  • strictFunctionTypes — Function parameters are checked correctly

Exporting Types

Components 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"