Patterns
How do I accept either `href` or `onClick`, but reject objects that provide both?
Union the two shapes and add the other side’s unique keys as optional `never` properties.
The recipe
type Without<T, U> = { [K in Exclude<keyof T, keyof U>]?: never } type XOR<T, U> = | (T & Without<U, T>) | (U & Without<T, U>) type LinkProps = XOR< { href: string }, { onClick: () => void } >
The build compiles this and checks each result below.
How it works
- 01
[K in Exclude<keyof T, keyof U>]?: never
Unique keys from the opposite branch may be absent, but can never hold a value.
- 02
| (U & Without<T, U>)
Each union member combines its required shape with a ban on the other one.
What you get
{ href: string } extends LinkProps ? true : false→true{ onClick: () => void } extends LinkProps ? true : false→true{ href: string; onClick: () => void } extends LinkProps ? true : false→false
Where it goes wrong
This is clearest for two small object shapes. Large overlapping types produce noisy diagnostics, and `exactOptionalPropertyTypes` changes whether an explicit `undefined` can satisfy optional `never`.
Takeaway
Use optional `never` to say that a property belongs to the other branch and must not appear here.
