Building a Tabs Component with React

joshtronic

If you’ve ever built a web app, there’s a good chance you’ve built a tabbed document interface at some point or another. Tabs allow you to break up complex interfaces into (presumably) more manageable subsections that a user can quickly switch between. Love ‘em or hate ‘em, tabs aren’t going anywhere any time soon.

In this article you will learn how to create a simple and reusable tab container component that you can use by itself or with your existing components.

We will create three components:

  • Tabs that will display a list of clickable Tab components across the top, hold a state for which tab is active, and the active tab’s contents below the list of tabs.
  • Tab which displays the tab’s label, handles click events and let the Tabs component know which tab has been clicked.
  • App component so we can see our tabs in action!

Instead of going crazy with a bunch of tiny components, we will use boring old <div> tags for the tabs themselves. Each will have a label attribute that will be used for the list of clickable tabs.


Let’s get started with our App component that will hold our Tabs:

import React from 'react';
import { render } from "react-dom";

function App() {
  return (
    <div>
      <h1>Tabs Demo</h1>
    </div>
  );
}

const container = document.createElement('div');
document.body.appendChild(container);
render(<App />, container);

Nothing too exciting, yet. Just basic boilerplate stuff to get us up and running.


Next, we’ll build out our Tabs component:

Component: Tabs.js

import React, { Component } from 'react';
import PropTypes from 'prop-types';

import Tab from './Tab';

class Tabs extends Component {
  static propTypes = {
    children: PropTypes.instanceOf(Array).isRequired,
  }

  constructor(props) {
    super(props);

    this.state = {
      activeTab: this.props.children[0].props.label,
    };
  }

  onClickTabItem = (tab) => {
    this.setState({ activeTab: tab });
  }

  render() {
    const {
      onClickTabItem,
      props: {
        children,
      },
      state: {
        activeTab,
      }
    } = this;

    return (
      <div className="tabs">
        <ol className="tab-list">
          {children.map((child) => {
            const { label } = child.props;

            return (
              <Tab
                activeTab={activeTab}
                key={label}
                label={label}
                onClick={onClickTabItem}
              />
            );
          })}
        </ol>
        <div className="tab-content">
          {children.map((child) => {
            if (child.props.label !== activeTab) return undefined;
            return child.props.children;
          })}
        </div>
      </div>
    );
  }
}

export default Tabs;

This component keeps track of which tab is active, displays a list of tabs and the content for the active tab.

The Tabs component utilizes our next component, Tab:

Component: Tab.js

import React, { Component } from 'react';
import PropTypes from 'prop-types';

class Tab extends Component {
  static propTypes = {
    activeTab: PropTypes.string.isRequired,
    label: PropTypes.string.isRequired,
    onClick: PropTypes.func.isRequired,
  };

  onClick = () => {
    const { label, onClick } = this.props;
    onClick(label);
  }

  render() {
    const {
      onClick,
      props: {
        activeTab,
        label,
      },
    } = this;

    let className = 'tab-list-item';

    if (activeTab === label) {
      className += ' tab-list-active';
    }

    return (
      <li
        className={className}
        onClick={onClick}
      >
        {label}
      </li>
    );
  }
}

export default Tab;

The Tab component displays the name of the tab and adds an additional class if the tab is active. When clicked, the component will fire a handler that will let Tabs know which tab should be active.


In addition to the components we just created, let’s add a small bit of CSS to make sure everything looks snazzy (and tab-like):

Styles: styles.css

.tab-list {
  border-bottom: 1px solid #ccc;
  padding-left: 0;
}

.tab-list-item {
  display: inline-block;
  list-style: none;
  margin-bottom: -1px;
  padding: 0.5rem 0.75rem;
}

.tab-list-active {
  background-color: white;
  border: solid #ccc;
  border-width: 1px 1px 0 1px;
}

Now that we have our components and associated styles, we will update our App component to utilize them:

import React from 'react';
import { render } from "react-dom";

import Tabs from './Tabs';
require('./styles.css');

function App() {
  return (
    <div>
      <h1>Tabs Demo</h1>
     <Tabs>
      <div label="Gator">
        See ya later, <em>Alligator</em>!
      </div>
      <div label="Croc">
        After 'while, <em>Crocodile</em>!
      </div>
      <div label="Sarcosuchus">
        Nothing to see here, this tab is <em>extinct</em>!
      </div>
    </Tabs>
    </div>
  );
}

const container = document.createElement('div');
document.body.appendChild(container);
render(<App />, container);

With Tabs added to our App component’s render method, we should have a working tabbed interface that allows us to toggle between sections!

It should look something like this:

React Tabs Component

You can find a working demo and code from this article over on CodeSandbox.

Enjoy! 💥

  Tweet It

🕵 Search Results

🔎 Searching...