//
Search across all documentation pages
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Define clear, type-safe prop contracts for your React components using TypeScript interfaces and type aliases. Cover required props, optional props, children, and common prop patterns.
// Basic props with required and optional fields
type ButtonProps = {
label: string;
variant?: "primary" | "secondary" | "danger";
disabled?: boolean;
onClick: () => void;
};
export function Button({ label, variant = "primary", disabled = false, onClick }: ButtonProps) {
return (
<button className={`btn btn-${variant}`} disabled={disabled} onClick={onClick}>
{label}
</button>
);
}// Typing children
type CardProps = {
title: string;
children: React.ReactNode;
};
export function Card({ title, children }: CardProps) {
return (
<div className="card">
<h2>{title}</h2>
<div className="card-body">{children}</div>
</div>
);
}// Render prop pattern
type DataListProps<T> = {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
};
export function DataList<T>({ items, renderItem }: DataListProps<T>) {
return <ul>{items.map((item, i) => <li key={i}>{renderItem(item, i)}</li>)}</ul>;
}// Usage
<Button label="Submit" onClick={() => console.log("clicked")} />
<Card title="Welcome"><p>Hello world</p></Card>
<DataList items={["a", "b"]} renderItem={(item) => <span>{item}</span>} />React.ReactNode is the broadest children type. It accepts strings, numbers, JSX elements, arrays, fragments, null, and undefined.React.ReactElement is narrower than ReactNode. Use it when you specifically need a JSX element (not a string or number).variant = "primary") work seamlessly with optional props. TypeScript infers the narrowed type inside the function body."primary" | "secondary" give you autocomplete and catch typos at compile time.Extending HTML element props:
type InputProps = React.ComponentPropsWithoutRef<"input"> & {
label: string;
error?: string;
};
export function Input({ label, error, ...rest }: InputProps) {
return (
<div>
<label>{label}</label>
<input {...rest} />
{error && <span className="error">{error}</span>}
</div>
);
}Props with component injection:
type LayoutProps = {
as?: React.ElementType;
children: React.ReactNode;
className?: string;
};
export function Layout({ as: Component = "div", children, className }: LayoutProps) {
return <Component className={className}>{children}</Component>;
}type for props that use unions or intersections. Use interface when you need extends or want declaration merging.React.PropsWithChildren<T> is a shorthand that adds children?: React.ReactNode to your type.React.ComponentPropsWithRef<"div"> includes the ref prop; ComponentPropsWithoutRef<"div"> excludes it.React.FC adds an implicit children prop in older React types (pre-18). In React 18+ types, React.FC no longer includes children implicitly.JSX.Element will reject strings, numbers, and arrays. Use React.ReactNode unless you have a specific reason....rest onto a DOM element without filtering custom props causes React warnings about unknown DOM attributes.| Approach | Pros | Cons |
|---|---|---|
type alias | Supports unions, intersections, mapped types | No declaration merging |
interface | Extendable, familiar OOP pattern | Cannot express union types directly |
React.FC<Props> | Explicit return type annotation | Verbose, no generic component support |
| Inline prop types | Quick for throwaway components | Hard to reuse or export |
PropsWithChildren | Convenient children shorthand | Hides the children prop from readers |
type when you need unions, intersections, or mapped types.interface when you need extends or declaration merging.type is sufficient and more flexible.ReactNode accepts strings, numbers, JSX elements, arrays, fragments, null, and undefined.ReactElement only accepts JSX elements (not strings or numbers).ReactNode unless you specifically need to restrict children to JSX elements.type InputProps = React.ComponentPropsWithoutRef<"input"> & {
label: string;
error?: string;
};
function Input({ label, error, ...rest }: InputProps) {
return (
<div>
<label>{label}</label>
<input {...rest} />
{error && <span>{error}</span>}
</div>
);
}ComponentPropsWithoutRef<"element"> to get all native props.&.label or error get passed to the HTML element.type ButtonProps = {
variant?: "primary" | "secondary";
};
function Button({ variant = "primary" }: ButtonProps) {
// TypeScript narrows variant to "primary" | "secondary" (not undefined)
}? in the type.children?: React.ReactNode to your type.children prop from readers of the type definition.children: React.ReactNode in the type for clarity.React.FC no longer includes children implicitly.children prop.type DataListProps<T> = {
items: T[];
renderItem: (item: T, index: number) => React.ReactNode;
};React.ReactNode.type LayoutProps = {
as?: React.ElementType;
children: React.ReactNode;
};
function Layout({ as: Component = "div", children }: LayoutProps) {
return <Component>{children}</Component>;
}React.ElementType accepts string tags ("div", "span") and component types.Reviewed by Chris St. John·Last updated Jul 7, 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