Button
Triggers an action or event when activated.
Overview
The Button component provides a versatile and customizable button system with multiple variants, appearances, sizes, and shapes. It supports polymorphic rendering with the render prop (Base UI pattern), as well as special features like pending states and URL state management.
Use Button.Wrapper to visually attach adjacent buttons for split-button layouts while keeping each inner action as a real Button.
Usage
import { Button } from "@tilt-legal/cubitt-components/primitives";<Button>Click me</Button>Using Cubitt's URL-state hooks, you can sync button clicks with the URL by providing paramName and paramSetValue:
// Clicking sets ?action=save in the URL
<Button paramName="action" paramSetValue="save">
Save Draft
</Button>
// Advanced options:
<Button
paramName="action"
paramSetValue="publish"
paramDebounce={300} // Debounce URL updates
>
Publish
</Button>Use the render prop to render the button as a different element while preserving button styling:
import { Button } from "@tilt-legal/cubitt-components/primitives";
import { Link } from "@tanstack/react-router";
<Button render={<Link href="/home" />}>Go Home</Button>;Examples
Secondary
import { Button } from "@tilt-legal/cubitt-components/primitives";
export default function Component() {
return <Button variant="secondary">Secondary</Button>;
}Frost
import { Button } from "@tilt-legal/cubitt-components/primitives";
export default function Component() {
return <Button variant="frost">Frost</Button>;
}Destructive
import { Button } from "@tilt-legal/cubitt-components/primitives";
export default function Component() {
return <Button variant="destructive">Destructive</Button>;
}Outline
import { Button } from "@tilt-legal/cubitt-components/primitives";
export default function Component() {
return <Button variant="outline">Outline</Button>;
}Ghost
import { Button } from "@tilt-legal/cubitt-components/primitives";
export default function Component() {
return <Button variant="ghost">Ghost</Button>;
}With Icon
import { Button } from "@tilt-legal/cubitt-components/primitives";
import {
UserPen } from "@tilt-legal/cubitt-icons/ui/outline";
export default function Component() {
return (
<Button variant="secondary">
<UserPen />
With Icon
</Button>
);
}Icon Only
import { Button } from "@tilt-legal/cubitt-components/primitives";
import {
UserPen } from "@tilt-legal/cubitt-icons/ui/outline";
export default function Component() {
return (
<Button variant="secondary" mode="icon">
<UserPen />
</Button>
);
}Pending
Shows a loading spinner while an action is in progress.
"use client";
import { Button } from "@tilt-legal/cubitt-components/primitives";
import { useEffect, useState } from "react";
export default function Component() {
const [isPending, setIsPending] = useState(false);
useEffect(() => {
if (!isPending) {
return;
}
const timeout = window.setTimeout(() => {
setIsPending(false);
}, 2000);
return () => window.clearTimeout(timeout);
}, [isPending]);
return (
<Button
variant="secondary"
onClick={() => setIsPending(true)}
pending={isPending}
>
Submit
</Button>
);
}With Badge
Combine buttons with badges for notifications and counters.
import { Button } from "@tilt-legal/cubitt-components/primitives";
import {
Bell,
Inbox,
CircleCheck } from "@tilt-legal/cubitt-icons/ui/outline";
export default function Component() {
return (
<div className="flex flex-col items-center gap-10">
<Button variant="outline" mode="icon" className="relative">
<Bell />
<span className="border-2 border-background rounded-full size-3 bg-primary absolute -top-1 -end-1 animate-bounce" />
</Button>
<Button variant="outline" mode="icon" className="relative">
<Inbox />
<Badge
variant="primary"
shape="circle"
size="sm"
className="absolute top-0 start-full -translate-y-1/2 -translate-x-1/2 rtl:translate-x-1/2"
>
5
</Badge>
</Button>
<Button variant="outline" className="relative">
<Inbox />
Messages
<Badge
variant="destructive"
shape="circle"
size="sm"
className="absolute top-0 start-full -translate-y-1/2 -translate-x-1/2 rtl:translate-x-1/2"
>
5
</Badge>
</Button>
<Button variant="outline">
<CircleCheck />
Notifications
<Badge variant="brand" size="sm">
10+
</Badge>
</Button>
</div>
);
}Sizes
import { Button } from "@tilt-legal/cubitt-components/primitives";
import {
UserPen } from "@tilt-legal/cubitt-icons/ui/outline";
export default function Component() {
return (
<div className="flex flex-col items-center justify-center gap-6">
<div className="flex items-center gap-4">
<Button variant="outline" size="sm">
<UserPen />
Small
</Button>
<Button variant="outline">
<UserPen />
Medium
</Button>
<Button variant="outline" size="lg">
<UserPen />
Large
</Button>
</div>
<div className="flex items-center gap-4">
<Button variant="outline" size="sm" mode="icon">
<UserPen />
</Button>
<Button variant="outline" mode="icon">
<UserPen />
</Button>
<Button variant="outline" size="lg" mode="icon">
<UserPen />
</Button>
</div>
</div>
);
}Link
Style buttons as links using variant="link".
import { Button } from "@tilt-legal/cubitt-components/primitives";
import { Link } from "@tanstack/react-router";
export default function Component() {
return (
<div className="flex flex-col items-center gap-6">
<Button variant="link" size="sm" render={<Link href="#" />}>
Link Button
</Button>
<Button variant="link" render={<Link href="#" />}>
Link Button
</Button>
<Button variant="link" size="lg" render={<Link href="#" />}>
Link Button
</Button>
</div>
);
}Split Button
Use Button.Wrapper when you want a primary action and an adjacent secondary trigger to read as a single control.
"use client";
import {
Button,
Menu,
MenuContent,
MenuItem,
MenuSeparator,
MenuTrigger,
} from "@tilt-legal/cubitt-components/primitives";
import { useEffect, useState } from "react";
export default function Component() {
const PENDING_RESET_DELAY_MS = 2000;
const variants = [
{ label: "Default", moreLabel: "More primary actions", variant: "default" },
{ label: "Secondary", moreLabel: "More secondary actions", variant: "secondary" },
{ label: "Destructive", moreLabel: "More destructive actions", variant: "destructive" },
{ label: "Frost", moreLabel: "More frost actions", variant: "frost" },
{ label: "Outline", moreLabel: "More outline actions", variant: "outline" },
] as const;
function SplitButtonItem({
label,
moreLabel,
variant,
}: (typeof variants)[number]) {
const [isPending, setIsPending] = useState(false);
useEffect(() => {
if (!isPending) {
return;
}
const timeout = window.setTimeout(() => {
setIsPending(false);
}, PENDING_RESET_DELAY_MS);
return () => window.clearTimeout(timeout);
}, [isPending]);
return (
<Menu>
<Button.Wrapper>
<Button
onClick={() => setIsPending(true)}
pending={isPending}
variant={variant}
>
{label}
</Button>
<Button
aria-label={moreLabel}
mode="icon"
render={<MenuTrigger />}
variant={variant}
>
<Button.Arrow className="me-0 ms-0" />
</Button>
</Button.Wrapper>
<MenuContent align="end">
<MenuItem>Duplicate</MenuItem>
<MenuItem>Move</MenuItem>
<MenuSeparator />
<MenuItem variant="destructive">Archive</MenuItem>
</MenuContent>
</Menu>
);
}
return (
<div className="flex flex-wrap items-center gap-4">
{variants.map((item) => (
<SplitButtonItem key={item.variant} {...item} />
))}
</div>
);
}Disabled
import { Button } from "@tilt-legal/cubitt-components/primitives";
export default function Component() {
return (
<div className="flex items-center gap-4">
<Button disabled>Primary</Button>
<Button variant="secondary" disabled>
Secondary
</Button>
<Button variant="outline" disabled>
Outline
</Button>
</div>
);
}URL State
Sync button clicks with URL parameters. Click a button to see the URL update with the corresponding action.
import { Button } from "@tilt-legal/cubitt-components/primitives";
export default function Component() {
return (
<div className="flex items-center gap-4">
<Button paramName="demo-action" paramSetValue="save" variant="secondary">
Save Draft
</Button>
<Button paramName="demo-action" paramSetValue="publish">
Publish
</Button>
<Button
paramName="demo-action"
paramSetValue="archive"
variant="destructive"
>
Archive
</Button>
</div>
);
}API Reference
Button
The root component for creating buttons with multiple variants, appearances, sizes, and shapes. Supports polymorphic rendering via the render prop and URL state management.
Prop
Type
URL State Props
When provided with a paramName, the button will set URL parameters when clicked.
Prop
Type