Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

How to type props in React with TypeScript

Typing props is a fundamental task when building robust React applications with TypeScript to ensure data integrity across your component tree.
As the creator of CoreUI and with over 25 years of experience in software development, I’ve implemented type-safe prop systems in hundreds of production-ready components.
The most efficient and modern approach is using TypeScript interface or type aliases to explicitly define the shape of your component’s input data.
This practice not only prevents runtime errors but also provides excellent developer experience through IDE autocompletion and self-documenting code.

Define a TypeScript interface to describe your props and apply it to the functional component definition.

import React from 'react'

interface MyComponentProps {
  title: string
  isActive: boolean
}

const MyComponent = ({ title, isActive }: MyComponentProps) => {
  return (
    <div>
      <h1>{title}</h1>
      <p>Status: {isActive ? 'Active' : 'Inactive'}</p>
    </div>
  )
}

export default MyComponent

In this example, we define an interface named MyComponentProps that specifies a title as a string and isActive as a boolean. When we define MyComponent, we use this interface to type the destructured props. TypeScript will now enforce that any parent component passing data to MyComponent provides exactly these types, or it will throw a compile-time error.

1. Typing Primitive and Object Props

Defining types for simple values like strings, numbers, and booleans is straightforward, but components often require more complex data structures. When passing objects, you should define a nested interface or a specific type to maintain deep type safety throughout your application.

import React from 'react'

interface UserProfile {
  id: number
  username: string
  email: string
}

interface UserCardProps {
  user: UserProfile
  isAdmin: boolean
  loginCount: number
}

const UserCard = ({ user, isAdmin, loginCount }: UserCardProps) => {
  return (
    <div className='user-card'>
      <h2>{user.username}</h2>
      <p>Email: {user.email}</p>
      {isAdmin && <span>Administrator</span>}
      <p>Logins: {loginCount}</p>
    </div>
  )
}

export default UserCard

This structure ensures that the user object adheres to the UserProfile schema. If you need to manipulate this data before passing it, for example, if you need to convert an array to a string for display, TypeScript will ensure the resulting type matches your prop definition.

2. Handling Optional Props

Not every prop is required for a component to function correctly. TypeScript allows you to mark props as optional using the ? syntax. This is particularly useful for configuration flags or optional UI elements that may not always be present.

import React from 'react'

interface AlertProps {
  message: string
  type?: 'success' | 'danger' | 'warning' | 'info'
  dismissible?: boolean
}

const CustomAlert = ({ message, type = 'info', dismissible = false }: AlertProps) => {
  return (
    <div className={`alert alert-${type}`}>
      {message}
      {dismissible && <button>Close</button>}
    </div>
  )
}

export default CustomAlert

By adding ? after the prop name in the interface, we tell TypeScript that these values might be undefined. It is a best practice to provide default values during destructuring to ensure the component behaves predictably even when optional props are omitted.

3. Typing Event Handlers and Callbacks

Passing functions as props is essential for component communication. TypeScript provides built-in types for standard DOM events, which helps in typing event handlers correctly and gaining access to event properties like target or preventDefault.

import React from 'react'

interface SearchInputProps {
  label: string
  onSearch: (query: string) => void
  onChange: (event: React.ChangeEvent<HTMLInputElement>) => void
}

const SearchInput = ({ label, onSearch, onChange }: SearchInputProps) => {
  return (
    <div>
      <label>{label}</label>
      <input type='text' onChange={onChange} />
      <button onClick={() => onSearch('current-value')}>Search</button>
    </div>
  )
}

export default SearchInput

In this section, onSearch is defined as a function that takes a string and returns nothing (void). The onChange prop uses React.ChangeEvent<HTMLInputElement>, which is a specific React type that provides full type safety for input change events, including the correctly typed event.target.value.

4. The Children Prop with React.ReactNode

When creating “wrapper” or “container” components, you often need to pass nested elements. The children prop is a special prop in React that should be typed as React.ReactNode to accept strings, numbers, elements, or arrays of these types.

import React from 'react'

interface ContainerProps {
  children: React.ReactNode
  fluid?: boolean
}

const LayoutContainer = ({ children, fluid }: ContainerProps) => {
  const className = fluid ? 'container-fluid' : 'container'
  
  return (
    <div className={className}>
      {children}
    </div>
  )
}

export default LayoutContainer

Using React.ReactNode is the most inclusive type for children. It covers everything that React can render. If you need to perform operations on children before rendering, such as if you wanted to sort an array of data passed as children, you would need to ensure the logic handles the complexity of the ReactNode type.

5. Extending Standard HTML Attributes

When building UI components like buttons or inputs, you often want your component to accept all standard HTML attributes (like className, style, or onClick) without manually defining every single one in your interface. You can achieve this by extending React’s internal attribute types.

import React from 'react'
import { CButton } from '@coreui/react'

interface ActionButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant: 'primary' | 'secondary'
  isLoading?: boolean
}

const ActionButton = ({ variant, isLoading, children, ...rest }: ActionButtonProps) => {
  return (
    <CButton color={variant} disabled={isLoading} {...rest}>
      {isLoading ? 'Loading...' : children}
    </CButton>
  )
}

export default ActionButton

By extending React.ButtonHTMLAttributes<HTMLButtonElement>, our ActionButton automatically inherits all standard button props. We then use the rest operator (...rest) to spread these attributes onto the underlying CoreUI Button component. This pattern is exactly what we use in our Free React Admin Template to ensure maximum flexibility.

6. Passing Arrays and Filtering Data

Components frequently receive arrays of data to render lists. It is vital to type these arrays accurately to ensure that the mapping logic remains type-safe. If you need to filter an array before passing it as a prop, the receiving component should expect the filtered type.

import React from 'react'

interface Item {
  id: string
  name: string
}

interface ListComponentProps {
  items: Item[]
  emptyMessage?: string
}

const ItemList = ({ items, emptyMessage = 'No items found' }: ListComponentProps) => {
  if (items.length === 0) {
    return <p>{emptyMessage}</p>
  }

  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  )
}

export default ItemList

Defining items as Item[] (an array of Item objects) allows TypeScript to verify that every object in the array has an id and a name. This prevents common errors like accessing properties that don’t exist during the .map() execution.

Best Practice Note:

Always prefer interface over type for public-facing component props, as interfaces are extendable and provide better error messages in many TypeScript versions. This is the standard approach we follow in CoreUI to maintain a scalable and developer-friendly codebase. Additionally, always destructure your props at the top of your function to keep your component logic clean and readable. To learn how these typed props interact with hooks like useState and useEffect, check out our guide on how to use TypeScript with React Hooks.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
JavaScript printf equivalent
JavaScript printf equivalent

How to validate an email address in JavaScript
How to validate an email address in JavaScript

Understanding the Difference Between NPX and NPM
Understanding the Difference Between NPX and NPM

How to Choose the Best Bootstrap Admin Template in 2026: Complete Buyer's Guide
How to Choose the Best Bootstrap Admin Template in 2026: Complete Buyer's Guide

Answers by CoreUI Core Team