Components
Button Group
Group multiple buttons together to form a segmented control with shared sizing and orientation.
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-slotCopy 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
| Prop | Description |
|---|---|
size | Size applied to items by default. Items can override via size prop. |
disabled | Disable all actions on items within the group. |
orientation | Layout direction for the group. |
ButtonGroupItem
| Prop | Description |
|---|---|
size | Overrides the size for an individual item. |
variant | Visual style of the item (same as Button). |
asChild | Render the item as a child element via Slot. |
disabled | Disable 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
disabledstate 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.