From b61041bf0287ee1092ef51fa144ece52a47bad7e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Jul 2026 20:09:02 +0000 Subject: [PATCH 1/4] Document prop merging conventions Co-authored-by: joshblack <3901764+joshblack@users.noreply.github.com> --- contributor-docs/style.md | 53 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/contributor-docs/style.md b/contributor-docs/style.md index c9dabb0db1a..8d71f6905d4 100644 --- a/contributor-docs/style.md +++ b/contributor-docs/style.md @@ -68,6 +68,7 @@ row before the line. - [Utilities](#utilities) - [Props](#props) - [Prefer applying component rest parameters to the root element rendered by a component](#prefer-applying-component-rest-parameters-to-the-root-element-rendered-by-a-component) + - [Merge shared props intentionally](#merge-shared-props-intentionally) - [Prefer authoring callback prop types with arguments that can be extended](#prefer-authoring-callback-prop-types-with-arguments-that-can-be-extended) - [Hooks](#hooks) - [Prefer authoring hooks that accept a `ref` instead of returning a `ref` to apply](#prefer-authoring-hooks-that-accept-a-ref-instead-of-returning-a-ref-to-apply) @@ -299,6 +300,58 @@ function Example({children, ...rest}: Props) { +#### Merge shared props intentionally + +When a component and a consumer can both provide a prop, define how those values +are merged instead of allowing the order of prop spreads to decide accidentally. +Use the following conventions: + +- Event handlers run the component's handler first, then the consumer's handler + if the event has not been prevented. +- Class names are merged with `clsx`. +- Style objects apply the component's styles first and the consumer's styles + second so that the consumer can override them. +- Other attributes use the consumer's value. If an attribute must be controlled + by the component, do not expose it as a prop, or use types that only offer it + in the scenarios where the consumer can control it. + +For example: + +```tsx +type Props = React.ComponentPropsWithoutRef<'button'> + +function Example({className, onClick, style, ...rest}: Props) { + const handleClick = (event: React.MouseEvent) => { + performInternalAction(event) + + if (!event.defaultPrevented) { + onClick?.(event) + } + } + + return ( +