Important Update
The Guide Feature will be discontinued after December 15th, 2023. Until then, you can continue to access and refer to the existing guides.
Author avatar

Gaurav Singhal

How to Compose Components to Create Tabs in React

Gaurav Singhal

  • Mar 16, 2020
  • 9 Min read
  • 1,944 Views
  • Mar 16, 2020
  • 9 Min read
  • 1,944 Views
Web Development
Front End Web Development
Client-side Framework
React

Introduction

This guide will focus on how to compose components to create a tab component that will display a tab-based navigation view in a declarative manner—that is, with fewer and simpler props with no callbacks at the higher level. All the logic will be inside the <Tabs /> component, making it easy to use anywhere in your project.

The <App /> Component

Inside the <App /> component, call the <Tabs /> component as follows.

1export default function App() {
2  return (
3    <div className="App">
4      <h1>Tab Component Demo</h1>
5      <h2>Start adding tabs to see some magic happen!</h2>
6      <Tabs>
7        <div title="Tab 1">
8          Ut condimentum, ex sit amet rutrum ultrices, nisi velit condimentum
9          mi, et sagittis turpis ligula ut neque. Nunc ut arcu velit. Sed
10          suscipit nisl est, ut scelerisque elit pretium et.
11        </div>
12        <div title="Tab 2">
13          Phasellus vel nisi lectus. Curabitur ac efficitur quam. Aliquam
14          faucibus, nibh non auctor auctor, lectus leo cursus massa, ac ornare
15          dolor ante eu libero.
16        </div>
17        <div title="Tab 3">
18          Aliquam erat volutpat. Sed id pulvinar magna. Aenean scelerisque ex
19          vitae faucibus faucibus. Proin rutrum est eget odio faucibus tempor.
20          Nulla ut tellus non urna congue finibus.
21        </div>
22      </Tabs>
23    </div>
24  );
25}
jsx

The <Tabs /> component can have multiple child components that represent each tab. Each tab must have a title prop and should contain the tab content as its child.

A Simple <Tabs /> Component

Let's get the basic structure of the component sorted.

1class Tabs extends Component {
2  state = {
3    activeTabIndex: 0
4  };
5
6  render() {
7    const { activeTabIndex } = this.state;
8    const activeTab = props.children[activeTabIndex];
9    return (
10      <div>
11        <div className="tabs">
12          {
13            //
14          }
15        </div>
16        <div className="tab-content">{activeTab.props.children}</div>
17      </div>
18    );
19  }
20}
jsx

In the state, keep track of the active tab index so that you can track which of the tabs is currently active.

Inside the tabs section, loop through the children and display each title in a <button />. On click of the tab button, set the index as the current active tab. The tab-content section will display the content of the active tab.

1// ...
2<div className="tabs">
3  {props.children.map((tab, i) => (
4    <button
5      className="tab-btn"
6      onClick={() => {
7        setActiveTabIndex(i);
8      }}
9      key={i}
10    >
11      {tab.props.title}
12    </button>
13  ))}
14</div>
15// ...
jsx

Now, if you check out the result, you'll be able to navigate through the tabs. But it's not looking that great; there's no way the user is going to understand which tab is currently active. So, you'll have to create a tab indicator for that.

1// ...
2<div className="tabs">
3    {props.children.map((tab, i) => (
4      <button
5        className="tab-btn"
6        onClick={() => {
7          setActiveTabIndex(i);
8        }}
9        key={i}
10      >
11        {tab.props.title}
12      </button>
13    ))}
14</div>
15<div className="tab-indicator-container">
16  <div
17    className="tab-indicator"
18    style={{
19      width: 100 / props.children.length + "%",
20      transform: `translateX(${activeTabIndex * 100}%)`
21    }}
22  />
23</div>
24<div className="tab-content">{activeTab.props.children}</div>
25// ...
jsx

Use the transform property to slide the indicator to the currently active tab. To determine the width of the indicator, consider the tab count, which can be retrieved with props.children.length.

Use this CSS to prettify the component.

1.tabs {
2  display: flex;
3  align-items: center;
4  justify-content: center;
5}
6
7.tab-btn {
8  display: block;
9  flex: 1;
10  padding: 8px 20px;
11  background: #eee;
12  border: 0;
13}
14
15.tab-indicator-container {
16  display: flex;
17  background: #eee;
18  margin-bottom: 10px;
19}
20
21.tab-indicator {
22  height: 3px;
23  background: #007bff;
24  transition: transform 0.2s;
25}
26
27.tab-content {
28  padding: 10px;
29}
css

And finally, the <Tabs /> component will look as follows.

