https://
import { ChevronDownIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
ButtonGroup,
ButtonGroupSeparator,
ButtonGroupText,
} from "@/components/ui/button-group"
export default function ButtonGroupDemo() {
return (
<div className="flex flex-col gap-6">
<ButtonGroup>
<Button variant="outline">Copy</Button>
<Button variant="outline">Paste</Button>
<Button variant="outline" size="icon">
<ChevronDownIcon />
</Button>
</ButtonGroup>
<ButtonGroup>
<ButtonGroupText>https://</ButtonGroupText>
<ButtonGroupSeparator />
<Button variant="outline">ui.logic2b.com</Button>
</ButtonGroup>
</div>
)
}Installation
npx logic2b@latest add button-groupWorking with an AI assistant? Copy this prompt into Claude Code, Cursor or Copilot — it carries the install steps, a no-CLI fallback and a verification checklist.
Add the "button-group" item from the logic2b ui registry (https://ui.logic2b.com) to this project. Follow every step and verify at the end. ## Steps 1. If the project has no `components.json` yet, set up logic2b ui first: ```bash npx logic2b@latest init ``` 2. Install the item (the CLI resolves registry dependencies automatically): ```bash npx logic2b@latest add button-group ``` 3. Install any npm dependencies the command prints. ## If the CLI is not available Do it manually from the registry payload: 1. Fetch https://ui.logic2b.com/r/button-group.json. 2. Write every entry in `files[]` to its mapped project path (see below). 3. Repeat (recursively) for each name in `registryDependencies`. 4. Install every package listed in `dependencies` across those payloads. ## Registry reference - Index of every item: https://ui.logic2b.com/r/index.json - Item payload (full source): https://ui.logic2b.com/r/<name>.json - Usage examples per item: https://ui.logic2b.com/r/demos/index.json → https://ui.logic2b.com/r/demos/<demo>.json - Docs index for agents: https://ui.logic2b.com/llms.txt (full docs: https://ui.logic2b.com/llms-full.txt) - Every docs page is Markdown when you append `.md` to its URL. Registry file paths map to project paths like this: - `ui/*` → `@/components/ui/*` - `blocks/<name>/*` → `@/components/*` - `charts/*` → `@/components/charts/*` - `hooks/*` → `@/hooks/*` - `lib/utils.ts` → `@/lib/utils.ts` - `theme.css` → next to the project's Tailwind entry stylesheet ## Conventions (do not break these) - Tailwind CSS v4 (CSS-first config; no tailwind.config.js needed) and React 19. - All colors go through semantic tokens (`bg-primary`, `text-muted-foreground`, `border-border`, …). Never hardcode hex/oklch values in components. - Dark mode is class-based: toggle `.dark` on `<html>`. - Icons come from lucide-react. ## Verify before finishing 1. The dev server starts and the app renders without console errors. 2. TypeScript compiles (`tsc --noEmit` or the framework's check). 3. Components use the theme: a `<Button>` shows the primary token color, and toggling `.dark` on `<html>` switches the palette.
Copy into ui/button-group.tsx:
import * as React from "react"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
import { Separator } from "@/components/ui/separator"
function ButtonGroup({
className,
orientation = "horizontal",
...props
}: React.ComponentProps<"div"> & {
orientation?: "horizontal" | "vertical"
}) {
return (
<div
role="group"
data-slot="button-group"
data-orientation={orientation}
className={cn(
"flex w-fit items-stretch [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 [&>input]:flex-1",
orientation === "horizontal"
? "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none"
: "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
className
)}
{...props}
/>
)
}
function ButtonGroupText({
className,
asChild = false,
...props
}: React.ComponentProps<"div"> & { asChild?: boolean }) {
const Comp = asChild ? Slot.Root : "div"
return (
<Comp
data-slot="button-group-text"
className={cn(
"bg-muted flex items-center gap-2 rounded-md border px-4 text-sm font-medium shadow-xs [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function ButtonGroupSeparator({
className,
orientation = "vertical",
...props
}: React.ComponentProps<typeof Separator>) {
return (
<Separator
data-slot="button-group-separator"
orientation={orientation}
className={cn(
"bg-input relative !m-0 self-stretch data-[orientation=vertical]:h-auto",
className
)}
{...props}
/>
)
}
export { ButtonGroup, ButtonGroupText, ButtonGroupSeparator }Usage
import { ButtonGroup, ButtonGroupSeparator, ButtonGroupText } from "@/components/ui/button-group"
<ButtonGroup>
<Button variant="outline">Copy</Button>
<Button variant="outline">Paste</Button>
</ButtonGroup>
<ButtonGroup>
<ButtonGroupText>https://</ButtonGroupText>
<ButtonGroupSeparator />
<Button variant="outline">example.com</Button>
</ButtonGroup>
API
| Prop | Type | Default |
|---|---|---|
orientation (ButtonGroup) |
horizontal | vertical |
horizontal |
orientation (ButtonGroupSeparator) |
horizontal | vertical |
vertical |
asChild (ButtonGroupText) |
boolean |
false |
All three parts also forward the native props of the element they render
(ButtonGroup and ButtonGroupText render a <div>; ButtonGroupSeparator
renders a Separator).
Examples
Vertical
import { MinusIcon, PlusIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
export default function ButtonGroupVerticalDemo() {
return (
<ButtonGroup orientation="vertical">
<Button variant="outline" size="icon" aria-label="Increment">
<PlusIcon />
</Button>
<Button variant="outline" size="icon" aria-label="Decrement">
<MinusIcon />
</Button>
</ButtonGroup>
)
}With an input
import { Button } from "@/components/ui/button"
import { ButtonGroup } from "@/components/ui/button-group"
import { Input } from "@/components/ui/input"
export default function ButtonGroupInputDemo() {
return (
<ButtonGroup className="w-[320px]">
<Input placeholder="Search..." />
<Button variant="outline">Search</Button>
</ButtonGroup>
)
}