Skip to content

feat: add SplitButton component#4998

Open
draggie wants to merge 1 commit into
callstack:mainfrom
draggie:feat/split-button
Open

feat: add SplitButton component#4998
draggie wants to merge 1 commit into
callstack:mainfrom
draggie:feat/split-button

Conversation

@draggie

@draggie draggie commented Jun 15, 2026

Copy link
Copy Markdown

Motivation

Implements the new Material Design 3 SplitButton component as part of the v6 component modernization work.

The component provides a primary leading action and a separate trailing action for contextual options. It reuses the modernized Button API direction
from #4928/#4943: explicit label/icon props, MD3 mode names (filled, tonal, elevated, outlined), derived ripple/state-layer colors,
extracted component tokens, size metrics, shape tokens, disabled/loading states, and separate accessibility/press handlers for each button segment.

This also adds the component to the public exports, documentation generation config, theme color docs data, and the example app. The example includes a
Menu-based playground, mode showcase, loading/disabled controls, and custom color/label styling.

Related issue

Closes #4986

Related to #4928 and #4943

Test plan

  • yarn lint-no-fix
    • Passes with one existing unrelated warning in src/components/__tests__/TextInput.test.tsx.
  • yarn typescript
  • yarn test --watchman=false
  • yarn docs build

Manual verification in the example app:

  • Open the SplitButton example screen.
  • In the playground, tap the leading Send action and trailing menu action separately.
  • Verify the trailing menu opens below the SplitButton anchor and does not cover the button.
  • Toggle Disabled and verify both press targets are disabled and styled correctly.
  • Toggle Loading and verify the leading icon is replaced with a spinner.
  • Review all modes: filled, tonal, elevated, and outlined.
  • Review custom color and custom label style examples.
Screenshot_1781525547 Simulator Screenshot - iPhone 17 - 2026-06-15 at 14 15 22

Copilot AI 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.

Pull request overview

Adds a new Material Design 3 SplitButton component to React Native Paper as part of the v6 modernization effort, and wires it into the library’s exports, docs, and example app.

Changes:

  • Introduces SplitButton component implementation with size/mode tokens and styling utilities.
  • Adds unit tests for SplitButton rendering/handlers and several utility helpers.
  • Exposes the component publicly and integrates it into docs + the example app (screen + docs config/theme color docs data).

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/index.tsx Exports SplitButton and SplitButtonProps from the public entrypoint.
src/components/SplitButton/utils.ts Adds MD3 color/shape/size helpers, ripple color derivation, and hitSlop expansion logic.
src/components/SplitButton/tokens.ts Introduces SplitButton size/shape/elevation tokens.
src/components/SplitButton/SplitButton.tsx Implements the SplitButton component UI, interaction handling, accessibility, and pressed-elevation/shape behavior.
src/components/SplitButton/index.ts Barrel export for the new component and its props.
src/components/tests/SplitButton.test.tsx Adds tests for SplitButton rendering, interaction separation, accessibility state, and several utils.
example/src/Examples/SplitButtonExample.tsx Adds a SplitButton example screen with Menu-based playground and showcases.
example/src/ExampleList.tsx Registers the SplitButton example in the example app’s main list.
docs/static/llms.txt Adds SplitButton docs link to the LLMs index.
docs/src/data/themeColors.js Adds SplitButton entries to the theme color documentation data.
docs/docusaurus.config.js Registers SplitButton in the docs sidebar/config.

Comment on lines +40 to +43
export type Props = $Omit<
React.ComponentProps<typeof Surface>,
'children' | 'mode'
> & {
Comment on lines +170 to +186
export const getSplitButtonRippleColor = ({
contentColor,
customRippleColor,
}: {
contentColor: ColorValue;
customRippleColor?: ColorValue;
}): ColorValue | undefined => {
if (customRippleColor) {
return customRippleColor;
}

if (typeof contentColor !== 'string') {
return undefined;
}

return color(contentColor).alpha(stateOpacity.pressed).rgb().string();
};

@satya164 satya164 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

the outline split button design doesn't seem to match the screenshots on md guidelines. they look very dark

export const resolveSplitButtonCorner = (
theme: InternalTheme,
key: SplitButtonShapeKey
) => (key === 'full' ? cornerFull : (theme as Theme).shapes.corner[key]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

don't use as type

import * as React from 'react';
import {
AccessibilityState,
Animated,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Lets use reanimated

* export default MyComponent;
* ```
*/
const SplitButton = forwardRef<View, Props>(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

remove forwardRef. the codebase in on react 19
though even then why does this component need ref?

hitSlop,
trailingHitSlop,
theme: themeOverrides,
testID = 'split-button',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

don't add default test ids. such a default will leave multiple test ids and may conflict with the user's own test ids - both of which maybe confusing

Comment on lines +388 to +393
const handleLeadingPressIn = React.useCallback(
(e: GestureResponderEvent) => {
onPressIn?.(e);
setPressedButton('leading');
animateElevation(splitButtonElevation.pressed);
},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

use reanimated shared values instead of state for animation

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.

feat: add Split Button

3 participants