Skip to contents

mergeProps

A utility to merge multiple sets of Solid props.

mergeProps helps you combine multiple prop objects (for example, internal props + user props) into a single set of props you can spread onto an element. It behaves like Object.assign (rightmost wins) with a few special cases, so common Solid patterns work as expected.

mergeProps returns a props snapshot except for lazy children. Call it in JSX, a memo, or a props accessor to keep reactive values up to date. Solid bound event handlers ([handler, data]) are supported.

How merging works

  • For most keys (everything except class, style, ref, children, and event handlers), the value from the rightmost object wins:

    returns { id: 'b', dir: 'ltr' }
    mergeProps({ id: 'a', dir: 'ltr' }, { id: 'b' });
    
  • ref callbacks are composed so each receives the element:

    mergeProps({ ref: refA }, { ref: refB });
    
  • class values are merged into a Solid class array, rightmost first:

    mergeProps({ class: 'a' }, { class: 'b' });
    
  • style objects are merged, with keys from the rightmost style overwriting earlier ones.

  • Event handlers are merged and executed right-to-left (rightmost first):

    mergeProps({ onClick: a }, { onClick: b });
    
    • For native DOM events, Base UI adds event.preventBaseUIHandler(). Calling it prevents Base UI’s internal logic from running. This does not call preventDefault() or stopPropagation().
    • For non-DOM events (custom events with primitive/object values), this mechanism isn’t available and all handlers always execute.

Preventing Base UI’s default behavior

When using the function form of the render prop, props are not merged automatically. You can use mergeProps to combine Base UI’s props with your own, and call preventBaseUIHandler() to stop Base UI’s internal logic from running:

Favorite (locked)
import { createSignal } from 'solid-js';
import type { JSX } from '@solidjs/web';
import { mergeProps } from 'base-ui-solid/merge-props';
import { Toggle } from 'base-ui-solid/toggle';
import styles from './index.module.css';

export default function ExamplePreventBaseUIHandler() {
  const [locked, setLocked] = createSignal(true);
  const [pressed, setPressed] = createSignal(true);
  const getToggleProps = (props: JSX.IntrinsicElements['button']) =>
    mergeProps(props, {
      onClick(event) {
        if (locked()) {
          event.preventBaseUIHandler();
        }
      },
    });

  return (
    <div class={styles.Container}>
      <div class={styles.ToggleRow}>
        <Toggle
          aria-label="Favorite"
          pressed={pressed()}
          onPressedChange={setPressed}
          class={styles.Toggle}
          render={(props, state) => (
            <button type="button" {...(getToggleProps(props) as JSX.IntrinsicElements['button'])}>
              {state.pressed ? <HeartFilledIcon /> : <HeartOutlineIcon />}
            </button>
          )}
        />
        <span class={styles.Label}>Favorite {locked() ? '(locked)' : '(unlocked)'}</span>
      </div>
      <button type="button" class={styles.Button} onClick={() => setLocked((l) => !l)}>
        {locked() ? 'Unlock' : 'Lock'}
      </button>
    </div>
  );
}

function HeartFilledIcon(props: JSX.IntrinsicElements['svg']) {
  return (
    <svg
      width="16"
      height="16"
      viewBox="0 0 16 16"
      fill="currentColor"
      {...props}
      style={props.style}
    >
      <path d="M7.99961 13.8667C7.88761 13.8667 7.77561 13.8315 7.68121 13.7611C7.43321 13.5766 1.59961 9.1963 1.59961 5.8667C1.59961 3.80856 3.27481 2.13336 5.33294 2.13336C6.59054 2.13336 7.49934 2.81176 7.99961 3.3131C8.49988 2.81176 9.40868 2.13336 10.6663 2.13336C12.7244 2.13336 14.3996 3.80803 14.3996 5.8667C14.3996 9.1963 8.56601 13.5766 8.31801 13.7616C8.22361 13.8315 8.11161 13.8667 7.99961 13.8667Z" />
    </svg>
  );
}

