` element that wraps the two buttons.
Use a `SplitButton` when one action is the obvious default, but closely related variations of it should stay within reach. When there is no default action to promote, reach for [the `Menu` component](./menu.mdx) on its own; when there are no alternatives to offer, reach for [the `Button` component](./button.mdx).
## Examples
### Basic usage
Render a `SplitButton` by giving it some children to label the action button, an `onClick` handler for that action, and the `menuItems` that make up its dropdown:
```tsx demo
import { useState } from "react";
import { SplitButton } from "@tenstorrent/vesper/split-button";
import { Typography } from "@tenstorrent/vesper/typography";
export default function BasicSplitButton() {
const [lastAction, setLastAction] = useState
(null);
return (
setLastAction("Save")}
menuItems={[
{ text: "Save as draft", onSelect: () => setLastAction("Draft") },
{
text: "Save and publish",
onSelect: () => setLastAction("Publish"),
},
]}
>
Save
Last action: {lastAction ?? "none"}
);
}
```
Clicking the action button fires `onClick` without ever opening the menu, and clicking the caret button opens the menu without firing `onClick`.
### Menu items
`menuItems` takes the same items as [the `Menu` component](./menu.mdx), so each one can render an `icon`, a `description`, and one of the `"default"`, `"selected"`, `"danger"`, `"locked"`, or `"disabled"` styles:
```tsx demo
import { Copy, Download, Trash } from "@tenstorrent/vesper/icons";
import { SplitButton } from "@tenstorrent/vesper/split-button";
export default function SplitButtonMenuItems() {
return (
{}}
menuItems={[
{
text: "Export as CSV",
icon: ,
description: "Includes every visible column",
onSelect: () => {},
},
{
text: "Copy to clipboard",
icon: ,
onSelect: () => {},
},
{
text: "Delete export",
icon: ,
style: "danger",
onSelect: () => {},
},
]}
>
Export
);
}
```
### Variants
A `SplitButton` can be rendered in one of two variants, which is applied to both of its buttons at once:
| Variant | When to use |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `"contrast"` | Use when the action needs strong visual contrast against its surrounding content, such as the main action of a toolbar. |
| `"subtle"` | Default variant. Use when the action should sit alongside surrounding content without dominating it, such as a row-level action in a table. |
```tsx demo
import { SplitButton } from "@tenstorrent/vesper/split-button";
const MENU_ITEMS = [
{ text: "Save as draft", onSelect: () => {} },
{ text: "Save and publish", onSelect: () => {} },
];
export default function SplitButtonVariants() {
return (
Contrast
Subtle
);
}
```
### Different sizes
A `SplitButton` can be rendered in one of three sizes; `"sm"`, `"md"`, or `"lg"`. The default size is `"md"`.
```tsx demo
import { SplitButton } from "@tenstorrent/vesper/split-button";
const MENU_ITEMS = [
{ text: "Save as draft", onSelect: () => {} },
{ text: "Save and publish", onSelect: () => {} },
];
export default function SplitButtonSizes() {
return (
Small
Medium
Large
);
}
```
### Setting the menu width
The dropdown is `200px` wide by default. Pass `menuWidth` when that is too narrow for its items, such as when they carry descriptions or unusually long labels. Like the menu offsets, this value scales with the base rem size:
```tsx demo
import { SplitButton } from "@tenstorrent/vesper/split-button";
const MENU_ITEMS = [
{ text: "Export as CSV", onSelect: () => {} },
{ text: "Export as JSON", onSelect: () => {} },
];
export default function SplitButtonMenuWidth() {
return (
320px width menu
);
}
```
### Positioning the menu
The dropdown is positioned against the split button as a whole, not just the caret button that opens it, so a menu aligned to `"end"` lines up with the right edge of the action button. The `menuSide`, `menuSideOffset`, `menuAlign`, and `menuAlignOffset` props map onto [the `Menu` component](./menu.mdx)'s `side`, `sideOffset`, `align`, and `alignOffset` props:
```tsx demo
import { SplitButton } from "@tenstorrent/vesper/split-button";
const MENU_ITEMS = [
{ text: "Save as draft", onSelect: () => {} },
{ text: "Save and publish", onSelect: () => {} },
];
export default function SplitButtonMenuPlacement() {
return (
Aligned to start
Aligned to end
Above the button
);
}
```
### Controlling the menu's open state
By default, a `SplitButton` tracks its own menu state. Pass `defaultMenuOpen` to render with the menu already open, or use `menuOpen` and `onMenuOpenChange` together to own the state yourself:
```tsx demo
import { useState } from "react";
import { SplitButton } from "@tenstorrent/vesper/split-button";
import { Typography } from "@tenstorrent/vesper/typography";
export default function ControlledSplitButtonMenu() {
const [menuOpen, setMenuOpen] = useState(false);
return (
{}}
menuItems={[
{ text: "Save as draft", onSelect: () => {} },
{ text: "Save and publish", onSelect: () => {} },
]}
>
Save
Menu open state: {String(menuOpen)}
);
}
```
### Labelling the menu toggle
The caret button has no visible text, so it is labelled `"Toggle menu"` for assistive technology by default. Pass `menuButtonAriaLabel` to override the default ARIA label:
```tsx demo
import { SplitButton } from "@tenstorrent/vesper/split-button";
export default function LabelledSplitButton() {
return (
{}}
menuItems={[
{ text: "Export as CSV", onSelect: () => {} },
{ text: "Export as JSON", onSelect: () => {} },
]}
>
Export
);
}
```
### Disabling a split button
Pass `disabled` to disable both the action button and the caret button at the same time. The action button stops firing `onClick`, and the caret button no longer opens its menu:
```tsx demo
import { SplitButton } from "@tenstorrent/vesper/split-button";
export default function DisabledSplitButton() {
return (
{}}
menuItems={[
{ text: "Save as draft", onSelect: () => {} },
{ text: "Save and publish", onSelect: () => {} },
]}
>
Save
);
}
```
> [!NOTE]
>
> There is no way to disable only one half of a `SplitButton`. When the alternatives are unavailable but the main action is not (or the other way around), render the individual items as `"disabled"` or `"locked"` [menu items](./menu.mdx) instead, so the reason stays visible to the user.