shadimage
kanpeki
uibutton-groupsource: ok · hit

Button Group

npx shadcn add "https://kanpeki.vercel.app/r/button-group.json"
free

No selection tags yet — empty means no evidence, not lack of support.

Dependencies

Bundle weight · light
ui-primitives@base-ui/react

Registry dependencies

@kanpeki/separator

Files4 · source cached 10/8/2026, 12:51:18 PM

src/registry/ui/button-group/index.ts
export * from "./button-group";
export * as ButtonGroup from "./namespace";
export * from "./styles";
src/registry/ui/button-group/button-group.tsx
"use client";

import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
import type { VariantProps } from "cva";
import { Group } from "react-aria-components";
import { Separator } from "~/registry/ui/separator";
import { ButtonGroupStyles } from "./styles";

export interface ButtonGroupRootProps
  extends React.ComponentProps<typeof Group>,
    VariantProps<typeof ButtonGroupStyles.Root> {}

export function ButtonGroupRoot({
  className,
  orientation,
  ...props
}: ButtonGroupRootProps) {
  return (
    <Group
      className={ButtonGroupStyles.Root({ orientation, className })}
      data-orientation={orientation}
      data-slot="button-group-root"
      {...props}
    />
  );
}

export interface ButtonGroupTextProps extends useRender.ComponentProps<"div"> {}

export function ButtonGroupText({
  render,
  className,
  ...props
}: ButtonGroupTextProps) {
  const defaultProps: useRender.ElementProps<"div"> = {
    className: ButtonGroupStyles.Text({ className }),
  };

  return useRender({
    defaultTagName: "div",
    render,
    props: mergeProps<"div">(defaultProps, props),
    state: {
      slot: "button-group-text",
    },
  });
}

export interface ButtonGroupSeparatorProps
  extends React.ComponentProps<typeof Separator> {}

export function ButtonGroupSeparator({
  className,
  orientation = "vertical",
  ...props
}: ButtonGroupSeparatorProps) {
  return (
    <Separator
      className={ButtonGroupStyles.Separator({ className })}
      data-slot="button-group-separator"
      orientation={orientation}
      {...props}
    />
  );
}
src/registry/ui/button-group/namespace.ts
export {
  ButtonGroupRoot as Root,
  ButtonGroupSeparator as Separator,
  ButtonGroupText as Text,
} from "./button-group";
src/registry/ui/button-group/styles.ts
import { cva } from "~/registry/lib/cva";

export const ButtonGroupStyles = {
  Root: cva({
    base: "flex w-fit items-stretch *:focus-visible:relative *:focus-visible:z-10 has-[>[data-slot=button-group-root]]:gap-2 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",
    },
  }),
  Text: cva({
    base: "flex items-center gap-2 rounded-md border bg-muted px-4 font-medium text-sm shadow-xs [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
  }),
  Separator: cva({
    base: "relative m-0! orientation-vertical:h-auto self-stretch bg-input",
  }),
};

More from kanpeki

Browse all components in this library →