Free Web Design Code & Scripts

Tab Menu With Svg Animated Icon

Tab Menu With Svg Animated Icon
Code Snippet:Outline-to-Fill Tab Icon Animations
Author: Jon Kantner
Published: 4 weeks ago
Last Updated: November 19, 2025
Downloads: 42
License: MIT
Edit Code online: View on CodePen
Read More

This tutorial will guide you through the process of creating a dynamic Tab Menu With Svg Animated Icon. This type of menu is especially useful for enhancing user experience on websites and applications, offering a visually appealing and intuitive way for users to navigate between different sections or functionalities. The animated SVG icons provide a modern touch and can greatly improve engagement.

Adding Header Assets

Before diving into the structure, let’s add the necessary assets to the <head> of your HTML document. This includes the viewport meta tag and links to external stylesheets like Google Fonts.

<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"><link rel='stylesheet' href='https://fonts.googleapis.com/css2?family=Noto+Sans:wght@500&amp;display=swap'>

Creating the HTML Structure

Now, let’s build the HTML structure for the tab menu. This involves creating a navigation bar with a list of tabs, each containing an SVG icon and a text label.

<nav class="tab-bar">
	<ul class="tab-bar__tabs">
		<li class="tab-bar__tab">
			<a class="tab-bar__tab-link" href="#home" aria-current="page">
				<svg class="tab-bar__tab-icon tab-bar__tab-icon--home" viewBox="0 0 24 24" width="24px" height="24px" aria-hidden="true">
					<g class="tab-bar__tab-icon-1" fill="var(--focus-t)" stroke="currentColor" stroke-width="2" stroke-linejoin="round">
						<polygon points="12 1,23 10,23 23,16 23,16 14,8 14,8 23,1 23,1 10" />
					</g>
				</svg>
				<span class="tab-bar__tab-name">Home</span>
			</a>
		</li>
		<li class="tab-bar__tab">
			<a class="tab-bar__tab-link" href="#videos">
				<svg class="tab-bar__tab-icon tab-bar__tab-icon--videos" viewBox="0 0 24 24" width="24px" height="24px" aria-hidden="true">
					<g fill="var(--focus-t)" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
						<line class="tab-bar__tab-icon-1" x1="3" y1="1" x2="21" y2="1" />
						<line x1="2" y1="5" x2="22" y2="5" />
						<g class="tab-bar__tab-icon-2" transform="translate(1,9)">
							<polygon points="9 3,15 7.5,9 11" />
							<rect rx="2" ry="2" width="22" height="14" />
							<polygon class="tab-bar__tab-icon-3" opacity="0" points="9 3,15 7.5,9 11" />
						</g>
					</g>
				</svg>
				<span class="tab-bar__tab-name">Videos</span>
			</a>
		</li>
		<li class="tab-bar__tab">
			<a class="tab-bar__tab-link" href="#books">
				<svg class="tab-bar__tab-icon tab-bar__tab-icon--books" viewBox="0 0 24 24" width="24px" height="24px" aria-hidden="true">
					<g class="tab-bar__tab-icon-1" fill="var(--focus-t)" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
						<rect class="tab-bar__tab-icon-2" x="1" y="1" rx="2" ry="2" width="11" height="19" />
						<rect class="tab-bar__tab-icon-3" x="12" y="1" rx="2" ry="2" width="11" height="19" />
						<line x1="12" y1="21" x2="12" y2="23" />
					</g>
				</svg>
				<span class="tab-bar__tab-name">Books</span>
			</a>
		</li>
		<li class="tab-bar__tab">
			<a class="tab-bar__tab-link" href="#profile">
				<svg class="tab-bar__tab-icon tab-bar__tab-icon--profile" viewBox="0 0 24 24" width="24px" height="24px" aria-hidden="true">
					<g fill="var(--focus-t)" stroke="currentColor" stroke-width="2">
						<circle class="tab-bar__tab-icon-1" cx="12" cy="6.5" r="5.5"/>
						<path d="M20.473,23H3.003c-1.276,0-2.228-1.175-1.957-2.422,.705-3.239,3.029-8.578,10.693-8.578s9.987,5.336,10.692,8.575c.272,1.248-.681,2.425-1.959,2.425Z"/>
					</g>
				</svg>
				<span class="tab-bar__tab-name">Profile</span>
			</a>
		</li>
	</ul>
</nav>
    <script  src="./script.js"></script>

Styling with CSS

Next, we will style the tab menu using CSS. This will cover the basic layout, colors, animations, and responsive behavior of the menu.

