Building flexible UIs with component composition patterns.
Toro UI components are designed to be composed together. Rather than configuring behavior through props on a single monolithic component, you assemble UIs from small, focused parts.
Most Toro UI components follow a compound component pattern, where each component is made up of smaller parts:
import {
Dialog,
DialogTrigger,
DialogContent,
DialogTitle,
DialogDescription,
} from "@/components/ui/dialog"
<Dialog>
<DialogTrigger>
<Button>Open</Button>
</DialogTrigger>
<DialogContent>
<DialogTitle>Are you sure?</DialogTitle>
<DialogDescription>This action cannot be undone.</DialogDescription>
</DialogContent>
</Dialog>Each part handles one responsibility — trigger, content, title — and you compose them into the structure you need. Skip parts you don't need, rearrange them, or add your own elements between them.
Under the hood, Toro UI components wrap Base UI primitives. Base UI provides the accessible behavior — keyboard navigation, focus management, ARIA attributes — while Toro UI adds the styling and defaults:
// Toro UI's Button wraps Base UI's Button primitive
import { Button as ButtonPrimitive } from "@base-ui/react/button"
function Button({ className, variant, size, ...props }) {
return (
<ButtonPrimitive
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}This means you get full accessibility support — focus rings, keyboard handling, screen reader announcements — without any extra work.
Every Toro UI component sets a data-slot attribute identifying its role. This enables parent-aware styling and makes it easy to target specific parts:
<Field>
<FieldLabel>Email</FieldLabel> {/* data-slot="field-label" */}
<Input /> {/* data-slot="input" */}
<FieldDescription> {/* data-slot="field-description" */}
We'll never share your email.
</FieldDescription>
</Field>You can use data slots for contextual styling:
// Make all labels bold within a specific container
<div className="[&_[data-slot=field-label]]:font-bold">
<Field>
<FieldLabel>Name</FieldLabel>
<Input />
</Field>
</div>Components are built to nest naturally. A common pattern is combining Field components with form controls:
<FieldSet>
<FieldLegend>Account Details</FieldLegend>
<FieldGroup>
<Field>
<FieldLabel>Username</FieldLabel>
<Input placeholder="Enter username" />
</Field>
<Field>
<FieldLabel>Email</FieldLabel>
<Input type="email" placeholder="Enter email" />
</Field>
</FieldGroup>
</FieldSet>Since every component lives in your codebase, you can compose higher-level components from Toro UI primitives:
import { Button } from "@/components/ui/button"
import { Spinner } from "@/components/ui/spinner"
function LoadingButton({ loading, children, ...props }) {
return (
<Button disabled={loading} {...props}>
{loading && <Spinner />}
{children}
</Button>
)
}Toro UI components don't impose layout decisions. Wrap them with your own layout as needed:
<div className="flex gap-2">
<Button variant="outline">Cancel</Button>
<Button>Confirm</Button>
</div>Use ButtonGroup when you need connected buttons:
<ButtonGroup>
<Button variant="outline">Left</Button>
<Button variant="outline">Center</Button>
<Button variant="outline">Right</Button>
</ButtonGroup>