Skip to content

feat(button-group): add ButtonGroup component#640

Open
stritt wants to merge 4 commits into
mainfrom
button-group
Open

feat(button-group): add ButtonGroup component#640
stritt wants to merge 4 commits into
mainfrom
button-group

Conversation

@stritt

@stritt stritt commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a ButtonGroup component that visually joins related buttons into a single segmented control — action rows, icon toolbars, and split buttons (a primary action next to a dropdown trigger).

Motivated by a hand-rolled split-button snippet that flattened corners manually and used raw Tailwind colors (border-blue-700 dark:border-blue-600), which violate kumo's styling rules. ButtonGroup makes this a first-class, reusable component built entirely on semantic tokens.

Design

  • Layout-only wrapper: children keep their own variant, size, and shape. The group flattens inner corners and overlaps borders (-1px) so adjacent buttons share a single seam — the seam color comes from each button's own ring, so it works for every variant without hardcoded colors.
  • Orientation: horizontal (default) and vertical.
  • A11y: role="group" by default, overridable (e.g. role="toolbar" with aria-label). Interacted buttons lift via z-index so focus rings aren't clipped by the overlap.
  • Tokens only: no raw colors, no dark: — passes the custom color lint.

What's included

  • Component + required variant exports (KUMO_BUTTON_GROUP_VARIANTS, _DEFAULT_VARIANTS, _STYLING), forwardRef, displayName
  • 9 unit tests (render, role default/override, orientation, className merge, ref forwarding)
  • Docs page (/components/button-group) with 6 demos (basic, split button, icon toolbar, icons+labels, vertical, sizes)
  • HomeGrid showcase entry + sidebar nav entry
  • Registry category mapping (Action) + changeset (minor)

Testing

  • pnpm --filter @cloudflare/kumo test button-group → 9/9 pass

  • pnpm --filter @cloudflare/kumo typecheck → clean

  • pnpm --filter @cloudflare/kumo lint → no button-group issues

  • pnpm --filter @cloudflare/kumo build + docs build (72 pages, button-group page renders)

  • Reviews

  • bonk has reviewed the change

  • automated review not possible because: new component with visual/interaction behavior best verified by a human in the preview link

  • Tests

  • Tests included/updated

Joins related buttons into a single segmented control. Layout-only: children
keep their own variant, size, and shape, while ButtonGroup flattens inner
corners and overlaps borders so adjacent buttons share one seam.

- Horizontal (default) and vertical orientation
- Works for action rows, icon toolbars, and split buttons (primary action +
  dropdown trigger)
- role="group" by default, overridable (e.g. role="toolbar")
- Uses only kumo semantic tokens (no raw colors, no dark: variant)

Includes unit tests, docs page + demos, HomeGrid showcase entry, sidebar nav
entry, registry category mapping, and a changeset.
@pkg-pr-new

pkg-pr-new Bot commented Jul 17, 2026

Copy link
Copy Markdown
npm i https://pkg.pr.new/@cloudflare/kumo@640

commit: bf9d392

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Docs Preview

View docs preview

Commit: 03f7f29

@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor
Visual Regression Report — 25 changed, 17 unchanged

25 screenshot(s) with visual changes:

Button / Variant: Secondary

411 px (0.46%) changed

Before After Diff
Before After Diff

Button / Variant: Outline

269 px (0.3%) changed

Before After Diff
Before After Diff

Button / With Icon

819 px (0.92%) changed

Before After Diff
Before After Diff

Button / Loading State

34 px (0.04%) changed

Before After Diff
Before After Diff

Button / Title

405 px (0.46%) changed

Before After Diff
Before After Diff

Dialog / Dialog Basic

401 px (0.45%) changed

Before After Diff
Before After Diff

Dialog / Dialog Sizes

2,891 px (3.26%) changed

Before After Diff
Before After Diff

Dialog / Dialog Alert

1,168 px (1.32%) changed

Before After Diff
Before After Diff

Dialog / Dialog Confirmation

855 px (0.97%) changed

Before After Diff
Before After Diff

Dialog / Custom Max Width

1,192 px (1.35%) changed

Before After Diff
Before After Diff

Dialog / Dialog With Select

480 px (0.54%) changed

Before After Diff
Before After Diff

Dialog / Dialog With Combobox

543 px (0.61%) changed

Before After Diff
Before After Diff

Select / Select Sizes

3,108 px (1.91%) changed

Before After Diff
Before After Diff

Select / Select Without Label

664 px (0.75%) changed

Before After Diff
Before After Diff

Select / Select With Description

1,688 px (1.62%) changed

Before After Diff
Before After Diff

Select / Select Placeholder

977 px (1.1%) changed

Before After Diff
Before After Diff

Select / Select With Tooltip

196 px (0.22%) changed

Before After Diff
Before After Diff

Select / Select Custom Rendering

1,425 px (1.61%) changed

Before After Diff
Before After Diff

Select / Select Loading

2,475 px (1.4%) changed

Before After Diff
Before After Diff

Select / Select Disabled Options

1,700 px (1.92%) changed

Before After Diff
Before After Diff

Select / Select Disabled Items

778 px (0.88%) changed

Before After Diff
Before After Diff

Select / Select Grouped

105 px (0.12%) changed

Before After Diff
Before After Diff

Select / Select Grouped With Disabled

1,609 px (1.82%) changed

Before After Diff
Before After Diff

Select / Select Long List

1,918 px (1.84%) changed

Before After Diff
Before After Diff

Select (Open)

0 px (0%) changed

Before After Diff
Before After Diff
17 screenshot(s) unchanged
  • Button / Basic
  • Button / Variant: Primary
  • Button / Variant: Ghost
  • Button / Variant: Destructive
  • Button / Variant: Secondary Destructive
  • Button / Sizes
  • Button / Icon Only
  • Button / Disabled State
  • Button / Link as Button
  • Button / Disabled Link
  • Dialog / Dialog With Actions
  • Dialog / Dialog With Dropdown
  • Dialog (Open)
  • Select / Select Basic
  • Select / Select With Error
  • Select / Select Multiple
  • Select / Select Complex

Generated by Kumo Visual Regression

stritt added 3 commits July 17, 2026 17:07
…t corners

- Remove vertical orientation; ButtonGroup is horizontal-only
- Fix seam jumping on hover: only lift z-index on keyboard focus, never on
  hover, so the shared 1px seam stays put (later sibling paints on top)
- Make corner rounding robust to runtime-injected elements (dropdown popup /
  backdrop / focus guards) by scoping the join selectors to button/a via
  :*-of-type, so outer corners stay rounded when a menu opens
…ne control

Each kumo Button ships its own shadow-xs. Inside a group those drop shadows
overlap at the seams, making the middle button look elevated/boxed and leaving
a clipped-corner artifact on its trailing edge. Drop child shadows within the
group; the shared rings provide all the definition needed.
… dark box

Root cause of the boxed/dark middle segment: kumo's Button draws a near-black
ring on *any* focus (focus:ring-kumo-focus/50), including mouse clicks. Inside a
segmented group that renders as a jarring dark box on the clicked segment.

- Reset the ring to the resting line color for mouse focus
  (:focus:not(:focus-visible)); the fill/selected state is the feedback
- Keep the keyboard focus-visible ring (lifted) for a11y

Verified in a real browser across resting / hover / mouse-click / keyboard-focus
for the segmented control, the primary split button (incl. open dropdown), and
the icon toolbar.

@mattrothenberg mattrothenberg left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love this. We should probably use Base UI's Toolbar primitive here, no? https://base-ui.com/react/components/toolbar

@ArshVermaGit ArshVermaGit left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me. The component is implemented cleanly, with clear docs, demos, and test coverage. It fits well with the existing component library. LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants