CSS tab animation
by timguignard
HTML
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
<div class="tabs-wrapper">
<div class="tabs">
<button class="tab tab-1">
<span class="tab-icon">
<i class="icon" data-lucide="layout-dashboard"></i>
</span>
<span class="tab-label">
<span>Dashboard</span>
</span>
</button>
<button class="tab tab-2">
<span class="tab-icon">
<i class="icon" data-lucide="message-square"></i>
</span>
<span class="tab-label"><span>Comm Link</span></span>
</button>
<button class="tab tab-3">
<span class="tab-icon">
<i class="icon" data-lucide="user"></i>
</span>
<span class="tab-label"><span>Operatives</span></span>
</button>
<button class="tab tab-4">
<span class="tab-icon">
<i class="icon" data-lucide="settings"></i>
</span>
<span class="tab-label"><span>Systems</span></span>
</button>
</div>
<div class="content-scroll-container">
<div class="content-panel panel-1">
<div class="progress-block"></div>
<h2>Dashboard Overview</h2>
<p>
Welcome to the main dashboard. Swipe or scroll right to view more
panels.
</p>
</div>
<div class="content-panel panel-2">
<div class="progress-block"></div>
<h2>Communications</h2>
<p>All incoming and outgoing transmissions are securely logged here.</p>
</div>
<div class="content-panel panel-3">
<div class="progress-block"></div>
<h2>Active Operatives</h2>
<p>Real-time tracking and vitals for field personnel.</p>
</div>
<div class="content-panel panel-4">
<div class="progress-block"></div>
<h2>System Configuration</h2>
<p>Mainframe settings, security overrides, and network preferences.</p>
</div>
</div>
</div>
SCSS
@property --progress {
syntax: "<integer>";
initial-value: 0;
inherits: true;
}
:root {
--color-border: #e5e7eb;
--color-text-muted: #6b7280;
--color-text-hover: #111827;
--bg-hover: #f3f4f6;
--color-active: #111827;
--color-panel-1: var(--color-active);
--color-panel-2: var(--color-active);
--color-panel-3: var(--color-active);
--color-panel-4: var(--color-active);
}
* {
box-sizing: border-box;
}
// Form controls don't inherit the page font by default
button {
font: inherit;
}
html {
margin: 0;
height: 100%;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
body {
margin: 0;
font-family: "Roboto", sans-serif;
font-weight: 400;
background-color: #f5f5f5;
height: 100%;
}
main {
padding: 20px;
display: flex;
width: 100%;
height: 100%;
align-items: center;
justify-content: center;
}
.tabs-wrapper {
position: relative;
max-width: 100vw;
overflow: hidden;
box-shadow: 0 10px 10px rgba(41, 51, 100, 0.05);
border: 1px solid rgba(41, 51, 100, 0.06);
border-radius: 0.5rem;
background-color: #fff;
timeline-scope: --panel-1, --panel-2, --panel-3, --panel-4;
}
// --- Tabs Styles ---
.tabs {
display: flex;
position: relative;
overflow-x: auto;
scroll-behavior: smooth;
box-shadow: inset 0 -1px 0 var(--color-border);
}
.tab {
display: flex;
align-items: stretch;
position: relative;
padding: 0.75rem 1rem;
background: transparent;
border: none;
cursor: pointer;
font-size: 0.875rem;
font-weight: 500;
transition:
background-color 0.2s ease,
border-radius 0.2s ease;
flex: 0 0 auto;
white-space: nowrap;
color: var(--color-text-muted);
animation: active-tab-style linear;
&::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 2px;
...
JavaScript
lucide.createIcons();
// Replace every <i data-lucide="..."> with its SVG icon
lucide.createIcons({
icons: { LayoutDashboard, MessageSquare, User, Settings },
});