diff --git a/components/ui/8bit/button-group.tsx b/components/ui/8bit/button-group.tsx
new file mode 100644
index 00000000..fb233050
--- /dev/null
+++ b/components/ui/8bit/button-group.tsx
@@ -0,0 +1,114 @@
+import type * as React from "react";
+
+import { cn } from "@/lib/utils";
+
+import {
+ ButtonGroup as ShadcnButtonGroup,
+ ButtonGroupText as ShadcnButtonGroupText,
+ buttonGroupVariants,
+} from "@/components/ui/button-group";
+import { Separator } from "@/components/ui/8bit/separator";
+
+import "@/components/ui/8bit/styles/retro.css";
+
+export { buttonGroupVariants };
+
+// ─── ButtonGroup ──────────────────────────────────────────────────────────────
+
+export type BitButtonGroupProps = React.ComponentProps<
+ typeof ShadcnButtonGroup
+>;
+
+/**
+ * 8-bit ButtonGroup wraps the shadcn ButtonGroup and adds a shared retro
+ * pixelated border around the whole group.
+ *
+ * API matches shadcn: `orientation`, `data-slot="button-group"`.
+ * No React context — child button sizing and layout is handled via CSS
+ * child selectors in `buttonGroupVariants`, identical to shadcn.
+ */
+function ButtonGroup({
+ className,
+ orientation = "horizontal",
+ children,
+ ...props
+}: BitButtonGroupProps) {
+ return (
+
+ {/* Shared outer pixelated border */}
+ {/* Top */}
+
+ {/* Bottom */}
+
+ {/* Left */}
+
+ {/* Right */}
+
+ {/* Corners */}
+
+
+
+
+
+
+ {children}
+
+
+ );
+}
+
+// ─── ButtonGroupSeparator ────────────────────────────────────────────────────
+
+export type BitButtonGroupSeparatorProps = React.ComponentProps<
+ typeof Separator
+>;
+
+/**
+ * 8-bit ButtonGroupSeparator renders a pixel-art dashed divider between items.
+ * Wraps the 8-bit Separator component (which uses a pixel-dash background pattern).
+ * Defaults to `orientation="vertical"` for use inside a horizontal ButtonGroup.
+ */
+function ButtonGroupSeparator({
+ className,
+ orientation = "vertical",
+ ...props
+}: BitButtonGroupSeparatorProps) {
+ return (
+
+ );
+}
+
+// ─── ButtonGroupText ─────────────────────────────────────────────────────────
+
+export type BitButtonGroupTextProps = React.ComponentProps<
+ typeof ShadcnButtonGroupText
+>;
+
+/**
+ * 8-bit ButtonGroupText renders a retro-styled text label inside a ButtonGroup.
+ * Useful for split-button patterns (e.g. a label prefix before action buttons).
+ */
+function ButtonGroupText({ className, ...props }: BitButtonGroupTextProps) {
+ return (
+
+ );
+}
+
+export { ButtonGroup, ButtonGroupSeparator, ButtonGroupText };
diff --git a/components/ui/8bit/button.tsx b/components/ui/8bit/button.tsx
index 8b12184c..254bf951 100644
--- a/components/ui/8bit/button.tsx
+++ b/components/ui/8bit/button.tsx
@@ -61,8 +61,8 @@ function Button({ children, asChild, ...props }: BitButtonProps) {
{children}
{variant !== "ghost" && variant !== "link" && size !== "icon" && (
- <>
- {/* Pixelated border */}
+
+ {/* Pixelated border — hidden when inside ButtonGroup */}
@@ -84,18 +84,18 @@ function Button({ children, asChild, ...props }: BitButtonProps) {
>
)}
- >
+
)}
{size === "icon" && (
- <>
+
)}
) : (
@@ -103,8 +103,8 @@ function Button({ children, asChild, ...props }: BitButtonProps) {
{children}
{variant !== "ghost" && variant !== "link" && size !== "icon" && (
- <>
- {/* Pixelated border */}
+
+ {/* Pixelated border — hidden when inside ButtonGroup */}
@@ -126,18 +126,18 @@ function Button({ children, asChild, ...props }: BitButtonProps) {
>
)}
- >
+
)}
{size === "icon" && (
- <>
+
)}
>
)}
diff --git a/components/ui/button-group.tsx b/components/ui/button-group.tsx
new file mode 100644
index 00000000..cd550d7a
--- /dev/null
+++ b/components/ui/button-group.tsx
@@ -0,0 +1,83 @@
+import { cva, type VariantProps } from "class-variance-authority"
+import { Slot } from "radix-ui"
+
+import { cn } from "@/lib/utils"
+import { Separator } from "@/components/ui/separator"
+
+const buttonGroupVariants = cva(
+ "flex w-fit items-stretch has-[>[data-slot=button-group]]:gap-2 [&>*]:focus-visible:relative [&>*]:focus-visible:z-10 has-[select[aria-hidden=true]:last-child]:[&>[data-slot=select-trigger]:last-of-type]:rounded-r-md [&>[data-slot=select-trigger]:not([class*='w-'])]:w-fit [&>input]:flex-1",
+ {
+ variants: {
+ orientation: {
+ horizontal:
+ "[&>*:not(:first-child)]:rounded-l-none [&>*:not(:first-child)]:border-l-0 [&>*:not(:last-child)]:rounded-r-none",
+ vertical:
+ "flex-col [&>*:not(:first-child)]:rounded-t-none [&>*:not(:first-child)]:border-t-0 [&>*:not(:last-child)]:rounded-b-none",
+ },
+ },
+ defaultVariants: {
+ orientation: "horizontal",
+ },
+ }
+)
+
+function ButtonGroup({
+ className,
+ orientation,
+ ...props
+}: React.ComponentProps<"div"> & VariantProps) {
+ return (
+
+ )
+}
+
+function ButtonGroupText({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"div"> & {
+ asChild?: boolean
+}) {
+ const Comp = asChild ? Slot.Root : "div"
+
+ return (
+
+ )
+}
+
+function ButtonGroupSeparator({
+ className,
+ orientation = "vertical",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ ButtonGroup,
+ ButtonGroupSeparator,
+ ButtonGroupText,
+ buttonGroupVariants,
+}
diff --git a/config/nav-items.ts b/config/nav-items.ts
index e4ac665c..5be18f31 100644
--- a/config/nav-items.ts
+++ b/config/nav-items.ts
@@ -27,6 +27,10 @@ const components = [
title: "Button",
url: "/docs/components/button",
},
+ {
+ title: "Button Group",
+ url: "/docs/components/button-group",
+ },
{
title: "Calendar",
url: "/docs/components/calendar",
diff --git a/content/docs/components/button-group.mdx b/content/docs/components/button-group.mdx
new file mode 100644
index 00000000..ab766086
--- /dev/null
+++ b/content/docs/components/button-group.mdx
@@ -0,0 +1,178 @@
+---
+title: Button Group
+description: A container that groups related 8-bit buttons together with a shared retro pixelated border.
+---
+
+import { Button } from "@/components/ui/8bit/button";
+import { ButtonGroup, ButtonGroupSeparator, ButtonGroupText } from "@/components/ui/8bit/button-group";
+import CopyCommandButton from "@/components/copy-command-button";
+import InstallationCommands from "@/components/installation-commands";
+import ComponentPreview from "@/components/component-preview";
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Examples
+
+---
+
+### Horizontal (default)
+
+
+
+
+
+
+
+
+
+
+
+```tsx
+
+
+
+
+
+
+
+```
+
+### Vertical
+
+
+
+
+
+
+
+
+
+
+
+```tsx
+
+
+
+
+
+
+
+```
+
+### With Outline Variant
+
+
+
+
+
+
+
+
+
+
+
+```tsx
+
+
+
+
+
+
+
+```
+
+### With Text Label
+
+
+
+ Mode
+
+
+
+
+
+
+
+```tsx
+
+ Mode
+
+
+
+
+
+```
+
+## Installation
+
+---
+
+
+
+## Usage
+
+---
+
+```tsx
+import { Button } from "@/components/ui/8bit/button";
+import {
+ ButtonGroup,
+ ButtonGroupSeparator,
+ ButtonGroupText,
+} from "@/components/ui/8bit/button-group";
+```
+
+```tsx
+
+
+
+
+
+
+
+```
+
+## Props
+
+---
+
+### ButtonGroup
+
+| Prop | Type | Default |
+| --- | --- | --- |
+| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` |
+| `aria-label` | `string` | — |
+| `className` | `string` | — |
+
+### ButtonGroupSeparator
+
+Renders a pixel-art dashed divider. Wraps the 8-bit `Separator` component — defaults to `orientation="vertical"` for horizontal groups.
+
+| Prop | Type | Default |
+| --- | --- | --- |
+| `orientation` | `"horizontal" \| "vertical"` | `"vertical"` |
+| `className` | `string` | — |
+
+### ButtonGroupText
+
+Renders a retro-styled text label inside the group — useful for split-button patterns like a mode prefix.
+
+| Prop | Type | Default |
+| --- | --- | --- |
+| `asChild` | `boolean` | `false` |
+| `className` | `string` | — |
diff --git a/registry.json b/registry.json
index d5eafad9..e1ea2d84 100644
--- a/registry.json
+++ b/registry.json
@@ -47,6 +47,35 @@
}
]
},
+ {
+ "name": "button-group",
+ "type": "registry:component",
+ "title": "8-bit Button Group",
+ "description": "Groups multiple 8-bit buttons together inside a shared retro pixelated border.",
+ "registryDependencies": ["button", "button-group", "separator"],
+ "files": [
+ {
+ "path": "components/ui/8bit/button-group.tsx",
+ "type": "registry:component",
+ "target": "components/ui/8bit/button-group.tsx"
+ },
+ {
+ "path": "components/ui/8bit/button.tsx",
+ "type": "registry:component",
+ "target": "components/ui/8bit/button.tsx"
+ },
+ {
+ "path": "components/ui/8bit/separator.tsx",
+ "type": "registry:component",
+ "target": "components/ui/8bit/separator.tsx"
+ },
+ {
+ "path": "components/ui/8bit/styles/retro.css",
+ "type": "registry:component",
+ "target": "components/ui/8bit/styles/retro.css"
+ }
+ ]
+ },
{
"name": "input",
"type": "registry:component",