oks-ui

How to use

ButtonGroup

ButtonGroup merges a row of adjacent Buttons into a single visually connected control — shared borders where they touch, radius only on the outer corners, and one set of default props (variant, color, size, radius, fullWidth, isDisabled) that every child inherits unless it sets its own. It's a styling/defaults wrapper, not a selection manager — it doesn't track which child is "active" for you.

import { Button, ButtonGroup } from "oks-ui";

Sharing defaults across a row of actions

Set variant/color/size/radius once on ButtonGroup instead of repeating them on every Button — any child Button that doesn't set its own value for a given prop inherits the group's, and one that does set its own overrides just that prop while still inheriting the rest.

<ButtonGroup variant="bordered" color="default">
  <Button>Day</Button>
  <Button>Week</Button>
  <Button color="primary">Month</Button>
</ButtonGroup>

Only direct children get group defaults

The inheritance walks React.Children directly — a Button wrapped in another element (a styled <div>, a conditional fragment) won't pick up the group's defaults, since ButtonGroup has no reliable way to know what counts as a "wrapper" versus an unrelated child. Keep Buttons as direct children of ButtonGroup, even if you need conditional rendering around them.

// Works -- both are direct children
<ButtonGroup variant="soft">
  <Button>A</Button>
  {showB && <Button>B</Button>}
</ButtonGroup>

// Doesn't inherit -- Button is nested inside a wrapper, not a direct child
<ButtonGroup variant="soft">
  <div className="wrapper"><Button>A</Button></div>
</ButtonGroup>

Building a segmented toggle

ButtonGroup itself has no concept of a selected item — for a segmented control where exactly one option is active, track the selection in your own state and drive each Button's color/variant off it.

function ViewToggle() {
  const [view, setView] = useState<"list" | "grid">("list");
  return (
    <ButtonGroup variant="bordered" size="sm">
      <Button
        color={view === "list" ? "primary" : "default"}
        onPress={() => setView("list")}
      >
        List
      </Button>
      <Button
        color={view === "grid" ? "primary" : "default"}
        onPress={() => setView("grid")}
      >
        Grid
      </Button>
    </ButtonGroup>
  );
}

colorDepth has no real default

Unlike Button, ButtonGroup's colorDepth prop has no built-in default value — it stays unset (letting each child Button fall back to its own default) until you explicitly provide one, at which point it applies to every child that doesn't set colorDepth itself.

Accessibility

  • Renders with role="group" on the wrapper so assistive technology announces the Buttons as a related set, not a series of unrelated controls.
  • Every child stays a real, independently focusable <button> or <a> — ButtonGroup only affects styling and shared default props, never the underlying semantics or tab order of its children.
  • isDisabled on ButtonGroup disables every child that doesn't set isDisabled itself; a child that explicitly sets isDisabled={false} stays enabled even inside a disabled group.

Props reference

All available props for ButtonGroup. See the full component page for an interactive playground.

Props

children

ReactNode

Default:

Buttons rendered inside the group.

Best practices

  • Use ButtonGroup for actions that are genuinely related (view toggles, pagination, a Cancel/Confirm pair) — for unrelated actions scattered across a toolbar, plain spaced-out Buttons read more clearly.
  • Keep every Button a direct child; if you need conditional rendering, gate the Button itself rather than wrapping it in another element.
  • For a segmented control with a real "selected" state, drive each child's color from your own state — ButtonGroup only shares defaults, it doesn't manage selection.
  • Set fullWidth on the group rather than on each Button individually when you want the whole row to stretch to the container width.
View full API reference for ButtonGroup