Split button

One button in two halves: a default action, and a caret that opens related actions. Use it when one action is the main one and a few alternatives should stay close.

Usage

The action half runs onclick. The trailing half opens the menu and never triggers the action. Give it a menuLabel, because it shows only a caret. While the menu is open, the caret turns upside down and its half becomes a circle.

Action clicked
<script lang="ts">
	import { MenuItem, Snackbar, SplitButton } from 'noph-ui'

	let snackbar: ReturnType<typeof Snackbar> | undefined = $state()
	const say = () => snackbar?.show()
</script>

<SplitButton label="Save" onclick={say}>
	{#snippet menu()}
		<MenuItem onclick={say}>Save as draft</MenuItem>
		<MenuItem onclick={say}>Save a copy</MenuItem>
	{/snippet}
</SplitButton>
<Snackbar bind:this={snackbar} label="Action clicked" />

Variants

Both halves use the same variant. Four of the Button variants are available.

Action clicked
<script lang="ts">
	import { MenuItem, Snackbar, SplitButton } from 'noph-ui'

	let snackbar: ReturnType<typeof Snackbar> | undefined = $state()
	const say = () => snackbar?.show()
</script>

{#each ['filled', 'tonal', 'outlined', 'elevated'] as const as variant (variant)}
	<SplitButton {variant} label={variant} onclick={say}>
		{#snippet menu()}
			<MenuItem onclick={say}>First</MenuItem>
			<MenuItem onclick={say}>Second</MenuItem>
		{/snippet}
	</SplitButton>
{/each}
<Snackbar bind:this={snackbar} label="Action clicked" />

With an icon

Action clicked
<script lang="ts">
	import { MenuItem, Snackbar, SplitButton } from 'noph-ui'
	import { Icon } from 'noph-ui/icons'

	let snackbar: ReturnType<typeof Snackbar> | undefined = $state()
	const say = () => snackbar?.show()
</script>

<SplitButton label="Send" onclick={say}>
	{#snippet icon()}<Icon>send</Icon>{/snippet}
	{#snippet menu()}
		<MenuItem onclick={say}>Schedule send</MenuItem>
	{/snippet}
</SplitButton>
<Snackbar bind:this={snackbar} label="Action clicked" />

Theming

A split button is two buttons in a group. Theme it with the button tokens of its variant. --np-filled-button-container-color and the related tokens apply to both halves.

The shape comes from --np-shape-corner-full. The two halves keep small inner corners. The caret rotation and the shape change on press use the expressive motion tokens. With reduced motion, they do not animate.

Accessibility

Both halves are real buttons in a role="group". The leading half is named by its label, also with iconOnly, where the label becomes its tooltip. The caret is named by menuLabel. Give menuLabel a name that fits the action, for example More send options instead of the default.

The caret has aria-haspopup="menu" and aria-expanded, and opens the menu. The menu has arrow key navigation and returns focus to the caret when it closes. Tab reaches both halves, so keyboard users can run the default action without opening the menu.

API

Renders a connected ButtonGroup holding the two halves and the menu. bind:element gives you the group.

AttributeTypeDefaultDescription
labelstring''Text of the action half.
iconOnlybooleanfalseShows only icon on the leading button. label then names it and becomes its tooltip.
iconSnippet | undefinedundefinedLeading icon on the action half.
menuSnippet | undefinedundefinedMenu items, rendered inside a Menu.
onclickMouseEventHandler | undefinedundefinedThe action half's handler. The trailing half opens the menu instead.
variant'filled' | 'tonal' | 'outlined' | 'elevated''filled'Applied to both halves.
size'xs' | 's' | 'm' | 'l' | 'xl''s'Applied to both halves.
openbooleanfalseBindable. Whether the menu is open.
menuLabelstring'More options'Accessible name for the trailing half.