Maksud UI
Components

Button Group

Group multiple buttons together to form a segmented control with shared sizing and orientation.

API
import ButtonGroup, { ButtonGroupItem } from '@/components/ui/button-group';
 
export function ButtonGroupHorizontalDemo() {
  return (
    <ButtonGroup size='default' orientation='horizontal'>
      <ButtonGroupItem>Day</ButtonGroupItem>
      <ButtonGroupItem>Week</ButtonGroupItem>
      <ButtonGroupItem>Month</ButtonGroupItem>
    </ButtonGroup>
  );
}

Installation

CLI

npx shadcn@latest add "https://maksud.dev/r/button-group"

Manual

Install the following dependencies:

npm install @radix-ui/react-slot

Copy and paste the following code into your project.

import { Slot } from '@radix-ui/react-slot';
import * as React from 'react';
 
import { cn } from '@/components/lib/utils';
import { Button } from '@/components/ui/button';
 
/* ---------------------------------- Types --------------------------------- */
export type ButtonGroupProps = React.HTMLAttributes<HTMLDivElement> & {
  /** Size for items in the group; maps to Button sizes. */
  size?: 'sm' | 'default' | 'pill';
  /** Disable all actions on buttons within this group. */
  disabled?: boolean;
  /** Orientation of the group. */
  orientation?: 'horizontal' | 'vertical';
};
 
type ButtonGroupContextProps = {
  size?: ButtonGroupProps['size'];
  disabled?: ButtonGroupProps['disabled'];
  orientation?: ButtonGroupProps['orientation'];
};
 
export type ButtonGroupElement = HTMLDivElement;
 
export type ButtonGroupItemProps = React.ComponentPropsWithoutRef<typeof Button>;
 
/* --------------------------------- Context -------------------------------- */
const ButtonGroupContext = React.createContext<ButtonGroupContextProps | null>(null);
 
function useButtonGroupContext() {
  const context = React.useContext(ButtonGroupContext);
 
  if (!context) {
    throw new Error('ButtonGroupItem must be used within a ButtonGroup');
  }
 
  return context;
}
 
/* ------------------------------- Components ------------------------------- */
const ButtonGroup = React.forwardRef<HTMLDivElement, ButtonGroupProps>((props, ref) => {
  const {
    className,
    children,
    size = 'default',
    disabled = false,
    orientation = 'horizontal',
    ...otherProps
  } = props;
 
  return (
    <ButtonGroupContext.Provider value={{ size, disabled, orientation }}>
      <div
        ref={ref}
        className={cn(
          'inline-flex flex-wrap items-stretch rounded-md',
          orientation === 'vertical' ? 'flex-col divide-y divide-border' : 'divide-x divide-border',
          className
        )}
        {...otherProps}
      >
        {children}
      </div>
    </ButtonGroupContext.Provider>
  );
});
 
const ButtonGroupItem = React.forwardRef<HTMLButtonElement, ButtonGroupItemProps>((props, ref) => {
  const {
    asChild = false,
    children,
    className,
    disabled = false,
    size: _itemSize,
    ...otherProps
  } = props;
 
  const context = useButtonGroupContext();
  const { disabled: ctxDisabled, orientation = 'horizontal', size = 'default' } = context || {};
  const effectiveSize = _itemSize ?? size;
 
  const useAsChild = asChild && React.isValidElement(children);
  const Component = useAsChild ? Slot : Button;
 
  return (
    <Component
      ref={ref}
      className={cn(
        'flex items-center rounded-none focus:ring-0 focus:ring-none focus:ring-offset-0 focus-visible:z-10',
        'last-of-type:[&+span]:hidden',
        orientation === 'horizontal'
          ? effectiveSize === 'pill'
            ? 'first-of-type:rounded-s-full last-of-type:rounded-e-full'
            : 'first-of-type:rounded-s-md last-of-type:rounded-e-md'
          : effectiveSize === 'pill'
          ? 'first-of-type:rounded-t-full last-of-type:rounded-b-full'
          : 'first-of-type:rounded-t-md last-of-type:rounded-b-md',
        className
      )}
      disabled={disabled ? disabled : ctxDisabled}
      size={effectiveSize}
      {...otherProps}
    >
      {children}
    </Component>
  );
});
 
ButtonGroup.displayName = 'ButtonGroup';
ButtonGroupItem.displayName = 'ButtonGroupItem';
 
export { ButtonGroupItem };
export ButtonGroup;

Layout

Import the components and use them in your application.

import ButtonGroup, { ButtonGroupItem } from "@/components/ui/button-group";

export default function Example() {
  return (
    <ButtonGroup size="default" orientation="horizontal">
      <ButtonGroupItem>Day</ButtonGroupItem>
      <ButtonGroupItem>Week</ButtonGroupItem>
      <ButtonGroupItem>Month</ButtonGroupItem>
    </ButtonGroup>
  );
}

Examples

Horizontal

import ButtonGroup, { ButtonGroupItem } from '@/components/ui/button-group';
 
export function ButtonGroupHorizontalDemo() {
  return (
    <ButtonGroup size='default' orientation='horizontal'>
      <ButtonGroupItem>Day</ButtonGroupItem>
      <ButtonGroupItem>Week</ButtonGroupItem>
      <ButtonGroupItem>Month</ButtonGroupItem>
    </ButtonGroup>
  );
}

Vertical

import ButtonGroup, { ButtonGroupItem } from '@/components/ui/button-group';
 
export function ButtonGroupVerticalDemo() {
  return (
    <ButtonGroup size='default' orientation='vertical'>
      <ButtonGroupItem>Left</ButtonGroupItem>
      <ButtonGroupItem>Center</ButtonGroupItem>
      <ButtonGroupItem>Right</ButtonGroupItem>
    </ButtonGroup>
  );
}

Pill

import ButtonGroup, { ButtonGroupItem } from '@/components/ui/button-group';
 
export function ButtonGroupPillDemo() {
  return (
    <div className='flex flex-col gap-4'>
      <ButtonGroup size='pill' orientation='horizontal'>
        <ButtonGroupItem>Day</ButtonGroupItem>
        <ButtonGroupItem>Week</ButtonGroupItem>
        <ButtonGroupItem>Month</ButtonGroupItem>
      </ButtonGroup>
    </div>
  );
}

API Reference

ButtonGroup

PropDescription
sizeSize applied to items by default. Items can override via size prop.
disabledDisable all actions on items within the group.
orientationLayout direction for the group.

ButtonGroupItem

PropDescription
sizeOverrides the size for an individual item.
variantVisual style of the item (same as Button).
asChildRender the item as a child element via Slot.
disabledDisable the item.

Accessibility

  • Grouped buttons are keyboard accessible and preserve focus outlines.
  • Use clear labels for each item; avoid icon-only content without aria-label.
  • The disabled state at the group level disables all items consistently.

Credits

  • Button Group component's source code is taken from Lemonsqueezy Wedges and modified to fit the Maksud UI design system. You can refer to their docs here.