* {
  border: 0;
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

:root {
  --hue: 223;
  --bg: hsl(var(--hue),10%,90%);
  --fg: hsl(var(--hue),10%,10%);
  --focus: hsl(var(--hue),90%,50%);
  --focus-t: hsla(var(--hue),90%,50%,0);
  --tab-bar-bg: hsl(0,0%,100%);
  --trans-dur: 0.3s;
  --trans-timing: cubic-bezier(0.65,0,0.35,1);
  font-size: calc(14px + (30 - 14) * (100vw - 280px) / (3840 - 280));
}

body {
  background-color: var(--bg);
  color: var(--fg);
  display: flex;
  font: 1em/1.5 "Noto Sans", sans-serif;
  height: 100vh;
  transition: background-color var(--trans-dur), color var(--trans-dur);
}

.tab-bar {
  background-color: var(--tab-bar-bg);
  border-radius: 2em;
  box-shadow: 0 0 0.75em hsla(var(--hue), 10%, 10%, 0.1);
  margin: auto;
  width: calc(100% - 1.5em);
  max-width: 27em;
  transition: background-color var(--trans-dur), box-shadow var(--trans-dur);
}
.tab-bar__tabs {
  display: flex;
  justify-content: space-between;
  list-style: none;
}
.tab-bar__tab {
  text-align: center;
  width: 100%;
}
.tab-bar__tab-icon, .tab-bar__tab-name {
  display: block;
  pointer-events: none;
  transition: opacity var(--trans-dur) var(--trans-timing), transform var(--trans-dur) var(--trans-timing);
}
.tab-bar__tab-icon {
  margin: auto;
  overflow: visible;
  width: 1.5em;
  height: auto;
}
.tab-bar__tab-icon circle,
.tab-bar__tab-icon path,
.tab-bar__tab-icon polygon,
.tab-bar__tab-icon rect {
  transition: fill calc(var(--trans-dur) / 2) var(--trans-timing), opacity calc(var(--trans-dur) / 2) var(--trans-timing), stroke calc(var(--trans-dur) / 2) var(--trans-timing), transform var(--trans-dur) var(--trans-timing);
}
.tab-bar__tab-icon-1, .tab-bar__tab-icon-2, .tab-bar__tab-icon-3 {
  animation-duration: calc(var(--trans-dur) * 2.5);
  animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1);
}
.tab-bar__tab-icon--home .tab-bar__tab-icon-1 {
  transform-origin: 12px 24px;
}
.tab-bar__tab-icon--videos .tab-bar__tab-icon-3 {
  fill: var(--tab-bar-bg);
  stroke: var(--tab-bar-bg);
}
.tab-bar__tab-icon--books .tab-bar__tab-icon-2, .tab-bar__tab-icon--books .tab-bar__tab-icon-3 {
  transform-origin: 12px 21px;
}
.tab-bar__tab-name {
  font-size: 0.75em;
  font-weight: 500;
  line-height: 1;
  top: calc(100% - 0.5rem);
  opacity: 0;
  overflow: hidden;
  position: absolute;
  text-overflow: ellipsis;
  white-space: nowrap;
  width: 100%;
}
.tab-bar__tab-link {
  color: var(--fg);
  display: flex;
  position: relative;
  text-decoration: none;
  width: 100%;
  height: 5.5em;
  transition: color calc(var(--trans-dur) / 2);
  -webkit-tap-highlight-color: transparent;
}
.tab-bar__tab-link:hover, .tab-bar__tab-link:focus-visible {
  color: var(--focus);
}
.tab-bar__tab-link[aria-current=page] {
  color: var(--focus);
}
.tab-bar__tab-link[aria-current=page] .tab-bar__tab-icon {
  transform: translateY(-50%);
}
.tab-bar__tab-link[aria-current=page] .tab-bar__tab-icon circle,
.tab-bar__tab-link[aria-current=page] .tab-bar__tab-icon path,
.tab-bar__tab-link[aria-current=page] .tab-bar__tab-icon polygon,
.tab-bar__tab-link[aria-current=page] .tab-bar__tab-icon rect {
  fill: var(--focus);
}
.tab-bar__tab-link[aria-current=page] .tab-bar__tab-name {
  opacity: 1;
  transform: translateY(-200%);
}
.tab-bar__tab-link[aria-current=page] .tab-bar__tab-icon--home .tab-bar__tab-icon-1 {
  animation-name: home-bounce;
}
.tab-bar__tab-link[aria-current=page] .tab-bar__tab-icon--videos .tab-bar__tab-icon-1 {
  animation-name: video-move-1;
}
.tab-bar__tab-link[aria-current=page] .tab-bar__tab-icon--videos .tab-bar__tab-icon-2 {
  animation-name: video-move-2;
}
.tab-bar__tab-link[aria-current=page] .tab-bar__tab-icon--videos .tab-bar__tab-icon-3 {
  animation-name: video-fade-slide;
  opacity: 1;
  fill: var(--tab-bar-bg);
}
.tab-bar__tab-link[aria-current=page] .tab-bar__tab-icon--books .tab-bar__tab-icon-1 {
  animation-name: books-move;
}
.tab-bar__tab-link[aria-current=page] .tab-bar__tab-icon--books .tab-bar__tab-icon-2 {
  animation-name: books-scale-left;
}
.tab-bar__tab-link[aria-current=page] .tab-bar__tab-icon--books .tab-bar__tab-icon-3 {
  animation-name: books-scale-right;
}
.tab-bar__tab-link[aria-current=page] .tab-bar__tab-icon--profile .tab-bar__tab-icon-1 {
  animation-name: profile-head-bob;
}
[data-pristine] .tab-bar__tab-icon-1, [data-pristine] .tab-bar__tab-icon-2, [data-pristine] .tab-bar__tab-icon-3 {
  animation-duration: 0s;
}

