ButtonGroup
Neighbouring buttons fused into one control, sharing a single hairline.
The idea
Related buttons that merely sit close read as several controls; fused, with their inner corners squared and one shared edge between them, they read as one control with parts. The shared edge comes from pulling each button half a pixel over its neighbour rather than deleting a border, so a hovered button's darker edge still shows on both of its sides.
Composes Button.
Install
npm i mihirmodi-ui motionThe tokens, the theme script and the routing provider are a one-time job — see Setup. Or skip the package entirely and take the source at the foot of this page.
Usage
import { Button, ButtonGroup } from "mihirmodi-ui";
<ButtonGroup label="History">
<Button>Undo</Button>
<Button>Redo</Button>
<Button>History</Button>
</ButtonGroup>Props
| Prop | Type | Notes |
|---|---|---|
| labelstring | string | The group's accessible name. |
| orientation"horizontal" | "vertical" | "horizontal" | "vertical"= "horizontal" | Which edges fuse. |
Source
Exactly what the package ships — this is read out of the source at build time, not a copy. Take it and adapt it if you would rather not add a dependency.
import type { ReactNode } from "react";
export type ButtonGroupProps = {
children: ReactNode;
/** The group's accessible name — "Text alignment", "Pagination". */
label?: string;
orientation?: "horizontal" | "vertical";
className?: string;
};
/**
* Related buttons fused into one object: the inner corners square off and the
* neighbours share a single hairline, so the row reads as one control with
* several parts rather than several controls that happen to be close.
*
* Built for `secondary` buttons, whose hairline is what gets shared. The
* shared edge is drawn by pulling each button half a pixel over its
* neighbour, not by removing a border, so a hovered button's darker edge
* still shows on both of its sides.
*/
export function ButtonGroup({ children, label, orientation = "horizontal", className = "" }: ButtonGroupProps) {
const fuse =
orientation === "horizontal"
? "flex-row [&>*:not(:first-child)]:-ml-[0.5px] [&>*:not(:first-child)]:rounded-l-none [&>*:not(:last-child)]:rounded-r-none"
: "flex-col [&>*:not(:first-child)]:-mt-[0.5px] [&>*:not(:first-child)]:rounded-t-none [&>*:not(:last-child)]:rounded-b-none";
return (
<div
role="group"
aria-label={label}
className={`inline-flex ${fuse} [&>*:hover]:z-10 [&>*:focus-visible]:z-20 [&>*]:relative ${className}`}
>
{children}
</div>
);
}