React naming conventions
Event handlers start with 'on' followed by the past tense verb of the event: onClick, onChange, onSubmit. This differentiates handlers (callbacks your component receives) from internal methods (handleClick, handleChange). A Button component should receive onClick, not handleClick. The 'handle' prefix is for your component's private methods.
Boolean props use is/has/should prefixes: isOpen, hasError, shouldRender. Avoid negatives like isNotDisabled; prefer: isEnabled. Double negatives (disabled={!isNotHidden}) confuse. One exception: disabled is idiomatic in HTML, so disabled (not isDisabled) is fine for DOM consistency.
Content props accepting React.ReactNode should have descriptive names: leftIcon not icon, emptyState not fallback, errorMessage not error (error is better for the Error object). This makes code self-documenting: }> is clearer than }> when rightIcon also exists.
Common prop patterns
The value/onChange pattern is standard for controlled components. Your component receives value and calls onChange when the user interacts. Always include defaultValue for the uncontrolled version. For example, Input can work as (controlled) or (uncontrolled).
For components with multiple loading states, use the status pattern: 'idle' | 'loading' | 'success' | 'error'. This beats three booleans (isLoading, isSuccess, isError) because states are mutually exclusive. A component can't be loading and success simultaneously; the enum expresses this clearly.
When you need rendering flexibility, the render props pattern is powerful: renderItem={(item) => ...}. This lets consumers customize presentation without your component exposing its entire internal structure. DataTable with renderItem is more flexible than DataTable with itemClassName.
TypeScript types for props
Use generics for reusable components: List items={users}>, TypeScript knows renderItem receives User.
Optional props need the ? modifier. Don't use explicit undefined (onClick: undefined | (() => void)) when you can write onClick?. They're equivalent but the short syntax is idiomatic. For default values, use destructuring: function Button({ size = 'md' }: Props) instead of size?: Size then const actualSize = size ?? 'md'.
Typed event handlers prevent errors: onChange?: (value: string) => void beats onChange?: Function. Specify the parameter type. If your component passes multiple arguments, make it explicit: onItemClick?: (item: T, index: number) => void. This documents the contract and TypeScript verifies you comply.
Composition vs configuration
Prefer composition when customization is structural. Instead of
Use configuration props for predictable variations. A Button with variant='primary' | 'secondary' | 'ghost' is appropriate because styles are defined. You don't need compound components for this. But if consumers need custom structure, composition wins: Tabs compound better than
The 'as' prop enables polymorphism: