V1.0

Base Components

Button Group

Button groups are a set of buttons sticked together in a horizontal line.

Installation

Install the following dependencies:

terminal
npm install @radix-ui/react-slot

Create a button-group.tsx file and paste the following code into it.

/components/ui/button-group.tsx
// AlignUI ButtonGroup v0.0.0
 
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
 
import { PolymorphicComponentProps } from '@/utils/polymorphic';
import { recursiveCloneChildren } from '@/utils/recursive-clone-children';
import { tv, type VariantProps } from '@/utils/tv';
 
const BUTTON_GROUP_ROOT_NAME = 'ButtonGroupRoot';
const BUTTON_GROUP_ITEM_NAME = 'ButtonGroupItem';
const BUTTON_GROUP_ICON_NAME = 'ButtonGroupIcon';
 
export const buttonGroupVariants = tv({
  slots: {
    root: 'flex -space-x-[1.5px]',
    item: [
      // base
      'group relative flex items-center justify-center whitespace-nowrap bg-bg-white-0 text-center text-text-sub-600 outline-none',
      'border border-stroke-soft-200',
      'transition duration-200 ease-out',
      // hover
      'hover:bg-bg-weak-50',
      // focus
      'focus:bg-bg-weak-50 focus:outline-none',
      // active
      'data-[state=on]:bg-bg-weak-50',
      'data-[state=on]:text-text-strong-950',
      // disabled
      'disabled:pointer-events-none disabled:bg-bg-weak-50',
      'disabled:text-text-disabled-300',
    ],
    icon: 'shrink-0',
  },
  variants: {
    size: {
      small: {
        item: [
          // base
          'h-9 gap-4 px-4 text-label-sm',
          // radius
          'first:rounded-l-lg last:rounded-r-lg',
        ],
        icon: [
          // base
          '-mx-2 size-5',
        ],
      },
      xsmall: {
        item: [
          // base
          'h-8 gap-3.5 px-3.5 text-label-sm',
          // radius
          'first:rounded-l-lg last:rounded-r-lg',
        ],
        icon: [
          // base
          '-mx-2 size-5',
        ],
      },
      xxsmall: {
        item: [
          // base
          'h-6 gap-3 px-3 text-label-xs',
          // radius
          'first:rounded-l-md last:rounded-r-md',
        ],
        icon: [
          // base
          '-mx-2 size-4',
        ],
      },
    },
  },
  defaultVariants: {
    size: 'small',
  },
});
 
type ButtonGroupSharedProps = VariantProps<typeof buttonGroupVariants>;
 
type ButtonGroupRootProps = VariantProps<typeof buttonGroupVariants> &
  React.HTMLAttributes<HTMLDivElement> & {
    asChild?: boolean;
  };
 
const ButtonGroupRoot = React.forwardRef<HTMLDivElement, ButtonGroupRootProps>(
  ({ asChild, children, className, size, ...rest }, forwardedRef) => {
    const uniqueId = React.useId();
    const Component = asChild ? Slot : 'div';
    const { root } = buttonGroupVariants({ size });
 
    const sharedProps: ButtonGroupSharedProps = {
      size,
    };
 
    const extendedChildren = recursiveCloneChildren(
      children as React.ReactElement[],
      sharedProps,
      [BUTTON_GROUP_ITEM_NAME, BUTTON_GROUP_ICON_NAME],
      uniqueId,
      asChild,
    );
 
    return (
      <Component
        ref={forwardedRef}
        className={root({ class: className })}
        {...rest}
      >
        {extendedChildren}
      </Component>
    );
  },
);
ButtonGroupRoot.displayName = BUTTON_GROUP_ROOT_NAME;
 
type ButtonGroupItemProps = ButtonGroupSharedProps &
  React.ButtonHTMLAttributes<HTMLButtonElement> & {
    asChild?: boolean;
  };
 
const ButtonGroupItem = React.forwardRef<
  HTMLButtonElement,
  ButtonGroupItemProps
>(({ children, className, size, asChild, ...rest }, forwardedRef) => {
  const Component = asChild ? Slot : 'button';
  const { item } = buttonGroupVariants({ size });
 
  return (
    <Component
      ref={forwardedRef}
      className={item({ class: className })}
      {...rest}
    >
      {children}
    </Component>
  );
});
ButtonGroupItem.displayName = BUTTON_GROUP_ITEM_NAME;
 
function ButtonGroupIcon<T extends React.ElementType>({
  className,
  size,
  as,
  ...rest
}: PolymorphicComponentProps<T, ButtonGroupSharedProps>) {
  const Component = as || 'div';
  const { icon } = buttonGroupVariants({ size });
 
  return <Component className={icon({ class: className })} {...rest} />;
}
ButtonGroupIcon.displayName = BUTTON_GROUP_ICON_NAME;
 
export {
  ButtonGroupRoot as Root,
  ButtonGroupItem as Item,
  ButtonGroupIcon as Icon,
};

Update the import paths to match your project setup.

Examples

Size

Radix Toggle Group

An example demonstrating how to use Button Group with @radix-ui/react-toggle-group primitives.

API Reference

ButtonGroup.Root

This component is based on the <div> element.

PropTypeDefault
size
"small"|"xsmall"|"xxsmall"
"small"
asChild
boolean

ButtonGroup.Item

PropTypeDefault
asChild
boolean

ButtonGroup.Icon

The ButtonGroup.Icon component is polymorphic, allowing you to change the underlying HTML element using the as prop.

PropTypeDefault
as
React.ElementType
div
© 2024 AlignUI Design System. All rights reserved.