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.
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}
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.
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}
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// ...
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// ...
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}
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}
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};
Here's the final output.
Here is the full source code for your reference.
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}
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}
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.