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 },
});