1class Tabs extends Component {
2  state = {
3    activeTabIndex: 0
4  };
5
6  render() {
7    const { activeTabIndex } = this.state;
8    const activeTab = props.children[activeTabIndex];
9    return (
10      <div>
11        <div className="tabs">
12          {props.children.map((tab, i) => (
13            <button
14              className="tab-btn"
15              onClick={() => {
16                this.setState({ activeTabIndex: i });
17              }}
18              key={i}
19            >
20              {tab.props.title}
21            </button>
22          ))}
23        </div>
24        <div className="tab-indicator-container">
25          <div
26            className="tab-indicator"
27            style={{
28              width: 100 / props.children.length + "%",
29              transform: `translateX(${activeTabIndex * 100}%)`
30            }}
31          />
32        </div>
33        <div className="tab-content">{activeTab.props.children}</div>
34      </div>
35    );
36  }
37}
jsx

Instead of a class component, you can also have a functional component by using the useState hook. Here's how the functional version would look.

1const Tabs = props => {
2  const [activeTabIndex, setActiveTabIndex] = useState(0);
3  const activeTab = props.children[activeTabIndex];
4  return (
5    <div>
6      <div className="tabs">
7        {props.children.map((tab, i) => (
8          <button
9            className="tab-btn"
10            onClick={() => {
11              setActiveTabIndex(i);
12            }}
13            key={i}
14          >
15            {tab.props.title}
16          </button>
17        ))}
18      </div>
19      <div className="tab-indicator-container">
20        <div
21          className="tab-indicator"
22          style={{
23            width: 100 / props.children.length + "%",
24            transform: `translateX(${activeTabIndex * 100}%)`
25          }}
26        />
27      </div>
28      <div className="tab-content">{activeTab.props.children}</div>
29    </div>
30  );
31};
jsx

Here's the final output.

Complete Source Code

Here is the full source code for your reference.

App.js

1import React, { useState } from "react";
2import "./styles.css";
3
4const Tabs = props => {
5  const [activeTabIndex, setActiveTabIndex] = useState(0);
6  const activeTab = props.children[activeTabIndex];
7  return (
8    <div>
9      <div className="tabs">
10        {props.children.map((tab, i) => (
11          <button
12            className="tab-btn"
13            onClick={() => {
14              setActiveTabIndex(i);
15            }}
16            key={i}
17          >
18            {tab.props.title}
19          </button>
20        ))}
21      </div>
22      <div className="tab-indicator-container">
23        <div
24          className="tab-indicator"
25          style={{
26            width: 100 / props.children.length + "%",
27            transform: `translateX(${activeTabIndex * 100}%)`
28          }}
29        />
30      </div>
31      <div className="tab-content">{activeTab.props.children}</div>
32    </div>
33  );
34};
35
36export default function App() {
37  return (
38    <div className="App">
39      <h1>Tab Component Demo</h1>
40      <h2>Start adding tabs to see some magic happen!</h2>
41      <Tabs>
42        <div title="Tab 1">
43          Ut condimentum, ex sit amet rutrum ultrices, nisi velit condimentum
44          mi, et sagittis turpis ligula ut neque. Nunc ut arcu velit. Sed
45          suscipit nisl est, ut scelerisque elit pretium et.{" "}
46        </div>
47        <div title="Tab 2">
48          Phasellus vel nisi lectus. Curabitur ac efficitur quam. Aliquam
49          faucibus, nibh non auctor auctor, lectus leo cursus massa, ac ornare
50          dolor ante eu libero.{" "}
51        </div>
52        <div title="Tab 3">
53          Aliquam erat volutpat. Sed id pulvinar magna. Aenean scelerisque ex
54          vitae faucibus faucibus. Proin rutrum est eget odio faucibus tempor.
55          Nulla ut tellus non urna congue finibus.{" "}
56        </div>
57      </Tabs>
58    </div>
59  );
60}
jsx

styles.css

1.App {
2  font-family: sans-serif;
3  text-align: center;
4}
5
6.tabs {
7  display: flex;
8  align-items: center;
9  justify-content: center;
10}
11
12.tab-btn {
13  display: block;
14  flex: 1;
15  padding: 8px 20px;
16  background: #eee;
17  border: 0;
18}
19
20.tab-indicator-container {
21  display: flex;
22  background: #eee;
23  margin-bottom: 10px;
24}
25
26.tab-indicator {
27  height: 3px;
28  background: #007bff;
29  transition: transform 0.2s;
30}
31
32.tab-content {
33  padding: 10px;
34}
css

Conclusion

Tabs make it easier for users to navigate your application, and they are an important part of the UI structure of any application. Most frameworks implement their own version of tabs in their designs, which follow different patterns.

I hope you liked this guide and are looking forward to building some custom components.