/* Dark theme */
@media (prefers-color-scheme: dark) {
  :root {
    --bg: hsl(var(--hue),10%,30%);
    --fg: hsl(var(--hue),10%,90%);
    --focus: hsl(var(--hue),90%,60%);
    --focus-t: hsla(var(--hue),90%,60%,0);
    --tab-bar-bg: hsl(var(--hue),10%,10%);
  }

  .tab-bar {
    box-shadow: 0 0 0.75em hsla(var(--hue), 10%, 10%, 0.3);
  }
}
/* Animations */
@media (prefers-reduced-motion) {
  .tab-bar__tab-icon-1, .tab-bar__tab-icon-2, .tab-bar__tab-icon-3 {
    animation-duration: 0s;
  }
}
@keyframes home-bounce {
  from, to {
    transform: scale(1, 1) translateY(0);
  }
  20% {
    transform: scale(1.5, 0.75) translateY(0);
  }
  40% {
    transform: scale(0.8, 1.2) translateY(-4px);
  }
  60% {
    transform: scale(1.1, 0.9) translateY(0);
  }
  80% {
    transform: scale(0.95, 1.05) translateY(0);
  }
}
@keyframes video-move-1 {
  from, to {
    transform: translate(0, 0);
  }
  20%, 80% {
    transform: translate(0, 4px);
  }
}
@keyframes video-move-2 {
  from, to {
    transform: translate(1px, 9px);
  }
  20%, 80% {
    transform: translate(1px, 5px);
  }
}
@keyframes video-fade-slide {
  from {
    animation-timing-function: steps(1, end);
    opacity: 0;
    transform: translate(0, 0);
  }
  40% {
    animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1);
    opacity: 1;
    stroke: rgba(0, 0, 0, 0);
    transform: translate(-4px, 0);
  }
  60%, to {
    opacity: 1;
    stroke: var(--tab-bar-bg);
    transform: translate(0, 0);
  }
}
@keyframes books-move {
  from, 60%, to {
    transform: translateY(0);
  }
  20% {
    transform: translateY(-1px);
  }
  40% {
    transform: translateY(0.5px);
  }
}
@keyframes books-scale-left {
  from, to {
    transform: skewY(0);
  }
  20% {
    transform: skewY(-16deg);
  }
  40% {
    transform: skewY(12deg);
  }
  60% {
    transform: skewY(-8deg);
  }
  80% {
    transform: skewY(4deg);
  }
}
@keyframes books-scale-right {
  from, to {
    transform: skewY(0);
  }
  20% {
    transform: skewY(16deg);
  }
  40% {
    transform: skewY(-12deg);
  }
  60% {
    transform: skewY(8deg);
  }
  80% {
    transform: skewY(-4deg);
  }
}
@keyframes profile-head-bob {
  from, to {
    transform: translateX(0);
  }
  20% {
    transform: translateX(4px);
  }
  40% {
    transform: translateX(-3px);
  }
  60% {
    transform: translateX(2px);
  }
  80% {
    transform: translateX(-1px);
  }
}

Implementing JavaScript Functionality

To make the tab menu interactive, we’ll add JavaScript code to handle tab switching and animation triggers.

"use strict";
window.addEventListener("DOMContentLoaded", () => {
    const tabbar = new TabBar("nav");
});
class TabBar {
    /**
     * @param el CSS selector for the tab bar
     */
    constructor(el) {
        var _a, _b;
        this.el = document.querySelector(el);
        (_a = this.el) === null || _a === void 0 ? void 0 : _a.setAttribute("data-pristine", "true");
        (_b = this.el) === null || _b === void 0 ? void 0 : _b.addEventListener("click", this.switchTab.bind(this));
    }
    /**
     * Make the clicked tab active.
     * @param e Click event
     */
    switchTab(e) {
        var _a, _b;
        // allow animations, which were prevented on load
        (_a = this.el) === null || _a === void 0 ? void 0 : _a.removeAttribute("data-pristine");
        const target = e.target;
        const href = target.getAttribute("href");
        // target should be a link before assigning the “current” state
        if (href) {
            // remove the state from the current page…
            const currentPage = (_b = this.el) === null || _b === void 0 ? void 0 : _b.querySelector(`[aria-current="page"]`);
            if (currentPage) {
                currentPage.ariaCurrent = null;
            }
            // …and apply it to the next
            target.ariaCurrent = "page";
        }
    }
}

That’s all! Hopefully, you have successfully created Tab Menu With Svg Animated Icon. If you have any questions or suggestions, feel free to comment below.

Loading... ...

Loading preview...

Device: Desktop
Dimensions: 1200x800
Lines: 0 Characters: 0 Ln 1, Ch 1

Leave a Comment

About W3Frontend

W3Frontend provides free, open-source web design code and scripts to help developers and designers build faster. Every snippet is reviewed before publishing for quality. Learn more.