How to Create a Configurable Tabs Component in React

How to Create a Configurable Tabs Component in React

With TypeScript and HTML content support
Ferenc Almasi β€’ 2024 February 23 β€’ Read time 11 min read β€’ React v18.2
Learn how you can build a configurable Tabs component in React with TypeScript and HTML content support.
  • twitter
  • facebook
React Previous Tutorial

Tabs offer a versatile and efficient solution for organizing content, enhancing navigation, and improving the overall user experience. Whether you're building a simple webpage or a complex web application, integrating tabs can significantly enhance the usability and accessibility of your product.

In this tutorial, we'll take a look at how to create a configurable (through props) Tabs component in React with TypeScript included. Without further ado, let's jump into coding.


Laying out the Component

First, let's see how we want to use the Tabs component. It'll accept items and optionally an activeTab prop:

Copied to clipboard! Playground
import { Tabs } from './Tabs'

export const App = () => {
    const tabs = [
        {
            label: 'Tab 1',
            content: '<p>Content of the tab goes here, with <b>HTML</b> support</p>',
        },
        {
            label: 'Tab 2',
            content: '<ul><li>Content</li><li>From</li><li>CMS</li></ul>',
        }
    ]

    return <Tabs items={tabs} activeTab={2} />
}
App.jsx
How to use the Tabs component
  • Lines 4-13: The data for the tabs can be represented as an array of objects. In a real-world scenario, this data would be retrieved from a backend service such as a REST API for a CMS, or from a local JSON file that holds page data. Each tab can have a label and content that is displayed under the tab.
  • Line 15: The data is passed to the items prop, and we can also pass an optional activeTab prop that can define which tab should be active by default. If this prop is omitted, the component will default back to the first tab.
Design of the Tabs component
Position of the label and content properties of each tab
πŸ” Login to get access to the full source code in one piece.

Building the Tabs Component

Now that we know how the component will function, let's take a look at the component itself. Create a new file called Tabs.tsx in your project folder and add the following code to create the component:

Copied to clipboard! Playground
import classNames from 'classnames'
import React, { useState } from 'react'

export const Tabs = ({ activeTab = 1, items }) => {
    const [activeTabState, setActiveTab] = useState(activeTab)

    return (
        <React.Fragment>
            <ul className="tabs">
                {items?.map((item, index) => (
                    <li
                        key={index}
                        onClick={() => setActiveTab(index + 1)}
                        className={classNames(index + 1 === activeTabState && 'active')}
                    >
                        {item.label}
                    </li>
                ))}
            </ul>
            <div
                className="tabs-content"
                dangerouslySetInnerHTML={{ __html: items[activeTabState - 1].content }}
            />
        </React.Fragment>
    )
}
Tabs.tsx
Add the layout for the Tabs component

Inside this component, we make use of the popular classNames library to conditionally apply class names. Run npm i classnames in your project to install it.

  • Line 5: To switch between tabs, we need to use an internal state. Note that the aciveTab prop is passed to the useState hook as the initial value. When we switch between tabs, this state will be updated to the index of the tab. For example, if the first tab is active, its value will be 1.
  • Lines 10-18: The tabs themselves will be displayed as an unordered list, which better describes their structure than using divs. We can use a map to loop through the item props to display them as li elements. Don't forget to pass a key prop for React.
  • Line 13: To handle tab changes, we can add an onClick listener and update the state using the setActiveTab updater function. We need to pass index + 1 as the parameter because the index starts from 0, but we want to use numbers starting from 1 for the prop.
  • Line 14: Based on the activeTabState variable, we can conditionally apply an .active class on the li using the classNames library. This means that whichever tab is active will get an .active class.
  • Lines 20-23: Lastly, the content will be displayed in a div under the tabs. To allow the use of HTML tags, we can set the content of the div using the dangerouslySetInnerHTML prop. To get the correct content, we need to reference items[activeTabState - 1]. As activeTabState uses indexing from 1, we need to convert back to 0-based indexing by subtracting one from the activeTabState variable.

Adding TypeScript types

The layout of the component is ready, but we're missing the TypeScript types. To add the proper types for the props, extend the component with the following lines:

Copied to clipboard!
export type TabsProps = {
    activeTab?: number
    items: {
        label: string
        content: string
    }[]
}

export const Tabs = ({ activeTab = 1, items }: TabsProps) => { ... }
Tabs.tsx
Extend the Tabs component with TypeScript types

We can export the type from the file in case we need to reuse them elsewhere. Inside the type, we have the two props:

  • activeTab: An optional prop denoted by ?. This will be a number with a default value of 1 that is assigned in the parameter list on line 9.
  • items: The items prop is an array of objects. We can define the structure for the object and add [] at the end of the object to indicate it references an array of objects. Both the label and content properties are required strings.

Improving accessibility

Although the component is fully functioning at this stage, let's further improve it by adding some extra props on the li element to enhance the accessibility of the component. Currently, users cannot navigate between tabs using the keyboard, which affects usability. We can easily fix this by adding two extra props on the li elements:

Copied to clipboard! Playground
<li
    key={index}
    onClick={() => setActiveTab(index + 1)}
    onFocus={() => setActiveTab(index + 1)}
    className={classNames(
        index + 1 === activeTabState && 'active'
    )}
    tabIndex={0}
>
Tabs.tsx
Add support for keyboard navigation

The tabIndex attribute makes the element focusable. A value of 0 means the element should be focusable in sequential keyboard navigation, indicating that the focus navigation order is defined by the order of the elements in the document.

Now that the element is focusable via the keyboard, we can add an onFocus event listener, which should execute the same function as the onClick callback. If we use the Tab key to focus on the element, we can now navigate between different tabs. Likewise, we can use Shift + Tab to navigate backward.

Looking to improve your skills? Check out our interactive course to master React from start to finish.
Master Reactinfo Remove ads

Styling the Tabs

With every functionality in place, there's only one thing left to do: style the component. Create a new CSS file next to the component file called tabs.css and import it into the component above TabsProps:

Copied to clipboard!
import './tabs.css'
Tabs.tsx
Import a CSS file into the Tabs component

We're going to define the styles related to the Tabs component in this file. To order the tab elements next to each other and style the li elements too, append the following to the tabs.css file:

Copied to clipboard! Playground
:root {
    --border: #E7E7E7;
    --background: #E4E4E4;
}

.tabs {
    margin: 0;
    padding: 0;
    list-style-type: none;
    display: flex;
    gap: 5px;
}

.tabs li {
    padding: 2px 20px;
    background: var(--background);
    border: 1px solid var(--border);
    border-bottom: 0;
    cursor: pointer;
    user-select: none;
    border-top-left-radius: 2px;
    border-top-right-radius: 2px;
}
tabs.css
Add styles for the Tabs component

For the colors, we can define CSS variables on the :root element to make them available across the project. In a real-world scenario, these variables should be defined in your CSS reset file.

  • .tabs: The tabs element itself is displayed as a flexbox with a 5px gap between each tab. Also, make sure to set list-style-type to none to avoid displaying the bullet points.
  • .tabs li: The important parts for the li elements are highlighted. Each li will have a 1px border without a bottom border to ensure it blends with the container below the tabs. Because we don't need a border-radius on the bottom, we can target the top using top-left-radius and top-right-radius. Two other CSS rules can also improve user experience:
    • cursor: Setting the cursor property to pointer can indicate to users that the element is interactive when it's being hovered.
    • user-select: Setting user-select to none ensures that users won't be able to copy the text of the tab. This prevents a flashing caret when the element is clicked.
  • Tab 1
  • Tab 2
  • Tab 3

With the above styles applied, we'll have the above tabs. However, we're still missing the styles for the content and the .active tab, so let's add them next. Extend the CSS with the following lines to add the rest of the styles:

Copied to clipboard! Playground
.tabs li.active {
    background: #FFF;
    border-bottom: 1px solid #FFF;
    cursor: auto;
    margin-bottom: -1px;
}

.tabs-content {
    padding: 10px;
    background: #FFF;
    border: 1px solid var(--border);
    border-bottom-left-radius: 2px;
    border-bottom-right-radius: 2px;
}
tabs.css
Add styles for the active state and container
  • Tab 1
  • Tab 2
  • Tab 3

Content of the tab goes here, with HTML support (Tab 1)

With the new rules, we have everything ready for the component to be used. Let's inspect the styles we need for each element to achieve the final result:

  • .tabs li.active: For both selectors, the last three rules are important. To make the active tab and the content appear as one element, we need a bottom border with the same color as the background of .tabs-content. We also need to set its margin-bottom to -1px to cover the top border of the .tabs-content. A nice addition is setting cursor to auto to indicate to the user that the active tab is not interactive, as it's already in an active state.
  • .tabs-content: For the container of the content, the border-radius should only apply to the bottom borders, so we need to use bottom-left and bottom-right-radius.
πŸ” Login to get access to the full source code in one piece.

Benefits of Using Tabs

With the styles added, we're ready to use the component in new and existing projects. Organizing information into different tabs comes with several benefits:

  • check
    Organization: Tabs help organize content into separate sections, making it easier for users to navigate and understand the information presented.
  • check
    Space Efficiency: They save space on the UI by hiding content until it's needed, allowing users to focus on the active tab's content without distractions.
  • check
    User Experience: They provide a seamless user experience by allowing users to switch between related content without having to load new pages or scroll extensively.
  • check
    Clarity and Focus: Tabs can also improve the clarity of complex interfaces by breaking down information into manageable chunks, helping users focus on one task or topic at a time.

Summary

Congratulations! You've just completed the creation of your very first Tabs component in React! Interested in elevating this project even further? Here are some ideas on how to enhance the functionality of this component:

  • Add support for using icons
  • Implement different variants with different styles
  • Add prop for closing and creating tabs dynamically

Are you looking for more React projects? Check out our React projects roadmap, a comprehensive guide that offers inspiration on what to build next and how to approach it. Thank you for reading, and happy coding!

React Projects Roadmap
  • twitter
  • facebook
React
Did you find this page helpful?
πŸ“š More Webtips
Mentoring

Rocket Launch Your Career

Speed up your learning progress with our mentorship program. Join as a mentee to unlock the full potential of Webtips and get a personalized learning experience by experts to master the following frontend technologies:

Courses

Recommended

This site uses cookies We use cookies to understand visitors and create a better experience for you. By clicking on "Accept", you accept its use. To find out more, please see our privacy policy.