This is part of the Ext JS to React blog series. You can review the code from this article on the Ext JS to React Git repo.
The Tab Panel component is a commonly used view in both desktop and mobile UIs. Using tabbed views maximizes the real estate available for otherwise large, even full-screen layouts. The switchable view effectively offers different “pages” of UI within a given application view. In this article we’ll take a look at a sample implementation of tabs using React to render our views and handle user interactions.
Note: While not a requirement for React, this article’s code examples assume you’re starting with a starter app generated by create-react-app.
React Tab Class
In Ext JS, Tab Panels are passed items with text attributes and / or icons to display in auto-generated Tabs. To accomplish the same thing in React we need to first build our Tab and TabPanel classes. We can then look at an example implementation using both classes along with the CSS needed for styling and layout. Our Tab class will be used internally by our TabPanel class.
import React from 'react';
import FontAwesome from '@fortawesome/react-fontawesome';
import * as Icons from '@fortawesome/fontawesome-free-solid';
import './Tab.css';
const Tab = props => {
const { tabtext, icon, activetab, cardindex, onClick } = props,
isActive = activetab === cardindex ? ' active' : '';
return (
<div className={`tab${isActive}`} onClick={onClick}>
{icon ? <FontAwesome icon={Icons[`fa${icon}`]} /> : ''}
{tabtext}
</div>
);
};
export default Tab;
This example requires installing the FontAwesome icon modules:
npm install --save @fortawesome/fontawesome npm install --save @fortawesome/react-fontawesome npm install --save @fortawesome/fontawesome-free-solid
Initially, we import React, FontAwesome, and the FontAwesome icons. Next, we’ll extract some variables from the props passed in from the parent TabPanel container (which we’ll define in the next section). This will give us:
tabtextastext: the tab texticon(if provided)activetab: the currently active index / tabcardindex: the index where this tab resides- The derived
isActivestate indicating whether the active index matches our tab index. We’ll use this to set an “active” CSS class on our tab to differentiate from inactive tabs onClick: ourTabPanel‘s tab-click handler method
Finally, we’ll return the JSX that defines the shape of our tab. This will include the class decoration when the tab is active, the click handler, and the tab text / icon.
React Tab CSS
Below is the CSS imported by the above Tab.js:
.tab {
text-align: center;
cursor: pointer;
border: 2px solid transparent;
}
.tab:hover {
background-color: #d8d8d8;
}
.tab.active:hover {
background-color: transparent;
}
.tab .svg-inline--fa {
margin-right: 8px;
}
React TabPanel Class
Next, let’s look at the TabPanel class:
import React, { Component } from 'react';
import Tab from './Tab';
import './TabPanel.css';
class TabPanel extends Component {
static defaultProps = {
activetab: 0,
className: '',
position: 'top'
};
constructor(props) {
super(props);
this.state = {
activetab: props.activetab
};
}
render() {
const { children, className, position } = this.props;
const { activetab } = this.state;
return (
<div {...this.props} className={`${className} tab-panel ${position}`}>
<div className={`tab-strip`}>
{React.Children.map(children, (child, i) => (
<Tab
onClick={this.onTabClick.bind(this, i)}
{...child.props}
cardindex={i}
activetab={activetab}
/>
))}
</div>
<div className="card-ct">
{React.Children.map(children, (child, i) => {
let { className } = child.props;
className = className ? ` ${className}` : '';
const isActive = i === activetab ? ' active' : '';
const cardProps = {
...child.props,
className: `card${className}${isActive}`,
cardindex: i,
activetab: activetab
};
return React.cloneElement(child, cardProps);
})}
</div>
</div>
);
}
onTabClick(activetab) {
this.setState({
activetab
});
}
}
export default TabPanel;
React TabPanel Class Explained
The static defaultProps property sets the defaults for various props on the TabPanel. The constructor method sets the initial state and activetab property. The onTabClick method handles the Tab click that sets the activetab property that decorates the active tab and shows the active child card. The render method:
- Combines any
classNamestring passed in with those added by the class - Collects the
activetabfrom the component state object to inform the tabs / cards which one is currently active / visible - In the return:
- Create the wrapping
TabPanelelement that will house the tab container and card container in a flex layout - The tab container is added and we iterate over the child nodes (cards) passed to our
TabPanelto createTabinstances passing in the child nodes’ text and icon along with the current node index and the active tab index from theTabPanel‘s state. The active tab is decorated as active when thecardindexmatches theactivetab. - The child node (card) container is added and we loop over the child nodes, this time calling
React.cloneElementin order to add a few props likeclassName,cardindex, andactivetabto the original nodes that were passed in. The cloned elements are returned in an array as the child nodes of the parent element. The card whosecardindexmatches theactivetabis shown while the other cards are hidden using CSS rules.
- Create the wrapping
React TabPanel CSS
Before we look at implementing our TabPanel, let’s first show the CSS used to make our TabPanel lay out correctly and toggle active tabs / cards:
.tab-panel {
display: flex;
}
.tab-panel.top {
flex-direction: column;
}
.tab-panel.bottom {
flex-direction: column-reverse;
}
.tab-panel.left {
flex-direction: row;
}
.tab-panel.right {
flex-direction: row-reverse;
}
.tab-strip {
background-color: #e8e8e8;
display: flex;
justify-content: flex-start;
}
.tab-strip.indicator {
justify-content: center;
}
.tab-panel.top .tab-strip,
.tab-panel.bottom .tab-strip {
flex-direction: row;
}
.tab-panel.left .tab-strip,
.tab-panel.right .tab-strip {
flex-direction: column;
}
.tab-panel.top .tab,
.tab-panel.bottom .tab {
padding: 12px 6px;
}
.tab-panel.left .tab,
.tab-panel.right .tab {
padding: 6px 12px;
}
.tab-panel.top .tab.active {
border-bottom-color: #1e8bfb;
}
.tab-panel.left .tab.active {
border-right-color: #1e8bfb;
}
.tab-panel.bottom .tab.active {
border-top-color: #1e8bfb;
}
.tab-panel.right .tab.active {
border-left-color: #1e8bfb;
}
.card-ct {
flex: 1;
display: flex;
}
.card {
padding: 12px;
flex: 1 0 auto;
display: none;
}
.card.active {
display: block !important;
}
React TabPanel Example
Below is the code used to create a TabPanel with two tabs / cards each with tab text + icon:
import React, { Component } from 'react';
import TabPanel from './TabPanel';
class App extends Component {
render() {
return (
<TabPanel style={{ height: '400px', width: '600px' }}>
<div tabtext="Home" icon="Home">
Content for the first panel
</div>
<div tabtext="User" icon="User">
... and the second panel
</div>
</TabPanel>
);
}
}
export default App;
In our instance example, we’ve passed in a style prop to give the TabPanel specific dimensions. The activetab index may be passed in as a prop (defaults to 0). Another available prop is `position` that orients the tabs on one of the four TabPanel‘s edges (default is “top”). Our TabPanel class appends the position passed (top, left, right, and bottom) to the className of the TabPanel‘s outer element. The CSS rules then position the tabs accordingly using flexbox.
Conclusion
There are a number of pre-built UI solutions that exist in the React community. A few worth mentioning are Material UI, Semantic, and Kendo UI. If you’d like to animate and / or swipe cards within your TabPanel be sure to check out the code in the Swipeable Example on Material UI’s tab component page. In our next blog article we’ll take a look at the carousel whose utility closely resembles that of the tab panel, but with a bit leaner navigation UI.
Seth Lemmons
Related Posts
-
Ext JS to React: Migration to Open Source
Worried about Migrating from Ext JS? Modus has the Answers Idera’s acquisition of Sencha has…
-
Ext JS to React: FAQ
This is part of the Ext JS to React blog series. React is Facebook's breakout…

