action · conformant
SplitButton
SplitButtonSplitButtonPropsSplitButtonSizeSplitButtonVariant
import { useState } from 'react'
import {
Icon,
Surface,
SplitButton,
Text,
type SplitButtonSize,
} from '@language-lit/material3-expressive'
const sizes: readonly SplitButtonSize[] = [
'extra-small',
'small',
'medium',
'large',
'extra-large',
]
export function SplitButtonExample() {
const [open, setOpen] = useState(false)
return (
<Surface
as="section"
aria-labelledby="split-button-example-title"
color="surface-container-low"
shape="extra-large"
className="split-button-example"
>
<Text as="h2" id="split-button-example-title" variant="titleLarge" emphasis="emphasized">
Split button
</Text>
<Text as="p" variant="bodyMedium">
A primary action paired with an icon-only toggle, visually joined
as one pill. The trailing button morphs to a full circle while
selected.
</Text>
<div className="split-button-example__row">
<SplitButton
variant="filled"
trailingIcon={<Icon source={open ? 'expand_less' : 'expand_more'} />}
trailingLabel="More save options"
selected={open}
onSelectedChange={setOpen}
>
Save
</SplitButton>
<SplitButton
variant="tonal"
size="large"
leadingIcon={<Icon source="send" />}
trailingIcon={<Icon source="expand_more" />}
trailingLabel="More send options"
>
Send
</SplitButton>
<SplitButton
variant="outlined"
trailingIcon={<Icon source="expand_more" />}
trailingLabel="More options"
disabled
>
Unavailable
</SplitButton>
</div>
<div className="split-button-example__row" aria-label="Split button sizes">
{sizes.map((size) => (
<SplitButton
key={size}
variant="tonal"
size={size}
trailingIcon={<Icon source="expand_more" />}
trailingLabel={`More ${size} options`}
>
{size}
</SplitButton>
))}
</div>
<div className="split-button-example__row" aria-label="Split button variants">
<SplitButton
variant="elevated"
leadingIcon={<Icon source="download" />}
trailingIcon={<Icon source="expand_more" />}
trailingLabel="More download options"
>
Download
</SplitButton>
<SplitButton
variant="filled"
trailingIcon={<Icon source="expand_more" />}
trailingLabel="More options"
disabled
>
Unavailable
</SplitButton>
<SplitButton
variant="tonal"
trailingIcon={<Icon source="expand_more" />}
trailingLabel="More options"
disabled
>
Unavailable
</SplitButton>
<SplitButton
variant="elevated"
trailingIcon={<Icon source="expand_more" />}
trailingLabel="More options"
disabled
>
Unavailable
</SplitButton>
</div>
<div className="split-button-example__row" aria-label="Statically selected split button">
<SplitButton
variant="outlined"
trailingIcon={<Icon source="expand_less" />}
trailingLabel="More options"
defaultSelected
>
Statically selected
</SplitButton>
</div>
</Surface>
)
}SplitButton pairs a primary action button with an icon-only toggle
button (typically a chevron opening an attached menu), visually joined as
one pill with a small shared inner-corner radius. The trailing button
morphs to a full circle while selected.
import { SplitButton } from '@language-lit/material3-expressive'
import '@language-lit/material3-expressive/styles.css'
<SplitButton
onClick={save}
trailingIcon={<ChevronDownIcon />}
trailingLabel="More save options"
selected={menuOpen}
onSelectedChange={setMenuOpen}
>
Save
</SplitButton>Contract
childrenis the leading button's label;leadingIconis optional.onClickfires only for the leading button.trailingIconandtrailingLabel(the icon-only button's accessible name) are required.selected/defaultSelected/onSelectedChangefollow the same controlled/uncontrolled toggle contract asIconButton— wire them to an actual menu (e.g. this project's ownMenu) yourself.variant(filled|tonal|elevated|outlined, defaultfilled) andsize(extra-small|small|medium|large|extra-large, defaultsmall) apply to both buttons together.disableddisables both buttons together.
Tokens and boundaries
Colors, geometry, and per-size dimensions come entirely from
--m3e-comp-split-button-* CSS custom properties, scoped by
Material3Provider. SplitButton injects no runtime styles and renders
its own two <button> elements directly (it does not nest the public
Button/IconButton components).