A tabbed view webpage
Let’s created a tabbed view web page with HTML, CSS, and JavaScript. Our aim is to create the following:
The content pane has tabs, each displaying different content. The content of only one tab is visible at a time. Clicking on the tab title displays that tab’s content. The active tab title should have a distinctive color and a line underneath it.
Starter HTML
We start with the following HTML:
<!DOCTYPE html>
<html>
<head>
<title>Tabbed page</title>
<link rel="stylesheet" href="style.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@100;300;400;500;600;700;800&family=Noto+Sans:wght@100;300;400;500;600;700;800&family=Playpen+Sans:wght@100;300;400;500;600;700;800&family=Poppins:wght@100;300;400;500;600;700;800&family=Roboto:wght@100;300;400;500;600;700;800&display=swap" rel="stylesheet">
</head>
<body>
<section class="container">
<div class="tabs-box">
<button class="tabs-button">Home</button>
<button class="tabs-button">Products</button>
<button class="tabs-button">About us</button>
<button class="tabs-button">Contact</button>
</div>
<div class="content-box">
<div class="content">
<h2>Home</h2>
<p>
Welcome to our website. Here, we provide a site for the web. It is meant to be informational. Not for entertainment. If you are looking for entertainment, we refer you to watch Cartoon Network.
</p>
</div>
<div class="content">
<h2>Products</h2>
<p>
Our products cater to our customers' needs. We produce products for all kinds of clientele. We are famous for the following products:
<ul>
<li><a href="#">NoteTaker Free</a> Notes app for personal use.</li>
<li><a href="#">NoteTaker Pro</a> Notes app with the ability to allow editing by up to three other people.</li>
<li><a href="#">NoteTaker Enterprise</a> Notes app with collaborative editing by up to 1000 users.</li>
</ul>
</p>
</div>
<div class="content">
<h2>About us</h2>
<p>
Our team consists of highly skilled acrobats. We know what we are doing and are proud of that. Our products are the result of engineers investing unpaid overtime sacrificing their work-life balance on a consistent basis.
</p>
</div>
<div class="content">
<h2>Contact</h2>
<p>
You may contact us on the following:
<ul>Email: write@example.com</ul>
<ul>Snail mail: 420, Woodhut Dr., London, TX, 12345</ul>
<ul>Phone: 1-800-420-9211</ul>
</p>
</div>
</div>
</section>
<script src="app.js"></script>
</body>
</html>
In the head
section, we link to a CSS file. We also link to Google Fonts for a cooler look.
In the body
section, we create a section
element and give it a class
of container
. This will house all of the page content. Inside this element, we create two div
elements, one with a class of tabs-box
and the other with a class of content-box
. The former houses are tab buttons for navigation between the tabs, and the latter houses the actual content that is displayed in each tab.
Inside the tabs-box
element, we create four button
elements, one for each of the tabs. We give each of these elements a class of tabs-button
.
Inside the content-box
element, we create four div
elements, one for each of the tab’s contents. We give each of these div
elements a class of content. Each tab’s content starts with an h2
heading, followed by a p
element. Some tabs have an unordered list, depending on the content’s needs.
Starter CSS
We start styling the content:
*, ::before, ::after {
padding: 0;
margin: 0;
box-sizing: border-box;
}
html {
font-family: 'Montserrat', 'Poppins', sans-serif;
font-size: 62.5%;
}
body {
background-image: url('background.jpeg');
background-size: cover;
background-position: center;
background-attachment: fixed;
height: 100vh;
}
We start with a CSS reset, setting the margin
and padding
on all elements to zero, and setting box-sizing
to border-box
. This ensures that we start with a consistent baseline on all browsers. We also select a default font, and set the font-size
property to 62.5%
, which means that 1 rem equals 10 pixels.
I downloaded a free background image from unsplash for the page background. I want this image to cover the page background and stay in place. So, I selected this image as the background for the body
element. I set the background-size
property to cover
, so that the image covers the entire page. I set background-position
to center, so that the image is centred on the page, and set background-attachement
to fixed
, so that the background image remains fixed in its place even when the user scrolls. Finally, we set the height
of the body
element to 100vh
so that the body element occupies the entire page height.
Styling the tabbed display
Now, let’s style the tabbed view:
body {
/* Other styles from earlier */
display: flex;
align-items: center;
}
.container {
margin-left: 2rem;
width: 600px;
min-height: 70%;
background-color: #A0CFD3;
padding: 4rem;
border-radius: 2rem;
box-shadow: 0 0 0 0.5rem rgba(0, 0, 0, 0.3);
}
We want to center the tabbed view vertically, so we use Flexbox on its parent, the body
element and set the align-item
property to center
. We select the container
class and set its width
to 600 pixels, which should be good enough on a large screen.
My background image has a person in the right half. So, I want to position the tabbed view on the left half of the screen. That’s why we didn’t set justify-content
to center
on the body
element. But with the CSS reset, this means that the container
is too close to the left edge of the screen. To fix this, we set margin-left
property to 2rem
. We gave the container
a width
of 600 pixels, a min-height
of 70%, a background color that goes nicely with the background image’s color scheme, a padding so that the content of the box is set apart from its edges. If we don’t give the container
a minimum height, the container size will increase and decrease as we switch tabs, and that wouldn’t look pretty. For rounded edges, we use the border-radius
property. Finally, we give the box a box shadow. The first two values are x, and y offsets for the light source creating the shadow. Zeros in the first two places means that the shadow spreads equally on all sides of the box. The next two values of 0.5 rem are the blur radius, and the spread radius creating a shadow that decreases in intensity as we go away from the box. The final value is the color, which is a black with an opacity of 30%.
Here’s what the page looks like at the moment:
I feel that the image is too bright, and I want to “dim” it a little bit. That requires a bit of changes to the CSS:
*, ::before, ::after {
padding: 0;
margin: 0;
box-sizing: border-box;
}
html {
font-family: 'Montserrat', 'Poppins', sans-serif;
font-size: 62.5%;
}
body {
height: 100vh;
display: flex;
align-items: center;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('background.jpeg');
background-size: cover;
background-position: center;
background-attachment: fixed;
z-index: -1;
opacity: 0.5;
}
.container {
margin-left: 2rem;
width: 600px;
min-height: 70%;
background-color: #A0CFD3;
padding: 4rem;
border-radius: 2rem;
box-shadow: 0 0 0.5rem 0.5rem rgba(0, 0, 0, 0.3);
}
I create a body::after
pseudo-element, and move the background image to it, from the body
element. I set its z-index
property to -1 on this pseudo-element to ensure that the background image is rendered below this element. I set the width
and height
to 100% so that the pseudo-element is the same size as the body
element. I give it a position
of fixed
, with top
and left
properties set to 0, so that it overlaps the body
element. With content
set to empty and opacity
set to 0.5
, we get the results we wanted.
Styling the tab buttons
The tab buttons are not well laid out. Let’s spread them out in the container
.
.tabs-box {
width: 100%;
display: flex;
justify-content: space-around;
}
Setting width
to 100%, display
to flex
, and justify-conten
to space-around
does the trick.
Let’s put a line under the buttons to set it apart from the content underneath and remove the boring button styling.
.tabs-box {
width: 100%;
display: flex;
justify-content: space-around;
border-bottom: 0.1rem solid rgba(0, 0, 0, 0.5)
}
.tabs-button {
font-size: 1.8rem;
font-weight: 600;
background: none;
border: none;
color: rgba(0, 0, 0, 0.5);
padding: 0.5rem;
cursor: pointer;
}
We use the border-bottom
property to insert a line underneath the buttons. We select the tabs-button
class and change the font size and weight so that the button text is prominent. We set the background
and border
to none
so that the buttons don’t look like, well, buttons. We change the font color to a grayish tint. We set a bit of padding
to the buttons so that the button text is set apart from the surroundings. Finally, we set the cursor
to pointer
so that the user gets a visual clue when they hover over the buttons. Here’s what the page looks like now:
Styling the content
Let’s style the content now:
.content-box {
padding: 2rem;
}
.content h2 {
font-family: 'Playpen sans', sans-serif;
margin-bottom: 1rem;
}
.content {
font-family: 'Noto sans', sans-serif;
font-size: 1.2rem;
}
.content a,
.content a:visited,
.content a:hover,
.content a:active {
text-decoration: none;
color: #60594D;
}
.content a:hover {
color: #8B635C;
}
.content ul {
list-style-type: none;
}
First, we set a bit of padding to space the text away from the edges of the content-box
element. We select distinctive fonts for the text and the heading in the content
element. We set the font size for the text at 12 pixels, and put a bit of margin below the h2
elements to space the text away from the heading.
The unordered list, and the anchor tags look boring. We get rid of the underline from under the anchor tags by setting text-decoration
to none
. We also change the color from the deafult blue color. Note that we have to style the :hover
, :active
, and :visited
pseudo-classes, and that it must be done in a specific order. Note that we set the color property for :hover
element twice. The later specification takes effect. Finally, we remove the bullets from the unordered list by setting list-style-type
to none
. Here’s what the page looks like now:
Focused tab
Let’s give some distinctive styling to the selected tab. Let’s start by modifying the HTML to give one of the tab buttons a new class name of active
.
<div class="tabs-box">
<button class="tabs-button active">Home</button>
<button class="tabs-button">Products</button>
<button class="tabs-button">About us</button>
<button class="tabs-button">Contact</button>
</div>
Next, let’s define the styles for this class.
.tabs-button.active {
color: #3E2F5B;
}
We change the text color for the active
class and give it a position of relative
. Let’s also display a solid line below the selected tab button using another DOM element. For that, let’s add a div
element to the HTML:
<div class="tabs-box">
<button class="tabs-button active">Home</button>
<button class="tabs-button">Products</button>
<button class="tabs-button">About us</button>
<button class="tabs-button">Contact</button>
<div class="line"></div>
</div>
Next, we want to actually make this line
visible. That requires some CSS:
.tabs-box {
/* Other styles from earlier */
position: relative;
}
.line {
position: absolute;
display: block;
content: '';
height: 4px;
width: 10px;
background-color: #3E2F5B;
top: 3rem;
left: 0;
}
We define the line
class with a position
of absolute
. This means that this element will be positioned relative to its closest parent that does not have a position
property set to anything but static
. In this case, it is the div
element with the tabs-box
class. We want to specify this element’s width and height, so we set its display
property to block
. We set its content
to none. We set its height
to 4 pixels for the line thickness. We set the width
to 10 pixels, and left
to zero. This would show the line To position the line
element, we set top
to 3rem
, and left
to 0. This displays the line below the “Home” button. I got the 3rem
through a bit of hit and trial until the line coincided with the thin line that we already have under all the buttons. Finally, in order to actually see something, we set the background-color
to a purplish color which matches the text color for the active
class. But the line doesn’t cover the entire button’s width. To do that, we can modify the width
property to 100%.
Next, let’s write some JavaScript button click event handlers so that clicking on the buttons switches the active tab.
const tabs = document.querySelectorAll('.tabs-button')
const line = document.querySelector('.line')
const active = document.querySelector('.active')
tabs.forEach( (tab) => {
tab.addEventListener( 'click', (event) => {
tabs.forEach( (tab) => tab.classList.remove('active'))
event.target.classList.add('active')
line.style.left = event.target.offsetLeft + 'px'
line.style.width = event.target.offsetWidth + 'px'
})
})
We obtain objects corresponding to the elements with the tabs-button
, line
, and active
classes so that we can manipulate their properties. We iterate over the buttons using the forEach()
method. To every button, we add a click event listener using the addEventListener()
method. We assign an anonymous function as the event handler, which takes the event
as an argument. When a button is clicked, all other buttons should relinquish the active
class, if any. For that, we iterate over the buttons, again, and remove that class from the classList
property. Then, we add that class to the button that was just clicked. We also need to move the underline to the right place, i.e., under the button that was just clicked. For that, we modify the left
property to the offsetLeft
property of the event target. Since each button can have a different width, we also modify the width
property accordingly.
Now, we are able to click on the buttons and get the impression of switching tabs. However, there’s a bit of a glitch. The underline is initially displayed a bit further to the left than its position once we click the “Home” button. In other words, the underline seems to jump to the right if you load the page, then click on “Home”. Let’s fix that.
Inspect the “Home” button element in the browser developer console. Then, click the “Home” button. You’ll notice that the left and width properties appear in the HTML tag.
<div class="line" style="left: 26px; width: 60px;"></div>
We take the above values, and modify the CSS:
.line {
position: absolute;
display: block;
content: '';
height: 4px;
width: 60px;
background-color: #3E2F5B;
top: 3rem;
left: 26px;
}
With that problem fixed, let’s add a bit of a CSS transition to animate the line.
.line {
/* Other styles from earlier */
transition: all 0.5s ease;
}
We added a transition
that takes 0.5 seconds. So, as we click on a button, the left
property changes. The browser changes this property slowly from its initial to final value over a period of half a second, resulting in an animation.
One tab at a time
Finally, We don’t want to see all pieces of text at the same time. Let’s hide all of the content except the one corresponding to the active tab. For that, we start by setting display
to none
for the content
class. We also define another class named visible
that has display
set to block
.
.content {
font-family: 'Noto sans', sans-serif;
font-size: 1.5rem;
display: none;
}
.content.visible {
display: block;
}
Since initially, in HTML, no content
element has the visible
class, all of the text disappears. Initially, the “Home” tab content should be visible. Let’s fix that in HTML by adding the visible
class to the “Home” tab content.
<div class="content-box">
<div class="content visible">
<h2>Home</h2>
<p>
Welcome to our website. Here, we provide a site for the web. It is meant to be informational. Not for entertainment. If you are looking for entertainment, we refer you to watch Cartoon Network.
</p>
</div>
<div class="content">
<h2>Products</h2>
<p>
Our products cater to our customers' needs. We produce products for all kinds of clientele. We are famous for the following products:
<ul>
<li><a href="#">NoteTaker Free</a> Notes app for personal use.</li>
<li><a href="#">NoteTaker Pro</a> Notes app with the ability to allow editing by up to three other people.</li>
<li><a href="#">NoteTaker Enterprise</a> Notes app with collaborative editing by up to 1000 users.</li>
</ul>
</p>
</div>
<div class="content">
<h2>About us</h2>
<p>
Our team consists of highly skilled acrobats. We know what we are doing and are proud of that. Our products are the result of engineers investing unpaid overtime sacrificing their work-life balance on a consistent basis.
</p>
</div>
<div class="content">
<h2>Contact</h2>
<p>
You may contact us on the following:
<ul>Email: write@example.com</ul>
<ul>Snail mail: 420, Woodhut Dr., London, TX, 12345</ul>
<ul>Phone: 1-800-420-9211</ul>
</p>
</div>
</div>
Next, we want to change the visible content on button clicks. Back to the JavaScript!
const content = document.querySelectorAll('.content')
tabs.forEach( (tab, index) => {
tab.addEventListener( 'click', (event) => {
/* Other code from earlier */
content.forEach( (para) => para.classList.remove('visible'))
content[index].classList.add('visible')
})
})
We obtain an object corresponding to the div
elements with the class of content
. This will be a collection with four entries, one for each tab. In our click event handler, we iterate over all of these entries, removing the visible
class from each. Then, we need to access the content
element corresponding to the button clicked. This would be at the same index as the corresponding button has in the buttons array. So, we modify our forEach()
over the tabs
array, and add the index
as argument. We then index
into the content
array and add a class of visible
to it. That takes care of that.
Content animation
Let’s make the content appear as if its sliding from below:
.content {
/* Other styles from earlier */
animation: slide 0.5s ease;
}
@keyframes slide {
from {
transform: translateY(80px);
opacity: 0.3;
}
to {
transform: translateY(0);
opacity: 1;
}
}
We define the animation
property and refer to an animation named slide
to be played over half a second gradually. The animation defines two keyframes
. At first, we set the content to start displaced 80 pixels below its actual position, with an opacity of 30%. Its final position is to its expected position on we web page (thanks to translateY(0)
), with an opacity
of 100%.
That’s all folks!
With that our project is complete. Note that our styles work for screen sizes of upto 768 pixels width. Any narrower and the display gets distorted. So, we need to have an alternate style for smaller screens. This can be done using media queries. For smaller screens, perhaps we would do away with the tabs and put the different sections one after the other in vertical fashion.
You may download the code from this repository. If you want to copy paste, here’s the HTML:
<!DOCTYPE html>
<html>
<head>
<title>Tabbed page</title>
<link rel="stylesheet" href="style.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@100;300;400;500;600;700;800&family=Noto+Sans&family=Playpen+Sans:wght@400;600&family=Poppins:wght@100;300;400;500;600;700;800&family=Roboto:wght@300&display=swap" rel="stylesheet">
</head>
<body>
<section class="container">
<div class="tabs-box">
<button class="tabs-button active">Home</button>
<button class="tabs-button">Products</button>
<button class="tabs-button">About us</button>
<button class="tabs-button">Contact</button>
<div class="line"></div>
</div>
<div class="content-box">
<div class="content visible">
<h2>Home</h2>
<p>
Welcome to our website. Here, we provide a site for the web. It is meant to be informational. Not for entertainment. If you are looking for entertainment, we refer you to watch Cartoon Network.
</p>
</div>
<div class="content">
<h2>Products</h2>
<p>
Our products cater to our customers' needs. We produce products for all kinds of clientele. We are famous for the following products:
<ul>
<li><a href="#">NoteTaker Free</a> Notes app for personal use.</li>
<li><a href="#">NoteTaker Pro</a> Notes app with the ability to allow editing by up to three other people.</li>
<li><a href="#">NoteTaker Enterprise</a> Notes app with collaborative editing by up to 1000 users.</li>
</ul>
</p>
</div>
<div class="content">
<h2>About us</h2>
<p>
Our team consists of highly skilled acrobats. We know what we are doing and are proud of that. Our products are the result of engineers investing unpaid overtime sacrificing their work-life balance on a consistent basis.
</p>
</div>
<div class="content">
<h2>Contact</h2>
<p>
You may contact us on the following:
<ul>Email: write@example.com</ul>
<ul>Snail mail: 420, Woodhut Dr., London, TX, 12345</ul>
<ul>Phone: 1-800-420-9211</ul>
</p>
</div>
</div>
</section>
<script src="app.js"></script>
</body>
</html>
Here’s the CSS:
*, ::before, ::after {
padding: 0;
margin: 0;
box-sizing: border-box;
}
html {
font-family: 'Montserrat', 'Poppins', sans-serif;
font-size: 62.5%;
}
body {
height: 100vh;
display: flex;
align-items: center;
}
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('background.jpeg');
background-size: cover;
background-position: center;
background-attachment: fixed;
z-index: -1;
opacity: 0.5;
}
.container {
margin-left: 2rem;
width: 600px;
min-height: 70%;
background-color: #A0CFD3;
padding: 4rem;
border-radius: 2rem;
box-shadow: 0 0 0.5rem 0.5rem rgba(0, 0, 0, 0.3);
}
.tabs-box {
width: 100%;
display: flex;
justify-content: space-around;
border-bottom: 0.1rem solid rgba(0, 0, 0, 0.5);
position: relative;
}
.tabs-button {
font-size: 1.8rem;
font-weight: 600;
background: none;
border: none;
color: rgba(0, 0, 0, 0.5);
padding: 0.5rem;
cursor: pointer;
}
.tabs-button.active {
color: #3E2F5B;
}
.line {
display: block;
content: '';
height: 4px;
width: 60px;
background-color: #3E2F5B;
position: absolute;
top: 3rem;
left: 26px;
transition: all 0.5s ease;
}
.content-box {
padding: 2rem;
}
.content h2 {
font-family: 'Playpen sans', sans-serif;
margin-bottom: 1rem;
}
.content {
font-family: 'Noto sans', sans-serif;
font-size: 1.5rem;
display: none;
animation: slide 0.5s ease;
}
@keyframes slide {
from {
transform: translateY(80px);
opacity: 0.3;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.content.visible {
display: block;
}
.content a,
.content a:visited,
.content a:hover,
.content a:active {
text-decoration: none;
color: #81171B;
}
.content a:hover {
color: #8B635C;
}
.content ul {
list-style-type: none;
}
Finally, here’s the JavaScript:
const tabs = document.querySelectorAll('.tabs-button')
const line = document.querySelector('.line')
const content = document.querySelectorAll('.content')
const active = document.querySelector('.active')
tabs.forEach( (tab, index) => {
tab.addEventListener( 'click', (event) => {
tabs.forEach( (tab) => tab.classList.remove('active'))
event.target.classList.add('active')
line.style.left = event.target.offsetLeft + 'px'
line.style.width = event.target.offsetWidth + 'px'
content.forEach( (para) => para.classList.remove('visible'))
content[index].classList.add('visible')
})
})