function HeartOutlineIcon(props: JSX.IntrinsicElements['svg']) {
  return (
    <svg
      width="16"
      height="16"
      viewBox="0 0 16 16"
      fill="currentColor"
      {...props}
      style={props.style}
    >
      <path
        fill-rule="evenodd"
        clip-rule="evenodd"
        d="m7.99961 4.8232-.75505-.75666c-.40333-.40419-1.0559-.86651-1.91162-.86651-1.46903 0-2.66666 1.19764-2.66666 2.66667 0 .5412.24648 1.2356.75339 2.04713.49581.79376 1.17682 1.59861 1.89311 2.33647 1.06989 1.1022 2.1604 1.9962 2.68705 2.4102.52751-.4149 1.61735-1.3085 2.68657-2.4101.7163-.73792 1.3973-1.54278 1.8932-2.33656.5069-.81154.7533-1.50594.7533-2.04714 0-1.46947-1.1975-2.66667-2.6666-2.66667-.85574 0-1.50831.46232-1.91164.86651zm-.01387-1.52394c-.5031-.49988-1.40673-1.1659-2.6528-1.1659-2.05813 0-3.73333 1.6752-3.73333 3.73334 0 3.3296 5.8336 7.7099 6.0816 7.8944a.532.532 0 0 0 .3184.1056c.112 0 .224-.0352.3184-.1051.248-.185 6.08159-4.5653 6.08159-7.8949 0-2.05867-1.6752-3.73334-3.7333-3.73334-1.24617 0-2.14985.66611-2.65293 1.166q-.0069.00686-.0137.01367c.00002-.00003-.00002.00002 0 0-.00459-.0046-.00927-.00914-.01393-.01377"
      />
    </svg>
  );
}

Passing a function instead of an object

Each argument can be a props object or a function that receives the merged props up to that point (left to right) and returns a props object. This is useful when you need to compute the next props from whatever has already been merged.

Note that the function’s return value completely replaces the accumulated props up to that point. If you want to chain event handlers from the previous props, you must call them manually:

Manually chaining handlers in a function
const merged = mergeProps(
  {
    onClick(event) {
      // Handler from previous props
    },
  },
  (props) => ({
    onClick(event) {
      // Manually call the previous handler
      props.onClick?.(event);
      // Your logic here
    },
  }),
);

API reference

mergeProps

This function accepts up to 5 arguments, each being either a props object or a function that returns a props object. If you need to merge more than 5 sets of props, use mergePropsN instead.

Merges multiple sets of Solid props. It follows the Object.assign pattern where the rightmost object’s fields overwrite the conflicting ones from others. This doesn’t apply to event handlers, class and style props. Event handlers are merged and called in right-to-left order (rightmost handler executes first, leftmost last). For native DOM events, the rightmost handler can prevent prior (left-positioned) handlers from executing by calling event.preventBaseUIHandler(). For non-DOM events (custom events with primitive/object values), all handlers always execute without prevention capability. The class prop is merged as Solid class values in an array. The style prop is merged with rightmost styles overwriting the prior ones. Props can either be provided as objects or as functions that take the previous props as an argument. The function will receive the merged props up to that point (going from left to right): so in the case of (obj1, obj2, fn, obj3), fn will receive the merged props of obj1 and obj2. The function is responsible for chaining event handlers if needed (that is, we don’t run the merge logic). Event handlers returned by the functions are not automatically prevented when preventBaseUIHandler is called. They must check event.baseUIHandlerPrevented themselves and bail out if it’s true. Refs are composed into an array. Children are forwarded lazily. Call mergeProps in a reactive scope for updated values.

Prop
Type
Default
aInputProps<T>—
Props object to merge.InputProps<T>
bInputProps<T>—
Props object to merge. The function will overwrite conflicting props from a.InputProps<T>
c?InputProps<T>—
Props object to merge. The function will overwrite conflicting props from previous parameters.InputProps<T>
d?InputProps<T>—
Props object to merge. The function will overwrite conflicting props from previous parameters.InputProps<T>
e?InputProps<T>—
Props object to merge. The function will overwrite conflicting props from previous parameters.InputProps<T>
Return value
type ReturnValue<T extends object> = WithBaseUIEvent<T>;

mergePropsN

This function accepts an array of props objects or functions that return props objects. It is slightly less efficient than mergeProps, so only use it when you need to merge more than 5 sets of props.

Merges an arbitrary number of Solid props using the same logic as [mergeProps](#mergeprops). This function accepts an array of props instead of individual arguments. This has slightly lower performance than [mergeProps](#mergeprops) due to accepting an array instead of a fixed number of arguments. Prefer [mergeProps](#mergeprops) when merging 5 or fewer prop sets for better performance.

Prop
Type
Default
propsInputProps<T>[]—
Array of props to merge.InputProps<T>[]
Return value
type ReturnValue<T extends object> = WithBaseUIEvent<T>;