Open Source UI Kit

Tailwind / Bootstrap / Vanilla CSS - UI Blocks

Kumpulan komponen siap pakai berbasis AlpineJS & Tailwind CSS, Bootstrap 5, dan Vanilla CSS.
Copy, paste, dan sesuaikan langsung di proyek Anda.

AlpineJS v3 Tailwind CSS v4 Bootstrap 5 Vanilla CSS Laravel + Blade
Components
7
Framework
Laravel
License
MIT

All Components

7 components

Navbar

AlpineJS Tailwind CSS Bootstrap 5 Vanilla CSS

Navigasi responsif dengan sidebar toggle, nama layanan reaktif, dark mode toggle, dan responsive offset. Berbasis AlpineJS.

Cara Penggunaan Navbar

Navbar ini bersifat fixed top , mendukung dark mode , menampilkan nama layanan secara reaktif dari Alpine store, dan memiliki tombol toggle sidebar yang otomatis muncul di layar kecil. Tersedia untuk Tailwind CSS, Bootstrap 5, dan Vanilla CSS.

Dependensi

Library Tailwind Bootstrap 5 Vanilla CSS
Alpine.js v3 ✓ Wajib ✓ Wajib ✓ Wajib
Bootstrap 5 CSS+JS ✓ Wajib

Langkah 1 — Tambahkan CDN di <head>

Tailwind CSS & Vanilla CSS — tambahkan Alpine.js:

<!-- Alpine.js CDN -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>

Bootstrap 5 — tambahkan Bootstrap CSS, Bootstrap JS Bundle, dan Alpine.js:

<!-- Bootstrap 5 CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
<!-- Bootstrap 5 JS Bundle -->
<script defer src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Alpine.js CDN -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>

Langkah 2 — Tambahkan x-data pada tag <html>

Sama untuk semua varian (Tailwind, Bootstrap, Vanilla):

<html lang="id"
  x-data="{
    darkMode: localStorage.getItem('darkMode') === 'true',
    toggleDarkMode() {
      this.darkMode = !this.darkMode;
      localStorage.setItem('darkMode', this.darkMode);
      document.documentElement.classList.toggle('dark', this.darkMode);
    }
  }"
  x-init="document.documentElement.classList.toggle('dark', darkMode);"
  :class="{ 'dark': darkMode }"
>

Langkah 3 — Layout Wrapper (responsive margin)

Konten utama harus diberi margin kiri hanya pada layar besar (≥ 1024px) agar tidak tertutup sidebar:

Tailwind CSS :

<div class="lg:ml-64">
  <!-- navbar + konten + footer -->
</div>

Bootstrap 5 :

/* CSS */
.bs-main-wrapper { display: flex; flex-direction: column; min-height: 100vh; }
@media (min-width: 1024px) { .bs-main-wrapper { margin-left: 256px; } }

<!-- HTML -->
<div class="bs-main-wrapper">
  <!-- navbar + konten + footer -->
</div>

Vanilla CSS :

/* CSS */
.vn-main-wrapper { display: flex; flex-direction: column; min-height: 100vh; }
@media (min-width: 1024px) { .vn-main-wrapper { margin-left: 256px; } }

<!-- HTML -->
<div class="vn-main-wrapper">
  <!-- navbar + konten + footer -->
</div>

Langkah 4 — Copy & Paste Kode Navbar

Pilih tab sesuai kebutuhan ( Tailwind CSS , Bootstrap 5 , atau Vanilla CSS ), klik tombol Copy , lalu tempel ke dalam file layout Anda. Tempatkan @include (Laravel) atau langsung paste HTML-nya sebelum konten utama, di dalam wrapper yang sudah dibuat di langkah 4.

Fitur Navbar

Fitur Keterangan
Sidebar Toggle Tombol hamburger muncul otomatis di mobile (< 1024px). Mengontrol $store.sidebarMobile.open .
Dark Mode Toggle Ikon matahari / bulan. Memanggil toggleDarkMode() dari x-data di tag <html> .
Responsive Offset left: 0 di mobile, left: 16rem di desktop (≥ 1024px) agar sejajar dengan tepi sidebar.
<nav
  class="fixed right-0 left-0 z-40 h-17 border-b border-gray-200 bg-white transition-all duration-300 lg:left-64 dark:border-slate-800 dark:bg-slate-900"
>
  <div class="flex items-center justify-between px-5 py-4">
    {{-- ── Left: Sidebar toggle (mobile) + Service Name ── --}}
    <div class="flex items-center gap-3">
      {{-- Sidebar toggle — visible only on small screens --}}
      <button
        @click="$store.sidebarMobile.open = !$store.sidebarMobile.open"
        type="button"
        class="flex items-center justify-center rounded-lg p-1.5 text-gray-500 hover:bg-gray-100 hover:text-gray-700 focus:outline-none lg:hidden dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-white"
        aria-label="toggle sidebar"
      >
        <svg
          xmlns="http://www.w3.org/2000/svg"
          class="h-6 w-6"
          fill="none"
          viewBox="0 0 24 24"
          stroke="currentColor"
          stroke-width="2"
        >
          <path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16" />
        </svg>
      </button>

      {{-- Service / App Name --}}
      <span
        class="text-sm font-semibold text-gray-700 dark:text-gray-200"
        x-text="
          $store.menuFilter.selectedApp
            ? $store.menuFilter.selectedLayanan
              ? $store.menuFilter.selectedApp + ' — ' + $store.menuFilter.selectedLayanan
              : $store.menuFilter.selectedApp
            : 'Pilih Aplikasi'
        "
      ></span>
    </div>

    {{-- ── Right: Action buttons ── --}}
    <div class="flex gap-3">
      <button
        class="flex h-9 w-9 max-w-9 items-center justify-center rounded-lg border border-gray-200 bg-gray-100/50 dark:border-gray-700/40 dark:bg-gray-800/20"
      >
        <svg
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="1.5"
          stroke="currentColor"
          class="relative h-5 w-5 text-gray-600 duration-300 dark:text-white"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0"
          />
        </svg>
      </button>

      <button
        class="flex h-9 w-9 max-w-9 items-center justify-center rounded-lg border border-gray-200 bg-gray-100/50 dark:border-gray-700/40 dark:bg-gray-800/20"
      >
        <svg
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="1.5"
          stroke="currentColor"
          class="relative h-5 w-5 text-gray-600 duration-300 dark:text-white"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M3 4.5h14.25M3 9h9.75M3 13.5h9.75m4.5-4.5v12m0 0-3.75-3.75M17.25 21 21 17.25"
          />
        </svg>
      </button>

      <button
        @click="toggleDarkMode()"
        aria-label="theme switching"
        type="button"
        class="group flex h-9 w-9 max-w-9 items-center justify-center rounded-lg border border-gray-200 bg-gray-100/50 dark:border-gray-700/40 dark:bg-gray-800/20"
      >
        <svg
          x-show="darkMode"
          aria-hidden="true"
          xmlns="http://www.w3.org/2000/svg"
          viewBox="0 0 24 24"
          fill="currentColor"
          class="relative h-5 w-5 text-white duration-500 group-hover:rotate-360"
        >
          <path
            d="M12 2.25a.75.75 0 01.75.75v2.25a.75.75 0 01-1.5 0V3a.75.75 0 01.75-.75zM7.5 12a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM18.894 6.166a.75.75 0 00-1.06-1.06l-1.591 1.59a.75.75 0 101.06 1.061l1.591-1.59zM21.75 12a.75.75 0 01-.75.75h-2.25a.75.75 0 010-1.5H21a.75.75 0 01.75.75zM17.834 18.894a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 10-1.061 1.06l1.59 1.591zM12 18a.75.75 0 01.75.75V21a.75.75 0 01-1.5 0v-2.25A.75.75 0 0112 18zM7.758 17.303a.75.75 0 00-1.061-1.06l-1.591 1.59a.75.75 0 001.06 1.061l1.591-1.59zM6 12a.75.75 0 01-.75.75H3a.75.75 0 010-1.5h2.25A.75.75 0 016 12zM6.697 7.757a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 00-1.061 1.06l1.59 1.591z"
          ></path>
        </svg>
        <svg
          x-show="!darkMode"
          aria-hidden="true"
          xmlns="http://www.w3.org/2000/svg"
          viewBox="0 0 24 24"
          fill="currentColor"
          class="relative h-4 w-4 text-gray-700 duration-500 group-hover:rotate-360 group-hover:text-gray-900"
        >
          <path
            fill-rule="evenodd"
            d="M9.528 1.718a.75.75 0 01.162.819A8.97 8.97 0 009 6a9 9 0 009 9 8.97 8.97 0 003.463-.69.75.75 0 01.981.98 10.503 10.503 0 01-9.694 6.46c-5.799 0-10.5-4.701-10.5-10.5 0-4.368 2.667-8.112 6.46-9.694a.75.75 0 01.818.162z"
            clip-rule="evenodd"
          ></path>
        </svg>
      </button>
    </div>
  </div>
</nav>
<style>
  .bs-nav { position: fixed; top: 0; right: 0; left: 0; z-index: 40; height: 68px; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.08); }
  @media (min-width: 1024px) { .bs-nav { left: 16rem; } }
  .dark .bs-nav { background: #0f172a; box-shadow: 0 1px 3px rgba(0,0,0,.3); }
  .bs-nav__sb-toggle { display: flex; width: 36px; height: 36px; align-items: center; justify-content: center; border-radius: .5rem; border: none; background: none; cursor: pointer; color: #374151; padding: .25rem; }
  .bs-nav__sb-toggle:hover { background: rgba(0,0,0,.06); }
  .dark .bs-nav__sb-toggle { color: #e5e7eb; }
  .bs-nav__service { font-size: .875rem; font-weight: 600; color: #374151; }
  .dark .bs-nav__service { color: #e5e7eb; }
  .bs-nav__dark-btn { width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; border-radius: .5rem; border: 1px solid rgba(229,231,235,.4); background: rgba(243,244,246,.2); cursor: pointer; color: #374151; flex-shrink: 0; }
  .bs-nav__dark-btn:hover { background: rgba(0,0,0,.08); }
  .dark .bs-nav__dark-btn { border-color: rgba(55,65,81,.4); background: rgba(31,41,55,.2); color: #e5e7eb; }
  .bs-nav__icon-btn { width: 36px; height: 36px; display: flex; align-items: center; justify-content: center; border-radius: .5rem; border: 1px solid #e5e7eb; background: rgba(243,244,246,.5); cursor: pointer; color: #4b5563; flex-shrink: 0; }
  .bs-nav__icon-btn:hover { background: rgba(0,0,0,.06); }
  .dark .bs-nav__icon-btn { border-color: rgba(55,65,81,.4); background: rgba(31,41,55,.2); color: #e5e7eb; }
  [x-cloak] { display: none !important; }
</style>

<nav class="bs-nav d-flex align-items-center">
  <div class="d-flex align-items-center w-100 px-4">

    <!-- Left: sidebar toggle + service name -->
    <div class="d-flex align-items-center gap-2">
      <button
        type="button"
        class="bs-nav__sb-toggle d-lg-none"
        @click="$store.sidebarMobile.open = !$store.sidebarMobile.open"
        aria-label="Toggle sidebar"
      >
        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none"
             viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16"/>
        </svg>
      </button>
      <span
        class="bs-nav__service"
        x-text="$store.menuFilter.selectedApp
          ? ($store.menuFilter.selectedLayanan
              ? $store.menuFilter.selectedApp + ' — ' + $store.menuFilter.selectedLayanan
              : $store.menuFilter.selectedApp)
          : 'Pilih Aplikasi'"
      ></span>
    </div>

    <!-- Right: icon buttons + dark mode toggle -->
    <div class="ms-auto d-flex align-items-center gap-2">
      <button type="button" class="bs-nav__icon-btn" aria-label="Notifikasi">
        <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none"
             viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round"
                d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0"/>
        </svg>
      </button>
      <button type="button" class="bs-nav__icon-btn" aria-label="Urutkan">
        <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="none"
             viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round"
                d="M3 4.5h14.25M3 9h9.75M3 13.5h9.75m4.5-4.5v12m0 0-3.75-3.75M17.25 21 21 17.25"/>
        </svg>
      </button>
      <button
        type="button"
        class="bs-nav__dark-btn"
        aria-label="Toggle dark mode"
        @click="toggleDarkMode()"
      >
        <svg x-show="darkMode" x-cloak xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="currentColor">
          <path d="M12 2.25a.75.75 0 01.75.75v2.25a.75.75 0 01-1.5 0V3a.75.75 0 01.75-.75zM7.5 12a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM18.894 6.166a.75.75 0 00-1.06-1.06l-1.591 1.59a.75.75 0 101.06 1.061l1.591-1.59zM21.75 12a.75.75 0 01-.75.75h-2.25a.75.75 0 010-1.5H21a.75.75 0 01.75.75zM17.834 18.894a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 10-1.061 1.06l1.59 1.591zM12 18a.75.75 0 01.75.75V21a.75.75 0 01-1.5 0v-2.25A.75.75 0 0112 18zM7.758 17.303a.75.75 0 00-1.061-1.06l-1.591 1.59a.75.75 0 001.06 1.061l1.591-1.59zM6 12a.75.75 0 01-.75.75H3a.75.75 0 010-1.5h2.25A.75.75 0 016 12zM6.697 7.757a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 00-1.061 1.06l1.59 1.591z"/>
        </svg>
        <svg x-show="!darkMode" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="currentColor">
          <path fill-rule="evenodd" d="M9.528 1.718a.75.75 0 01.162.819A8.97 8.97 0 009 6a9 9 0 009 9 8.97 8.97 0 003.463-.69.75.75 0 01.981.98 10.503 10.503 0 01-9.694 6.46c-5.799 0-10.5-4.701-10.5-10.5 0-4.368 2.667-8.112 6.46-9.694a.75.75 0 01.818.162z" clip-rule="evenodd"/>
        </svg>
      </button>
    </div>
  </div>
</nav>
<style>
  *,
  *::before,
  *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }
  .vn {
    position: fixed;
    top: 0;
    right: 0;
    left: 0;
    z-index: 40;
    height: 68px;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
    display: flex;
    align-items: center;
  }
  @media (min-width: 1024px) {
    .vn {
      left: 16rem;
    }
  }
  .dark .vn {
    background: #0f172a;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  }
  .vn__inner {
    width: 100%;
    padding: 0 1.25rem;
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .vn__left {
    display: flex;
    align-items: center;
    gap: 0.75rem;
  }
  .vn__sb-toggle {
    display: none;
    width: 36px;
    height: 36px;
    align-items: center;
    justify-content: center;
    border-radius: 0.5rem;
    border: none;
    background: none;
    cursor: pointer;
    color: #374151;
    padding: 0.25rem;
  }
  .vn__sb-toggle:hover {
    background: rgba(0, 0, 0, 0.06);
  }
  .dark .vn__sb-toggle {
    color: #e5e7eb;
  }
  @media (max-width: 1023px) {
    .vn__sb-toggle {
      display: flex;
    }
  }
  .vn__service {
    font-size: 0.875rem;
    font-weight: 600;
    color: #374151;
  }
  .dark .vn__service {
    color: #e5e7eb;
  }
  .vn__right {
    display: flex;
    align-items: center;
    gap: 1rem;
    margin-left: auto;
  }
  .vn__dark {
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 0.5rem;
    border: 1px solid rgba(229, 231, 235, 0.4);
    background: rgba(243, 244, 246, 0.2);
    cursor: pointer;
    color: #374151;
    flex-shrink: 0;
  }
  .vn__dark:hover {
    background: rgba(0, 0, 0, 0.08);
  }
  .dark .vn__dark {
    border-color: rgba(55, 65, 81, 0.4);
    background: rgba(31, 41, 55, 0.2);
    color: #e5e7eb;
  }
  .vn__icon {
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 0.5rem;
    border: 1px solid #e5e7eb;
    background: rgba(243, 244, 246, 0.5);
    cursor: pointer;
    color: #4b5563;
    flex-shrink: 0;
  }
  .vn__icon:hover {
    background: rgba(0, 0, 0, 0.06);
  }
  .dark .vn__icon {
    border-color: rgba(55, 65, 81, 0.4);
    background: rgba(31, 41, 55, 0.2);
    color: #e5e7eb;
  }
  [x-cloak] {
    display: none !important;
  }
</style>

<nav class="vn" role="navigation">
  <div class="vn__inner">
    <!-- Left: sidebar toggle + service name -->
    <div class="vn__left">
      <button
        class="vn__sb-toggle"
        aria-label="Toggle sidebar"
        @click="$store.sidebarMobile.open = !$store.sidebarMobile.open"
      >
        <svg
          width="24"
          height="24"
          viewBox="0 0 24 24"
          fill="none"
          stroke="currentColor"
          stroke-width="2"
        >
          <path stroke-linecap="round" stroke-linejoin="round" d="M4 6h16M4 12h16M4 18h16" />
        </svg>
      </button>
      <span
        class="vn__service"
        x-text="
          $store.menuFilter.selectedApp
            ? $store.menuFilter.selectedLayanan
              ? $store.menuFilter.selectedApp + ' — ' + $store.menuFilter.selectedLayanan
              : $store.menuFilter.selectedApp
            : 'Pilih Aplikasi'
        "
      ></span>
    </div>

    <!-- Right: icon buttons + dark mode toggle -->
    <div class="vn__right">
      <button class="vn__icon" aria-label="Notifikasi">
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="20"
          height="20"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="1.5"
          stroke="currentColor"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M14.857 17.082a23.848 23.848 0 0 0 5.454-1.31A8.967 8.967 0 0 1 18 9.75V9A6 6 0 0 0 6 9v.75a8.967 8.967 0 0 1-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 0 1-5.714 0m5.714 0a3 3 0 1 1-5.714 0"
          />
        </svg>
      </button>
      <button class="vn__icon" aria-label="Urutkan">
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="20"
          height="20"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="1.5"
          stroke="currentColor"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M3 4.5h14.25M3 9h9.75M3 13.5h9.75m4.5-4.5v12m0 0-3.75-3.75M17.25 21 21 17.25"
          />
        </svg>
      </button>
      <button class="vn__dark" aria-label="Toggle dark mode" @click="toggleDarkMode()">
        <svg
          x-show="darkMode"
          x-cloak
          xmlns="http://www.w3.org/2000/svg"
          width="18"
          height="18"
          viewBox="0 0 24 24"
          fill="currentColor"
        >
          <path
            d="M12 2.25a.75.75 0 01.75.75v2.25a.75.75 0 01-1.5 0V3a.75.75 0 01.75-.75zM7.5 12a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM18.894 6.166a.75.75 0 00-1.06-1.06l-1.591 1.59a.75.75 0 101.06 1.061l1.591-1.59zM21.75 12a.75.75 0 01-.75.75h-2.25a.75.75 0 010-1.5H21a.75.75 0 01.75.75zM17.834 18.894a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 10-1.061 1.06l1.59 1.591zM12 18a.75.75 0 01.75.75V21a.75.75 0 01-1.5 0v-2.25A.75.75 0 0112 18zM7.758 17.303a.75.75 0 00-1.061-1.06l-1.591 1.59a.75.75 0 001.06 1.061l1.591-1.59zM6 12a.75.75 0 01-.75.75H3a.75.75 0 010-1.5h2.25A.75.75 0 016 12zM6.697 7.757a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 00-1.061 1.06l1.59 1.591z"
          />
        </svg>
        <svg
          x-show="!darkMode"
          xmlns="http://www.w3.org/2000/svg"
          width="16"
          height="16"
          viewBox="0 0 24 24"
          fill="currentColor"
        >
          <path
            fill-rule="evenodd"
            d="M9.528 1.718a.75.75 0 01.162.819A8.97 8.97 0 009 6a9 9 0 009 9 8.97 8.97 0 003.463-.69.75.75 0 01.981.98 10.503 10.503 0 01-9.694 6.46c-5.799 0-10.5-4.701-10.5-10.5 0-4.368 2.667-8.112 6.46-9.694a.75.75 0 01.818.162z"
            clip-rule="evenodd"
          />
        </svg>
      </button>
    </div>
  </div>
</nav>

Sidebar Navigation

AlpineJS Tailwind CSS Bootstrap 5 Vanilla CSS

Nested sidebar dengan sub-panel dan sub-sub-panel. Mendukung menu bertingkat, dark mode, dan animasi slide.

Filter Menu Sidebar

 /   / 
Pilih filter untuk mengubah menu di preview sidebar.

Cara Penggunaan Menu JSON

Struktur Folder

resources/js/
├── sidebar.js          ← file utama sidebar (jangan diubah)
└── menus/
    ├── {Nama Aplikasi}/
    │   └── {Nama Layanan}/
    │       ├── {Role 1}.json
    │       ├── {Role 2}.json
    │       └── ...
    └── registry.js

Dua Format yang Didukung

Sidebar mendukung dua format JSON: Item (daftar menu biasa) dan Group (menu dikelompokkan dalam kategori). Kedua format tidak boleh dicampur dalam satu file.

Properti Item

Digunakan pada format Item dan pada array items di dalam Group :

Properti Tipe Wajib Keterangan
label string ✓ Wajib Teks yang ditampilkan pada item menu
href string Opsional* URL tujuan. Wajib jika tidak ada children
children array Opsional* Sub-menu bertingkat. Mendukung hingga 3 level kedalaman

* Wajib salah satu: href atau children .

Properti Group

Digunakan pada elemen tingkat pertama array saat menggunakan format Group :

Properti Tipe Wajib Keterangan
group string ✓ Wajib Judul kategori/grup yang ditampilkan sebagai header section
items array ✓ Wajib Daftar item menu di dalam grup ini (menggunakan struktur Item di atas)

Contoh — Format Item (tanpa grup)

[
  {
    "label": "Dashboard",
    "href": "/dashboard"
  },
  {
    "label": "Laporan",
    "children": [
      {
        "label": "Laporan Harian",
        "href": "/laporan/harian"
      },
      {
        "label": "Laporan Bulanan",
        "children": [
          { "label": "Domestik",      "href": "/laporan/bulanan/domestik" },
          { "label": "Internasional", "href": "/laporan/bulanan/internasional" }
        ]
      }
    ]
  },
  {
    "label": "Pengaturan",
    "href": "/settings"
  }
]

Contoh — Format Group (dengan kategori)

[
  {
    "group": "Sertifikasi",
    "items": [
      {
        "label": "Air Operator Certificate",
        "children": [
          { "label": "Penerbitan AOC",    "href": "/sertifikasi/aoc/penerbitan" },
          { "label": "Perpanjangan AOC",  "href": "/sertifikasi/aoc/perpanjangan" }
        ]
      },
      {
        "label": "Pilot School Certificate",
        "href": "/sertifikasi/psc"
      }
    ]
  },
  {
    "group": "Administrasi",
    "items": [
      { "label": "Master Dokumen",    "href": "/admin/dokumen" },
      { "label": "Seluruh Pengajuan", "href": "/admin/pengajuan" }
    ]
  }
]

Cara Menambah Menu Baru

  1. Buat file JSON menu di mana saja, misalnya: resources/js/menus/my-menu.json
  2. Import file tersebut di app.js lalu assign ke currentMenu di dalam store (lihat contoh di bawah)
  3. Jalankan npm run build (atau npm run dev ) agar Vite mem-bundle file JSON baru

Aturan Penamaan

  • Nama file JSON bebas — tidak harus mengikuti struktur folder tertentu
  • Setiap item wajib memiliki label dan salah satu dari href atau children
  • Format Item dan Group tidak boleh dicampur dalam satu file JSON

Setup app.js — Bagian yang Wajib Ada

Sidebar dan header membaca data dari Alpine.store('menuFilter') . Tiga properti yang dibutuhkan: selectedApp , selectedLayanan , dan currentMenu .

Skenario A — Satu menu statis (paling sederhana)

import Alpine from 'alpinejs'
import { sidebarNavigation, sidebarMobileStore } from './sidebar'
import myMenu from './menus/my-menu.json'

Alpine.store('menuFilter', {
  selectedApp:     'Nama Aplikasi',  // ditampilkan di navbar
  selectedLayanan: 'Nama Layanan',   // ditampilkan di navbar
  currentMenu:     myMenu,           // isi sidebar
})

Alpine.store('sidebarMobile', sidebarMobileStore)
Alpine.data('sidebarNavigation', () => sidebarNavigation())
window.Alpine = Alpine
Alpine.start()

Skenario B — Banyak layanan, otomatis via role ID dari server

Setiap role mendapat menu yang berbeda. Server cukup menyuntikkan window.userRoleId ke halaman — JavaScript akan otomatis memilih menu yang tepat.

1. Buat resources/js/registry.js

import aolPtOperator  from './menus/AOL/Persetujuan Terbang/Operator Penerbangan (Airlines).json'
import aolPtAirNav   from './menus/AOL/Persetujuan Terbang/Stakeholder - AirNav.json'
import aolPtApindo   from './menus/AOL/Persetujuan Terbang/Stakeholder - APINDO.json'
import aolPprpOperator from './menus/AOL/Penetapan Pelaksanaan Rute Penerbangan PPRP/Operator Penerbangan (Airlines).json'
import aolPprpAirNav  from './menus/AOL/Penetapan Pelaksanaan Rute Penerbangan PPRP/Stakeholder - AirNav.json'

// key = role_id dari database
export const menus = {
  1: { app: 'AOL', layanan: 'Persetujuan Terbang',                          menu: aolPtOperator },
  2: { app: 'AOL', layanan: 'Persetujuan Terbang',                          menu: aolPtAirNav },
  3: { app: 'AOL', layanan: 'Persetujuan Terbang',                          menu: aolPtApindo },
  4: { app: 'AOL', layanan: 'Penetapan Pelaksanaan Rute Penerbangan PPRP', menu: aolPprpOperator },
  5: { app: 'AOL', layanan: 'Penetapan Pelaksanaan Rute Penerbangan PPRP', menu: aolPprpAirNav },
  // tambah role baru di sini — app.js tidak perlu diubah
}

2. Setup app.js

import Alpine from 'alpinejs'
import { sidebarNavigation, sidebarMobileStore } from './sidebar'
import { menus } from './registry'

const roleMenu = menus[window.userRoleId]  // window.userRoleId diisi server

Alpine.store('menuFilter', {
  selectedApp:     roleMenu.app,
  selectedLayanan: roleMenu.layanan,
  currentMenu:     roleMenu.menu,
})

Alpine.store('sidebarMobile', sidebarMobileStore)
Alpine.data('sidebarNavigation', () => sidebarNavigation())
window.Alpine = Alpine
Alpine.start()

3. Inject userRoleId dari Blade (sebelum tag </body> atau di layout utama)

  <!-- letakkan SEBELUM @vite(...) agar sudah tersedia saat JS dijalankan -->
  <script>
    window.userRoleId = {{ auth()->user()->role_id }}
  </script>
  @vite(['resources/css/app.css', 'resources/js/app.js'])

Otomatis sepenuhnya: tambah role baru → tambah satu baris di registry.js → selesai. app.js tidak perlu disentuh lagi. Navbar menampilkan nama app & layanan yang sesuai role masing-masing user secara otomatis.

/**
 * Sidebar Navigation Component
 *
 * Reads the active menu from Alpine.store('menuFilter').currentMenu.
 * Use the menu filter UI (component/menu-filter.blade.php) to switch menus.
 *
 * Menu formats supported:
 *   GROUPED: [{ group: 'Label', items: [{ label, children? }] }]
 *   FLAT:    [{ label, children? }]
 */

/**
 * sidebarMobileStore — register once via Alpine.store('sidebarMobile', ...)
 * Controls sidebar open/close state on mobile screens.
 */
export const sidebarMobileStore = {
  open: false,
}

function buildGroups(menu) {
  if (!menu || menu.length === 0) return []
  const isGrouped = 'group' in menu[0]
  const rawGroups = isGrouped ? menu : [{ group: null, items: menu }]
  let idx = 0
  return rawGroups.map((g) => ({
    label: g.group,
    items: (g.items ?? []).map((item) => ({ ...item, _idx: idx++ })),
  }))
}

export function sidebarNavigation() {
  return {
    activeIndex: null,
    activeChildIndex: null,
    userMenuOpen: false,

    user: {
      name: 'Sebut Saja Mawar',
      email: 'mawar@gmail.com',
      initials: 'SM',
    },

    init() {
      // Reset active state whenever the selected menu changes
      this.$watch(
        () => this.$store.menuFilter.currentMenu,
        () => {
          this.activeIndex = null
          this.activeChildIndex = null
        },
      )
    },

    // -- computed helpers --
    get groups() {
      return buildGroups(this.$store.menuFilter.currentMenu)
    },
    get flatItems() {
      return this.groups.flatMap((g) => g.items)
    },
    get activeItem() {
      return this.activeIndex !== null
        ? (this.flatItems.find((i) => i._idx === this.activeIndex) ?? null)
        : null
    },
    get activeChild() {
      return this.activeItem && this.activeChildIndex !== null
        ? (this.activeItem.children?.[this.activeChildIndex] ?? null)
        : null
    },

    // -- methods --
    hasChildren(item) {
      return Array.isArray(item?.children) && item.children.length > 0
    },

    selectItem(itemIdx) {
      const item = this.flatItems.find((i) => i._idx === itemIdx)
      if (!this.hasChildren(item)) return
      if (this.activeIndex === itemIdx) {
        this.activeIndex = null
        this.activeChildIndex = null
      } else {
        this.activeIndex = itemIdx
        this.activeChildIndex = null
      }
    },

    selectChild(index) {
      const child = this.activeItem?.children?.[index]
      if (!this.hasChildren(child)) {
        this.activeChildIndex = null
        return
      }
      this.activeChildIndex = this.activeChildIndex === index ? null : index
    },

    // Conditionally prevent default only when item has children (opens panel).
    // When no children, the native href navigation proceeds normally.
    handleItemClick(item, $event) {
      if (this.hasChildren(item)) {
        $event.preventDefault()
        this.selectItem(item._idx)
      }
    },

    handleChildClick(child, childIndex, $event) {
      if (this.hasChildren(child)) {
        $event.preventDefault()
        this.selectChild(childIndex)
      }
    },

    handlePanelOutsideClick($event) {
      if (this.activeIndex === null) return

      // Only close when click happens truly outside sidebar areas.
      if ($event.target.closest('[data-sidebar-area]')) return

      this.activeIndex = null
      this.activeChildIndex = null
    },
  }
}
[
  {
    "label": "Volcano & CDM",
    "children": [
      {
        "label": "Volcano",
        "href": "#"
      },
      {
        "label": "Eruption Statistic",
        "href": "#"
      },
      {
        "label": "Eruption Ash Statistics",
        "href": "#"
      }
    ]
  },
  {
    "label": "Interactive",
    "href": "#"
  },
  {
    "label": "Airspace 3D View",
    "href": "#"
  },
  {
    "label": "Care",
    "href": "#"
  },
  {
    "label": "GPS RAIM Predicttool",
    "href": "#"
  },
  {
    "label": "e-AIP",
    "children": [
      {
        "label": "e-AIP",
        "href": "#"
      },
      {
        "label": "Data Traceability",
        "href": "#"
      }
    ]
  },
  {
    "label": "Inbox",
    "href": "#"
  },
  {
    "label": "Help",
    "children": [
      {
        "label": "iWISH Manual Book",
        "href": "#"
      }
    ]
  }
]
<div
  x-data="sidebarNavigation()"
  @click.window="handlePanelOutsideClick($event)"
  class="relative flex"
>
  <!-- Mobile backdrop overlay -->
  <div
    x-show="$store.sidebarMobile.open"
    @click="$store.sidebarMobile.open = false; activeIndex = null; activeChildIndex = null"
    x-transition:enter="transition duration-200 ease-out"
    x-transition:enter-start="opacity-0"
    x-transition:enter-end="opacity-100"
    x-transition:leave="transition duration-150 ease-in"
    x-transition:leave-start="opacity-100"
    x-transition:leave-end="opacity-0"
    class="fixed inset-0 z-89 bg-black/50 lg:hidden"
    style="display: none"
  ></div>

  <!-- Main Sidebar -->
  <aside
    data-sidebar-area
    :class="$store.sidebarMobile.open ? 'translate-x-0' : '-translate-x-full lg:translate-x-0'"
    class="fixed z-99 flex h-screen w-64 flex-col overflow-y-auto border-r border-gray-200 bg-white px-5 py-5 transition-transform duration-300 ease-in-out rtl:border-r-0 rtl:border-l dark:border-slate-800 dark:bg-slate-900"
  >
    <a href="#">
      <img
        class="mb-2 h-12 w-auto invert dark:invert-0"
        src="https://aviasihub-dev.kemenhub.go.id/_next/static/media/aviasihub-logo.eb7d0138.png"
        alt=""
      />
    </a>
    <div
      x-show="groups.length > 0"
      x-transition:enter="transition duration-200 ease-out"
      x-transition:enter-start="-translate-y-1 opacity-0"
      x-transition:enter-end="translate-y-0 opacity-100"
      x-cloak
      class="my-3 border-y border-gray-200 py-3 dark:border-slate-700"
    >
      <div class="flex flex-col dark:text-gray-300">
        <p class="text-sm font-semibold">Unit Kerja</p>
        <br />
        <p class="text-xs">Nama Pegawai</p>
        <p class="text-xs">NIP. 199901012009031001</p>
      </div>
    </div>

    <div class="flex flex-1 flex-col justify-between">
      <nav class="-mx-5 space-y-6">
        {{-- Empty state: no menu selected --}}
        <div x-show="groups.length === 0" class="px-3 py-8 text-center">
          <svg
            xmlns="http://www.w3.org/2000/svg"
            class="mx-auto mb-3 h-10 w-10 text-gray-300 dark:text-slate-600"
            fill="none"
            viewBox="0 0 24 24"
            stroke="currentColor"
            stroke-width="1.5"
          >
            <path
              stroke-linecap="round"
              stroke-linejoin="round"
              d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z"
            />
          </svg>
          <p class="text-xs text-gray-400 dark:text-slate-500">
            Pilih filter untuk
            <br />
            menampilkan menu
          </p>
        </div>

        <template x-for="(group, gi) in groups" :key="gi">
          <div class="space-y-1">
            <template x-if="group.label">
              <label
                class="block px-5 text-xs leading-snug font-semibold tracking-normal text-gray-400 uppercase dark:text-gray-500"
                x-text="group.label"
              ></label>
            </template>
            <template x-for="(item, ii) in group.items" :key="ii">
              <a
                @click="handleItemClick(item, $event)"
                :href="item.href ?? '#'"
                :class="activeIndex === item._idx ? 'bg-blue-100 text-blue-600 dark:bg-blue-900 dark:text-blue-200' : 'text-gray-600 dark:text-gray-200'"
                class="mx-1 flex transform cursor-pointer items-center rounded-lg px-3 py-2 transition-colors duration-300 hover:bg-gray-100 hover:text-gray-700 dark:hover:bg-gray-800 dark:hover:text-gray-200"
              >
                {{--
                  <svg
                  xmlns="http://www.w3.org/2000/svg"
                  viewBox="0 0 24 24"
                  stroke-width="1.5"
                  stroke="currentColor"
                  class="size-1 fill-current text-black dark:text-white"
                  >
                  <path
                  stroke-linecap="round"
                  stroke-linejoin="round"
                  d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
                  />
                  </svg>
                --}}
                <span class="mx-1 text-sm font-medium" x-text="item.label"></span>
                <svg
                  x-show="hasChildren(item)"
                  xmlns="http://www.w3.org/2000/svg"
                  fill="none"
                  viewBox="0 0 24 24"
                  stroke-width="1.5"
                  stroke="currentColor"
                  class="ml-auto h-4 w-4 shrink-0"
                >
                  <path
                    stroke-linecap="round"
                    stroke-linejoin="round"
                    d="M8.25 4.5l7.5 7.5-7.5 7.5"
                  />
                </svg>
              </a>
            </template>
          </div>
        </template>
      </nav>
    </div>

    <!-- User Menu Bottom -->
    <div class="-mx-5 mt-auto space-y-1">
      <!-- Settings -->
      <a
        href="#"
        class="mx-1 flex transform cursor-pointer items-center rounded-lg px-3 py-2 text-gray-600 transition-colors duration-300 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-200 dark:hover:bg-gray-800 dark:hover:text-gray-200"
      >
        <svg
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="1.5"
          stroke="currentColor"
          class="h-5 w-5 shrink-0 text-gray-500 dark:text-gray-400"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 010 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 010-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28z"
          />
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
          />
        </svg>
        <span class="mx-1 text-sm font-medium">Pengaturan</span>
      </a>

      <a
        href="#"
        class="mx-1 flex transform cursor-pointer items-center rounded-lg px-3 py-2 text-gray-600 transition-colors duration-300 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-200 dark:hover:bg-gray-800 dark:hover:text-gray-200"
      >
        <svg
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="1.5"
          stroke="currentColor"
          class="h-5 w-5 shrink-0 text-gray-500 dark:text-gray-400"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75"
          />
        </svg>
        <span class="mx-1 text-sm font-medium">Kembali</span>
      </a>
    </div>
  </aside>

  <!-- Sub Module Panel -->
  <aside
    data-sidebar-area
    data-sub-panel
    x-show="activeIndex !== null && ($store.sidebarMobile.open || window.innerWidth >= 1024)"
    x-transition:enter="transition duration-300 ease-out"
    x-transition:enter-start="translate-x-4 opacity-0"
    x-transition:enter-end="translate-x-0 opacity-100"
    x-transition:leave="transition duration-200 ease-in"
    x-transition:leave-start="translate-x-0 opacity-100"
    x-transition:leave-end="translate-x-4 opacity-0"
    class="fixed left-0 z-100 flex h-screen w-64 flex-col overflow-y-auto border-r border-gray-200 bg-white px-5 py-8 shadow-lg lg:left-64 lg:z-98 dark:border-slate-700 dark:bg-slate-900"
  >
    {{-- Panel header: back (mobile) + title + close --}}
    <div class="mb-6 flex items-center gap-2">
      <button
        @click="activeIndex = null; activeChildIndex = null"
        class="shrink-0 rounded-lg p-1.5 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600 lg:hidden dark:text-gray-500 dark:hover:bg-slate-800 dark:hover:text-gray-300"
        title="Kembali"
      >
        <svg
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="2"
          stroke="currentColor"
          class="h-4 w-4"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18"
          />
        </svg>
      </button>
      <h3
        class="flex-1 truncate text-sm font-semibold tracking-tight text-gray-900 dark:text-white"
        x-text="activeItem?.label ?? 'Sub Modul'"
      ></h3>
      <button
        @click="activeIndex = null; activeChildIndex = null"
        class="shrink-0 rounded-lg p-1.5 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600 dark:text-gray-500 dark:hover:bg-slate-800 dark:hover:text-gray-300"
        title="Tutup"
      >
        <svg
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="2"
          stroke="currentColor"
          class="h-4 w-4"
        >
          <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
        </svg>
      </button>
    </div>

    <nav class="-mx-5 space-y-1">
      <template x-if="activeItem && activeItem.children">
        <template x-for="(child, childIndex) in activeItem.children" :key="childIndex">
          <a
            @click="handleChildClick(child, childIndex, $event)"
            :href="child.href ?? '#'"
            :class="activeChildIndex === childIndex ? 'bg-blue-100 text-blue-600 dark:bg-blue-900 dark:text-blue-200' : 'text-gray-600 dark:text-gray-200'"
            class="mx-1 flex transform cursor-pointer items-center rounded-lg px-3 py-2 transition-colors duration-300 hover:bg-gray-100 hover:text-gray-700 dark:hover:bg-gray-800 dark:hover:text-gray-200"
          >
            {{--
              <svg
              xmlns="http://www.w3.org/2000/svg"
              viewBox="0 0 24 24"
              stroke-width="1.5"
              stroke="currentColor"
              class="size-1 fill-current text-black dark:text-white"
              >
              <path
              stroke-linecap="round"
              stroke-linejoin="round"
              d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
              />
              </svg>
            --}}
            <span class="mx-1 text-sm font-medium" x-text="child.label"></span>
            <svg
              x-show="hasChildren(child)"
              xmlns="http://www.w3.org/2000/svg"
              fill="none"
              viewBox="0 0 24 24"
              stroke-width="1.5"
              stroke="currentColor"
              class="ml-auto h-4 w-4 shrink-0"
            >
              <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
            </svg>
          </a>
        </template>
      </template>
    </nav>
  </aside>

  <!-- Sub-Sub Module Panel -->
  <aside
    data-sidebar-area
    data-sub-panel
    x-show="
      activeChildIndex !== null &&
        ($store.sidebarMobile.open || window.innerWidth >= 1024)
    "
    x-transition:enter="transition duration-300 ease-out"
    x-transition:enter-start="translate-x-4 opacity-0"
    x-transition:enter-end="translate-x-0 opacity-100"
    x-transition:leave="transition duration-200 ease-in"
    x-transition:leave-start="translate-x-0 opacity-100"
    x-transition:leave-end="translate-x-4 opacity-0"
    class="fixed left-0 z-101 flex h-screen w-64 flex-col overflow-y-auto border-r border-gray-200 bg-white px-5 py-8 shadow-lg lg:left-128 lg:z-97 dark:border-slate-700 dark:bg-slate-900"
  >
    {{-- Panel header: back (mobile) + title + close --}}
    <div class="mb-6 flex items-center gap-2">
      <button
        @click="activeChildIndex = null"
        class="shrink-0 rounded-lg p-1.5 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600 lg:hidden dark:text-gray-500 dark:hover:bg-slate-800 dark:hover:text-gray-300"
        title="Kembali"
      >
        <svg
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="2"
          stroke="currentColor"
          class="h-4 w-4"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18"
          />
        </svg>
      </button>
      <h3
        class="flex-1 truncate text-sm font-semibold tracking-tight text-gray-900 dark:text-white"
        x-text="activeChild?.label ?? 'Sub-Sub Modul'"
      ></h3>
      <button
        @click="activeChildIndex = null"
        class="shrink-0 rounded-lg p-1.5 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600 dark:text-gray-500 dark:hover:bg-slate-800 dark:hover:text-gray-300"
        title="Tutup"
      >
        <svg
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="2"
          stroke="currentColor"
          class="h-4 w-4"
        >
          <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
        </svg>
      </button>
    </div>

    <nav class="-mx-5 space-y-1">
      <template x-if="activeChild && activeChild.children">
        <template x-for="(grandchild, i) in activeChild.children" :key="i">
          <a
            :href="grandchild.href ?? '#'"
            class="mx-1 flex transform cursor-pointer items-center rounded-lg px-3 py-2 text-gray-600 transition-colors duration-300 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-200 dark:hover:bg-gray-800 dark:hover:text-gray-200"
          >
            {{--
              <svg
              xmlns="http://www.w3.org/2000/svg"
              viewBox="0 0 24 24"
              stroke-width="1.5"
              stroke="currentColor"
              class="size-1 fill-current text-black dark:text-white"
              >
              <path
              stroke-linecap="round"
              stroke-linejoin="round"
              d="m9.75 9.75 4.5 4.5m0-4.5-4.5 4.5M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
              />
              </svg>
            --}}
            <span class="mx-1 text-sm font-medium" x-text="grandchild.label"></span>
          </a>
        </template>
      </template>
    </nav>
  </aside>
</div>
{{--
  Sidebar — Bootstrap 5 (Alpine-powered, dark-mode aware)
  Dep: bootstrap@5.3.3 CSS + JS bundle, Alpine.js, sidebarNavigation()
--}}

<style>
  /* ── dark-mode overrides ────────────────────────────────────────── */
  html.dark .bs-sb-aside {
    background: #0f172a !important;
    border-color: #334155 !important;
  }
  html.dark .bs-sb-brand {
    border-color: #334155 !important;
  }
  html.dark .bs-sb-profile {
    border-color: #334155 !important;
  }
  html.dark .bs-sb-profile .dark\:text-gray-300 {
    color: #d1d5db !important;
  }
  html.dark .bs-sb-brand img {
    filter: none !important;
  }
  html.dark .bs-sb-glabel {
    color: #64748b !important;
  }
  html.dark .bs-sb-link {
    color: #cbd5e1 !important;
  }
  html.dark .bs-sb-link:hover {
    background: #1e293b !important;
    color: #f1f5f9 !important;
  }
  html.dark .bs-sb-link.bs-active {
    background: rgba(37, 99, 235, .2) !important;
    color: #93c5fd !important;
  }
  html.dark .bs-sb-bot {
    border-color: #334155 !important;
  }
  html.dark .bs-sb-empty {
    color: #475569;
  }
  html.dark .bs-sb-panel {
    background: #0f172a !important;
    border-color: #334155 !important;
  }
  html.dark .bs-sb-panel-title {
    color: #f8fafc !important;
  }
  html.dark .bs-sb-panel-close {
    color: #94a3b8 !important;
  }
  html.dark .bs-sb-panel-close:hover {
    background: #1e293b !important;
  }
  /* ── base styles ────────────────────────────────────────────────── */
  .bs-sb-aside {
    position: fixed;
    left: 0;
    top: 0;
    z-index: 99;
    background: #fff;
    border-right: 1px solid #e5e7eb;
    width: 256px;
    height: 100vh;
    display: flex;
    flex-direction: column;
    transform: translateX(-100%);
    transition: transform 0.3s ease-in-out;
  }
  @media (min-width: 1024px) {
    .bs-sb-aside { transform: translateX(0); }
  }
  .bs-sb-aside.bs-open { transform: translateX(0); }
  .bs-sb-overlay {
    position: fixed;
    inset: 0;
    z-index: 89;
    background: rgba(0, 0, 0, 0.5);
  }
  @media (min-width: 1024px) {
    .bs-sb-overlay { display: none !important; }
  }
  .bs-sb-brand {
    display: block;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid #e5e7eb;
    text-decoration: none;
    flex-shrink: 0;
  }
  .bs-sb-brand img {
    height: 48px;
    filter: invert(1);
    display: block;
  }
  .bs-sb-profile {
    padding: 1rem 1.25rem;
    border-bottom: 1px solid #e5e7eb;
  }
  .bs-sb-profile .flex {
    display: flex;
  }
  .bs-sb-profile .flex-col {
    flex-direction: column;
  }
  .bs-sb-profile .text-sm {
    font-size: 0.875rem;
    margin: 0;
  }
  .bs-sb-profile .text-xs {
    font-size: 0.75rem;
    margin: 0;
  }
  .bs-sb-profile .font-semibold {
    font-weight: 600;
    margin: 0;
  }
  .bs-sb-nav {
    flex: 1;
    overflow-y: auto;
    padding: 0.75rem 0.5rem;
  }
  .bs-sb-glabel {
    padding: 0.25rem 0.5rem;
    font-size: 0.65rem;
    font-weight: 600;
    letter-spacing: 0;
    text-transform: uppercase;
    color: #9ca3af;
    margin-bottom: 0.25rem;
    display: block;
    line-height: 1.4;
  }
  .bs-sb-link {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0.5rem 0.75rem;
    border-radius: 0.5rem;
    font-size: 0.8125rem;
    font-weight: 500;
    color: #4b5563;
    text-decoration: none;
    cursor: pointer;
    background: none;
    border: none;
    width: 100%;
    text-align: left;
    transition: background 0.15s, color 0.15s;
  }
  .bs-sb-link:hover {
    background: #f3f4f6;
    color: #111827;
  }
  .bs-sb-link.bs-active {
    background: #dbeafe;
    color: #2563eb;
  }
  .bs-sb-arr {
    margin-left: auto;
    transition: transform 0.2s;
    width: 14px;
    height: 14px;
    flex-shrink: 0;
  }
  .bs-sb-ico {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
  }
  .bs-sb-bot {
    border-top: 1px solid #e5e7eb;
    padding: 0.5rem;
    flex-shrink: 0;
  }
  .bs-sb-empty {
    padding: 2rem 1rem;
    text-align: center;
    color: #9ca3af;
    font-size: 0.8rem;
  }
  /* ── fly-out panels ─────────────────────────────────────────────── */
  .bs-sb-panel {
    position: fixed;
    top: 0;
    bottom: 0;
    width: 256px;
    overflow-y: auto;
    background: #fff;
    border-right: 1px solid #e5e7eb;
    padding: 1.5rem 1.25rem;
    box-shadow: 4px 0 16px rgba(0, 0, 0, 0.08);
  }
  .bs-sb-panel-title {
    font-size: 1.05rem;
    font-weight: 600;
    color: #111827;
    margin: 0;
  }
  .bs-sb-panel-close {
    background: none;
    border: none;
    cursor: pointer;
    padding: 0.25rem;
    border-radius: 0.375rem;
    color: #6b7280;
    line-height: 0;
    transition: background 0.15s;
  }
  .bs-sb-panel-close:hover {
    background: #f3f4f6;
  }
  /* panel slide-in animation */
  @keyframes bs-slide-in {
    from { opacity: 0; transform: translateX(12px); }
    to   { opacity: 1; transform: translateX(0); }
  }
  .bs-sb-panel {
    animation: bs-slide-in 0.25s ease;
  }
</style>

<div x-data="sidebarNavigation()" @click.window="handlePanelOutsideClick($event)">

  <!-- Mobile backdrop overlay -->
  <div
    x-show="$store.sidebarMobile.open"
    @click="$store.sidebarMobile.open = false; activeIndex = null; activeChildIndex = null"
    class="bs-sb-overlay"
    style="display: none"
  ></div>

  {{-- ─── Main Sidebar (level 1) ──────────────────────────────────── --}}
  <aside data-sidebar-area class="bs-sb-aside" :class="{'bs-open': $store.sidebarMobile.open}">
    {{-- Brand --}}
    <a href="#" class="bs-sb-brand">
      <img
        src="https://aviasihub-dev.kemenhub.go.id/_next/static/media/aviasihub-logo.eb7d0138.png"
        alt="Logo"
      />
    </a>

    <div
      x-show="groups.length > 0"
      x-transition:enter="transition duration-200 ease-out"
      x-transition:enter-start="-translate-y-1 opacity-0"
      x-transition:enter-end="translate-y-0 opacity-100"
      x-cloak
      class="bs-sb-profile"
    >
      <div class="flex flex-col dark:text-gray-300">
        <p class="text-sm font-semibold">Unit Kerja</p>
        <br />
        <p class="text-xs">Nama Pegawai</p>
        <p class="text-xs">NIP. 199901012009031001</p>
      </div>
    </div>

    {{-- Navigation --}}
    <nav class="bs-sb-nav">

      {{-- Empty state --}}
      <div x-show="groups.length === 0" class="bs-sb-empty">
        <svg xmlns="http://www.w3.org/2000/svg" width="36" height="36" fill="none"
             viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5"
             style="margin: 0 auto 0.5rem; display:block; opacity:.4">
          <path stroke-linecap="round" stroke-linejoin="round"
                d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z"/>
        </svg>
        Pilih filter untuk menampilkan menu
      </div>

      {{-- Dynamic menu groups --}}
      <template x-for="(group, gi) in groups" :key="gi">
        <div style="margin-bottom: 0.75rem">
          <template x-if="group.label">
            <span class="bs-sb-glabel" x-text="group.label"></span>
          </template>
          <ul class="list-unstyled mb-0">
            <template x-for="item in group.items" :key="item._idx">
              <li>
                {{-- Parent item with children → opens fly-out panel --}}
                <button
                  type="button"
                  class="bs-sb-link"
                  :class="{ 'bs-active': activeIndex === item._idx }"
                  @click="selectItem(item._idx)"
                  x-show="hasChildren(item)"
                >
                  <span x-text="item.label" style="flex:1; text-align:left"></span>
                  <svg class="bs-sb-arr" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
                       :style="activeIndex === item._idx ? 'transform:rotate(90deg)' : ''">
                    <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5"/>
                  </svg>
                </button>

                {{-- Leaf item (no children) --}}
                <a
                  :href="item.href ?? '#'"
                  class="bs-sb-link"
                  :class="{ 'bs-active': activeIndex === item._idx }"
                  x-show="!hasChildren(item)"
                >
                  <span x-text="item.label"></span>
                </a>
              </li>
            </template>
          </ul>
        </div>
      </template>
    </nav>

    {{-- Bottom: Settings & Logout --}}
    <div class="bs-sb-bot">
      <a href="#" class="bs-sb-link">
        <svg class="bs-sb-ico" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round"
                d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 010 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 010-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28z"/>
          <path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
        </svg>
        Pengaturan
      </a>
      <a href="#" class="bs-sb-link">
        <svg class="bs-sb-ico" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round"
                d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75"/>
        </svg>
        Kembali
      </a>
    </div>
  </aside>

  {{-- ─── Sub Module Panel (level 2) — muncul di KANAN sidebar ──── --}}
  <div
    data-sidebar-area
    x-cloak
    x-show="activeIndex !== null && ($store.sidebarMobile.open || window.innerWidth >= 1024)"
    x-transition:enter="transition duration-300 ease-out"
    x-transition:enter-start="translate-x-4 opacity-0"
    x-transition:enter-end="translate-x-0 opacity-100"
    x-transition:leave="transition duration-200 ease-in"
    x-transition:leave-start="translate-x-0 opacity-100"
    x-transition:leave-end="translate-x-4 opacity-0"
    class="bs-sb-panel"
    style="left: 256px; z-index: 98;"
  >
    <div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 1.25rem;">
      <h3 class="bs-sb-panel-title" x-text="activeItem?.label ?? 'Sub Modul'"></h3>
      <button
        type="button"
        class="bs-sb-panel-close"
        @click="activeIndex = null; activeChildIndex = null"
      >
        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
             stroke-width="1.5" stroke="currentColor" style="width:18px; height:18px;">
          <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
        </svg>
      </button>
    </div>

    <ul class="list-unstyled mb-0">
      <template x-if="activeItem && activeItem.children">
        <template x-for="(child, ci) in activeItem.children" :key="ci">
          <li>
            {{-- Child with grandchildren → opens level-3 panel --}}
            <button
              type="button"
              class="bs-sb-link"
              :class="{ 'bs-active': activeChildIndex === ci }"
              @click="selectChild(ci)"
              x-show="hasChildren(child)"
            >
              <span x-text="child.label" style="flex:1; text-align:left"></span>
              <svg class="bs-sb-arr" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
                   :style="activeChildIndex === ci ? 'transform:rotate(90deg)' : ''">
                <path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5"/>
              </svg>
            </button>

            {{-- Leaf child --}}
            <a
              :href="child.href ?? '#'"
              class="bs-sb-link"
              :class="{ 'bs-active': activeChildIndex === ci }"
              x-show="!hasChildren(child)"
            >
              <span x-text="child.label"></span>
            </a>
          </li>
        </template>
      </template>
    </ul>
  </div>

  {{-- ─── Sub-Sub Module Panel (level 3) — muncul lebih ke kanan ── --}}
  <div
    data-sidebar-area
    x-cloak
    x-show="activeChildIndex !== null && ($store.sidebarMobile.open || window.innerWidth >= 1024)"
    x-transition:enter="transition duration-300 ease-out"
    x-transition:enter-start="translate-x-4 opacity-0"
    x-transition:enter-end="translate-x-0 opacity-100"
    x-transition:leave="transition duration-200 ease-in"
    x-transition:leave-start="translate-x-0 opacity-100"
    x-transition:leave-end="translate-x-4 opacity-0"
    class="bs-sb-panel"
    style="left: 512px; z-index: 97;"
  >
    <div style="display:flex; align-items:center; justify-content:space-between; margin-bottom: 1.25rem;">
      <h3 class="bs-sb-panel-title" x-text="activeChild?.label ?? 'Sub-Sub Modul'"></h3>
      <button
        type="button"
        class="bs-sb-panel-close"
        @click="activeChildIndex = null"
      >
        <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
             stroke-width="1.5" stroke="currentColor" style="width:18px; height:18px;">
          <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
        </svg>
      </button>
    </div>

    <ul class="list-unstyled mb-0">
      <template x-if="activeChild && activeChild.children">
        <template x-for="(grandchild, i) in activeChild.children" :key="i">
          <li>
            <a
              :href="grandchild.href ?? '#'"
              class="bs-sb-link"
            >
              <span x-text="grandchild.label"></span>
            </a>
          </li>
        </template>
      </template>
    </ul>
  </div>

</div>
{{-- Sidebar — Vanilla CSS (Alpine-powered, dark-mode aware, fly-out panels) --}}
<style>
  *,
  *::before,
  *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }
  /* ── main sidebar ───────────────────────────────────────────────── */
  .vs-sb {
    position: fixed;
    left: 0;
    top: 0;
    width: 256px;
    height: 100vh;
    background: #fff;
    border-right: 1px solid #e5e7eb;
    display: flex;
    flex-direction: column;
    font-family: system-ui, sans-serif;
    z-index: 99;
    transform: translateX(-100%);
    transition: transform 0.3s ease-in-out;
  }
  @media (min-width: 1024px) {
    .vs-sb {
      transform: translateX(0);
    }
  }
  .vs-sb.vs-open {
    transform: translateX(0);
  }
  .vs-overlay {
    position: fixed;
    inset: 0;
    z-index: 89;
    background: rgba(0, 0, 0, 0.5);
  }
  @media (min-width: 1024px) {
    .vs-overlay {
      display: none !important;
    }
  }
  .vs-brand {
    display: block;
    padding: 1rem 1.25rem;
    border-bottom: 1px solid #e5e7eb;
    text-decoration: none;
    flex-shrink: 0;
  }
  .vs-brand img {
    height: 48px;
    filter: invert(1);
    display: block;
  }
  .vs-profile {
    padding: 1rem 1.25rem;
    border-bottom: 1px solid #e5e7eb;
  }
  .vs-profile .flex {
    display: flex;
  }
  .vs-profile .flex-col {
    flex-direction: column;
  }
  .vs-profile .text-sm {
    font-size: 0.875rem;
    line-height: 1.25rem;
  }
  .vs-profile .text-xs {
    font-size: 0.75rem;
    line-height: 1rem;
  }
  .vs-profile .font-semibold {
    font-weight: 600;
  }
  .vs-nav {
    flex: 1;
    padding: 0.75rem 0.5rem;
    overflow-y: auto;
  }
  .vs-glabel {
    padding: 0.25rem 0.5rem;
    font-size: 0.65rem;
    font-weight: 600;
    letter-spacing: 0;
    text-transform: uppercase;
    color: #9ca3af;
    margin-bottom: 0.25rem;
    display: block;
    line-height: 1.4;
  }
  .vs-group {
    margin-bottom: 0.75rem;
    list-style: none;
  }
  .vs-lnk {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    padding: 0.5rem 0.75rem;
    border-radius: 0.5rem;
    font-size: 0.8125rem;
    font-weight: 500;
    color: #4b5563;
    text-decoration: none;
    cursor: pointer;
    background: none;
    border: none;
    width: 100%;
    text-align: left;
    transition:
      background 0.15s,
      color 0.15s;
  }
  .vs-lnk:hover {
    background: #f3f4f6;
    color: #111827;
  }
  .vs-lnk.active {
    background: #dbeafe;
    color: #2563eb;
  }
  .vs-arr {
    margin-left: auto;
    transition: transform 0.2s;
    width: 14px;
    height: 14px;
    flex-shrink: 0;
  }
  .vs-ico {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
  }
  .vs-bot {
    border-top: 1px solid #e5e7eb;
    padding: 0.5rem;
    flex-shrink: 0;
  }
  .vs-empty {
    padding: 2rem 1rem;
    text-align: center;
    color: #9ca3af;
    font-size: 0.8rem;
  }
  /* ── fly-out panels ─────────────────────────────────────────────── */
  .vs-panel {
    position: fixed;
    top: 0;
    bottom: 0;
    width: 256px;
    overflow-y: auto;
    background: #fff;
    border-right: 1px solid #e5e7eb;
    padding: 1.5rem 1.25rem;
    box-shadow: 4px 0 16px rgba(0, 0, 0, 0.08);
    font-family: system-ui, sans-serif;
  }
  @keyframes vs-slide-in {
    from {
      opacity: 0;
      transform: translateX(12px);
    }
    to {
      opacity: 1;
      transform: translateX(0);
    }
  }
  .vs-panel {
    animation: vs-slide-in 0.25s ease;
  }
  .vs-panel-title {
    font-size: 1.05rem;
    font-weight: 600;
    color: #111827;
    margin: 0;
  }
  .vs-panel-close {
    background: none;
    border: none;
    cursor: pointer;
    padding: 0.25rem;
    border-radius: 0.375rem;
    color: #6b7280;
    line-height: 0;
    transition: background 0.15s;
  }
  .vs-panel-close:hover {
    background: #f3f4f6;
  }
  .vs-panel-list {
    list-style: none;
  }

  /* ── dark mode ─────────────────────────────────────────────────── */
  html.dark .vs-sb {
    background: #0f172a;
    border-color: #334155;
  }
  html.dark .vs-brand {
    border-color: #334155;
  }
  html.dark .vs-profile {
    border-color: #334155;
  }
  html.dark .vs-profile .dark\:text-gray-300 {
    color: #d1d5db;
  }
  html.dark .vs-brand img {
    filter: none;
  }
  html.dark .vs-glabel {
    color: #64748b;
  }
  html.dark .vs-lnk {
    color: #cbd5e1;
  }
  html.dark .vs-lnk:hover {
    background: #1e293b;
    color: #f1f5f9;
  }
  html.dark .vs-lnk.active {
    background: rgba(37, 99, 235, 0.2);
    color: #93c5fd;
  }
  html.dark .vs-bot {
    border-color: #334155;
  }
  html.dark .vs-empty {
    color: #475569;
  }
  html.dark .vs-panel {
    background: #0f172a;
    border-color: #334155;
  }
  html.dark .vs-panel-title {
    color: #f8fafc;
  }
  html.dark .vs-panel-close {
    color: #94a3b8;
  }
  html.dark .vs-panel-close:hover {
    background: #1e293b;
  }
</style>

<div x-data="sidebarNavigation()" @click.window="handlePanelOutsideClick($event)">
  <!-- Mobile backdrop overlay -->
  <div
    x-show="$store.sidebarMobile.open"
    @click="$store.sidebarMobile.open = false; activeIndex = null; activeChildIndex = null"
    class="vs-overlay"
    style="display: none"
  ></div>

  {{-- ─── Main Sidebar (level 1) ──────────────────────────────────── --}}
  <aside data-sidebar-area class="vs-sb" :class="{'vs-open': $store.sidebarMobile.open}">
    {{-- Brand --}}
    <a href="#" class="vs-brand">
      <img
        src="https://aviasihub-dev.kemenhub.go.id/_next/static/media/aviasihub-logo.eb7d0138.png"
        alt="Logo"
      />
    </a>

    <div
      x-show="Array.isArray(groups) ? groups.length > 0 : true"
      x-transition:enter="transition duration-200 ease-out"
      x-transition:enter-start="-translate-y-1 opacity-0"
      x-transition:enter-end="translate-y-0 opacity-100"
      x-cloak
      class="vs-profile"
    >
      <div class="flex flex-col dark:text-gray-300">
        <p class="text-sm font-semibold">Unit Kerja</p>
        <br />
        <p class="text-xs">Nama Pegawai</p>
        <p class="text-xs">NIP. 199901012009031001</p>
      </div>
    </div>

    {{-- Navigation --}}
    <nav class="vs-nav">
      {{-- Empty state --}}
      <div x-show="groups.length === 0" class="vs-empty">
        <svg
          xmlns="http://www.w3.org/2000/svg"
          width="36"
          height="36"
          fill="none"
          viewBox="0 0 24 24"
          stroke="currentColor"
          stroke-width="1.5"
          style="margin: 0 auto 0.5rem; display: block; opacity: 0.4"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M3.75 6A2.25 2.25 0 016 3.75h2.25A2.25 2.25 0 0110.5 6v2.25a2.25 2.25 0 01-2.25 2.25H6a2.25 2.25 0 01-2.25-2.25V6zM3.75 15.75A2.25 2.25 0 016 13.5h2.25a2.25 2.25 0 012.25 2.25V18a2.25 2.25 0 01-2.25 2.25H6A2.25 2.25 0 013.75 18v-2.25zM13.5 6a2.25 2.25 0 012.25-2.25H18A2.25 2.25 0 0120.25 6v2.25A2.25 2.25 0 0118 10.5h-2.25a2.25 2.25 0 01-2.25-2.25V6zM13.5 15.75a2.25 2.25 0 012.25-2.25H18a2.25 2.25 0 012.25 2.25V18A2.25 2.25 0 0118 20.25h-2.25A2.25 2.25 0 0113.5 18v-2.25z"
          />
        </svg>
        Pilih filter untuk menampilkan menu
      </div>

      {{-- Dynamic menu groups --}}
      <template x-for="(group, gi) in groups" :key="gi">
        <div style="margin-bottom: 0.75rem">
          <template x-if="group.label">
            <span class="vs-glabel" x-text="group.label"></span>
          </template>
          <ul class="vs-group">
            <template x-for="item in group.items" :key="item._idx">
              <li>
                {{-- Parent item with children → opens fly-out panel --}}
                <button
                  type="button"
                  class="vs-lnk"
                  :class="{ 'active': activeIndex === item._idx }"
                  @click="selectItem(item._idx)"
                  x-show="hasChildren(item)"
                >
                  <span x-text="item.label" style="flex: 1; text-align: left"></span>
                  <svg
                    class="vs-arr"
                    fill="none"
                    viewBox="0 0 24 24"
                    stroke-width="1.5"
                    stroke="currentColor"
                    :style="activeIndex === item._idx ? 'transform:rotate(90deg)' : ''"
                  >
                    <path
                      stroke-linecap="round"
                      stroke-linejoin="round"
                      d="M8.25 4.5l7.5 7.5-7.5 7.5"
                    />
                  </svg>
                </button>

                {{-- Leaf item (no children) --}}
                <a :href="item.href ?? '#'" class="vs-lnk" x-show="! hasChildren(item)">
                  <span x-text="item.label"></span>
                </a>
              </li>
            </template>
          </ul>
        </div>
      </template>
    </nav>

    {{-- Bottom: Settings & Logout --}}
    <div class="vs-bot">
      <a href="#" class="vs-lnk">
        <svg
          class="vs-ico"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="1.5"
          stroke="currentColor"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 010 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 010-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28z"
          />
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
          />
        </svg>
        Pengaturan
      </a>
      <a href="#" class="vs-lnk">
        <svg
          class="vs-ico"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="1.5"
          stroke="currentColor"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M15.75 9V5.25A2.25 2.25 0 0013.5 3h-6a2.25 2.25 0 00-2.25 2.25v13.5A2.25 2.25 0 007.5 21h6a2.25 2.25 0 002.25-2.25V15M12 9l-3 3m0 0l3 3m-3-3h12.75"
          />
        </svg>
        Kembali
      </a>
    </div>
  </aside>

  {{-- ─── Sub Module Panel (level 2) — muncul di KANAN sidebar ──── --}}
  <div
    data-sidebar-area
    x-cloak
    x-show="activeIndex !== null && ($store.sidebarMobile.open || window.innerWidth >= 1024)"
    class="vs-panel"
    style="left: 256px; z-index: 98"
  >
    <div
      style="
        display: flex;
        align-items: center;
        justify-content: space-between;
        margin-bottom: 1.25rem;
      "
    >
      <h3 class="vs-panel-title" x-text="activeItem?.label ?? 'Sub Modul'"></h3>
      <button
        type="button"
        class="vs-panel-close"
        @click="activeIndex = null; activeChildIndex = null"
      >
        <svg
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="1.5"
          stroke="currentColor"
          style="width: 18px; height: 18px"
        >
          <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
        </svg>
      </button>
    </div>

    <ul class="vs-panel-list">
      <template x-if="activeItem && activeItem.children">
        <template x-for="(child, ci) in activeItem.children" :key="ci">
          <li>
            {{-- Child with grandchildren → opens level-3 panel --}}
            <button
              type="button"
              class="vs-lnk"
              :class="{ 'active': activeChildIndex === ci }"
              @click="selectChild(ci)"
              x-show="hasChildren(child)"
            >
              <span x-text="child.label" style="flex: 1; text-align: left"></span>
              <svg
                class="vs-arr"
                fill="none"
                viewBox="0 0 24 24"
                stroke-width="1.5"
                stroke="currentColor"
                :style="activeChildIndex === ci ? 'transform:rotate(90deg)' : ''"
              >
                <path
                  stroke-linecap="round"
                  stroke-linejoin="round"
                  d="M8.25 4.5l7.5 7.5-7.5 7.5"
                />
              </svg>
            </button>

            {{-- Leaf child --}}
            <a :href="child.href ?? '#'" class="vs-lnk" x-show="! hasChildren(child)">
              <span x-text="child.label"></span>
            </a>
          </li>
        </template>
      </template>
    </ul>
  </div>

  {{-- ─── Sub-Sub Module Panel (level 3) — muncul lebih ke kanan ── --}}
  <div
    data-sidebar-area
    x-cloak
    x-show="
      activeChildIndex !== null &&
        ($store.sidebarMobile.open || window.innerWidth >= 1024)
    "
    class="vs-panel"
    style="left: 512px; z-index: 97"
  >
    <div
      style="
        display: flex;
        align-items: center;
        justify-content: space-between;
        margin-bottom: 1.25rem;
      "
    >
      <h3 class="vs-panel-title" x-text="activeChild?.label ?? 'Sub-Sub Modul'"></h3>
      <button type="button" class="vs-panel-close" @click="activeChildIndex = null">
        <svg
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="1.5"
          stroke="currentColor"
          style="width: 18px; height: 18px"
        >
          <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
        </svg>
      </button>
    </div>

    <ul class="vs-panel-list">
      <template x-if="activeChild && activeChild.children">
        <template x-for="(grandchild, i) in activeChild.children" :key="i">
          <li>
            <a :href="grandchild.href ?? '#'" class="vs-lnk">
              <span x-text="grandchild.label"></span>
            </a>
          </li>
        </template>
      </template>
    </ul>
  </div>
</div>

Footer

Tailwind CSS Bootstrap 5 Vanilla CSS

Footer di bagian bawah halaman dengan layout responsif dan dukungan dark mode. fixed

Card

Tailwind CSS Bootstrap 5 Vanilla CSS

Komponen card dengan gambar, badge, judul, deskripsi, meta, dan tombol aksi. Responsif dan mendukung dark mode.

{{--
  Card Skeleton Component
--}}

<div class="flex flex-col gap-4">
  {{-- Row 1: --}}
  <div class="flex gap-3">
    <div
      class="flex h-70 flex-1 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-800"
    >
      <span class="text-xs text-gray-400 dark:text-gray-500">Page content goes here</span>
    </div>
    <div
      class="flex h-70 flex-1 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-800"
    >
      <span class="text-xs text-gray-400 dark:text-gray-500">Page content goes here</span>
    </div>
  </div>

  {{-- Row 2: --}}
  <div class="flex gap-3">
    <div
      class="flex h-40 flex-1 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-800"
    >
      <span class="text-xs text-gray-400 dark:text-gray-500">Page content goes here</span>
    </div>
    <div
      class="flex h-40 flex-1 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-800"
    >
      <span class="text-xs text-gray-400 dark:text-gray-500">Page content goes here</span>
    </div>
    <div
      class="flex h-40 flex-1 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-800"
    >
      <span class="text-xs text-gray-400 dark:text-gray-500">Page content goes here</span>
    </div>
  </div>

  {{-- Row 3: --}}
  <div class="flex gap-3">
    <div
      class="flex h-70 flex-1 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-800"
    >
      <span class="text-xs text-gray-400 dark:text-gray-500">Page content goes here</span>
    </div>
    <div
      class="flex h-70 flex-1 items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-800"
    >
      <span class="text-xs text-gray-400 dark:text-gray-500">Page content goes here</span>
    </div>
  </div>

  {{-- Row 4: --}}
  <div
    class="flex h-100 w-full items-center justify-center rounded-lg bg-gray-100 dark:bg-gray-800"
  >
    <span class="text-xs text-gray-400 dark:text-gray-500">Page content goes here</span>
  </div>
</div>
{{--
  Card Skeleton Component — Bootstrap 5
  Layout: Row1(2) / Row2(3) / Row3(2) / Row4(1)
  Mendukung dark & light mode (class="dark" on <html>).
--}}

<style>
  .bs-sk-box {
    background: #f3f4f6; /* gray-100 */
  }
  .bs-sk-box span {
    font-size: .75rem;
    color: #9ca3af; /* gray-400 */
  }
  /* dark mode — project toggles class="dark" on <html> */
  .dark .bs-sk-box       { background: #1f2937; /* gray-800 */ }
  .dark .bs-sk-box span  { color: #6b7280;      /* gray-500 */ }
</style>

<div class="d-flex flex-column gap-3">

  {{-- Row 1: [  ] [  ] --}}
  <div class="d-flex gap-3">
    <div class="flex-fill rounded-3 bs-sk-box d-flex align-items-center justify-content-center" style="height:280px">
      <span>Page content goes here</span>
    </div>
    <div class="flex-fill rounded-3 bs-sk-box d-flex align-items-center justify-content-center" style="height:280px">
      <span>Page content goes here</span>
    </div>
  </div>

  {{-- Row 2: [] [] [] --}}
  <div class="d-flex gap-3">
    <div class="flex-fill rounded-3 bs-sk-box d-flex align-items-center justify-content-center" style="height:160px">
      <span>Page content goes here</span>
    </div>
    <div class="flex-fill rounded-3 bs-sk-box d-flex align-items-center justify-content-center" style="height:160px">
      <span>Page content goes here</span>
    </div>
    <div class="flex-fill rounded-3 bs-sk-box d-flex align-items-center justify-content-center" style="height:160px">
      <span>Page content goes here</span>
    </div>
  </div>

  {{-- Row 3: [  ] [  ] --}}
  <div class="d-flex gap-3">
    <div class="flex-fill rounded-3 bs-sk-box d-flex align-items-center justify-content-center" style="height:280px">
      <span>Page content goes here</span>
    </div>
    <div class="flex-fill rounded-3 bs-sk-box d-flex align-items-center justify-content-center" style="height:280px">
      <span>Page content goes here</span>
    </div>
  </div>

  {{-- Row 4: [        ] --}}
  <div class="w-100 rounded-3 bs-sk-box d-flex align-items-center justify-content-center" style="height:400px">
    <span>Page content goes here</span>
  </div>

</div>
{{--
  Card Skeleton Component — Vanilla CSS
  Layout: Row1(2) / Row2(3) / Row3(2) / Row4(1)
  Mendukung dark & light mode (class="dark" on <html>).
--}}

<style>
  .vc-wrap {
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
  }
  .vc-row {
    display: flex;
    gap: 0.75rem;
  }
  .vc-box {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 0.5rem;
    background: #f3f4f6; /* gray-100 */
  }
  .vc-box span {
    font-size: 0.75rem;
    color: #9ca3af; /* gray-400 */
  }

  /* dark mode — project toggles class="dark" on <html> */
  .dark .vc-box {
    background: #1f2937; /* gray-800 */
  }
  .dark .vc-box span {
    color: #6b7280; /* gray-500 */
  }
</style>

<div class="vc-wrap">
  {{-- Row 1: [  ] [  ] --}}
  <div class="vc-row">
    <div class="vc-box" style="height: 280px"><span>Page content goes here</span></div>
    <div class="vc-box" style="height: 280px"><span>Page content goes here</span></div>
  </div>

  {{-- Row 2: [] [] [] --}}
  <div class="vc-row">
    <div class="vc-box" style="height: 160px"><span>Page content goes here</span></div>
    <div class="vc-box" style="height: 160px"><span>Page content goes here</span></div>
    <div class="vc-box" style="height: 160px"><span>Page content goes here</span></div>
  </div>

  {{-- Row 3: [  ] [  ] --}}
  <div class="vc-row">
    <div class="vc-box" style="height: 280px"><span>Page content goes here</span></div>
    <div class="vc-box" style="height: 280px"><span>Page content goes here</span></div>
  </div>

  {{-- Row 4: [        ] --}}
  <div class="vc-row">
    <div class="vc-box" style="height: 400px"><span>Page content goes here</span></div>
  </div>
</div>

Table

Tailwind CSS Bootstrap 5 Vanilla CSS

Tabel dengan badge role & status, pencarian real-time, dan tombol aksi. Responsif dan mendukung dark mode.

{{-- User Table — Tailwind CSS + AlpineJS --}}
<div
  x-data="{
    search: '',
    users: [
      {
        id: 1,
        name: 'Ahmad Fauzi',
        email: 'ahmad.fauzi@example.com',
        role: 'Admin',
        status: 'active',
        joined: '12 Jan 2024',
      },
      {
        id: 2,
        name: 'Siti Rahayu',
        email: 'siti.rahayu@example.com',
        role: 'Editor',
        status: 'active',
        joined: '03 Mar 2024',
      },
      {
        id: 3,
        name: 'Budi Santoso',
        email: 'budi.santoso@example.com',
        role: 'Viewer',
        status: 'inactive',
        joined: '28 Jun 2023',
      },
      {
        id: 4,
        name: 'Dewi Lestari',
        email: 'dewi.lestari@example.com',
        role: 'Editor',
        status: 'active',
        joined: '15 Sep 2023',
      },
      {
        id: 5,
        name: 'Riko Prasetyo',
        email: 'riko.prasetyo@example.com',
        role: 'Viewer',
        status: 'pending',
        joined: '07 Nov 2024',
      },
      {
        id: 6,
        name: 'Nurul Hidayah',
        email: 'nurul.hidayah@example.com',
        role: 'Admin',
        status: 'active',
        joined: '20 Feb 2024',
      },
      {
        id: 7,
        name: 'Yusuf Hakim',
        email: 'yusuf.hakim@example.com',
        role: 'Viewer',
        status: 'inactive',
        joined: '01 Apr 2023',
      },
    ],
    get filtered() {
      if (! this.search.trim()) return this.users
      const q = this.search.toLowerCase()
      return this.users.filter(
        (u) =>
          u.name.toLowerCase().includes(q) ||
          u.email.toLowerCase().includes(q) ||
          u.role.toLowerCase().includes(q),
      )
    },
    roleColor(role) {
      return (
        {
          Admin:
            'bg-blue-50 text-blue-700 ring-blue-600/20 dark:bg-blue-500/10 dark:text-blue-400 dark:ring-blue-500/20',
          Editor:
            'bg-gray-100 text-gray-700 ring-gray-400/30 dark:bg-gray-500/10 dark:text-gray-400 dark:ring-gray-500/20',
          Viewer:
            'bg-gray-50 text-gray-500 ring-gray-300/60 dark:bg-gray-500/5 dark:text-gray-500 dark:ring-gray-600/20',
        }[role] ?? ''
      )
    },
    statusColor(status) {
      return (
        {
          active:
            'bg-green-50 text-green-700 ring-green-600/20 dark:bg-green-500/10 dark:text-green-400 dark:ring-green-500/20',
          inactive:
            'bg-rose-50 text-rose-700 ring-rose-600/20 dark:bg-rose-500/10 dark:text-rose-400 dark:ring-rose-500/20',
          pending:
            'bg-orange-50 text-orange-700 ring-orange-600/20 dark:bg-orange-500/10 dark:text-orange-400 dark:ring-orange-500/20',
        }[status] ?? ''
      )
    },
  }"
  class="w-full"
>
  {{-- Toolbar --}}
  <div class="mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
    <div>
      <h3 class="text-base font-semibold text-gray-900 dark:text-white">Daftar Pengguna</h3>
      <p class="text-xs text-gray-500 dark:text-gray-400">
        Menampilkan
        <span x-text="filtered.length"></span>
        dari
        <span x-text="users.length"></span>
        pengguna
      </p>
    </div>

    <div class="flex items-center gap-2">
      {{-- Search --}}
      <div class="relative">
        <svg
          class="pointer-events-none absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-gray-400"
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="1.5"
          stroke="currentColor"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 15.803 7.5 7.5 0 0015.803 15.803z"
          />
        </svg>
        <input
          x-model="search"
          type="text"
          placeholder="Cari pengguna…"
          class="rounded-lg border border-gray-200 bg-white py-1.5 pr-4 pl-9 text-sm text-gray-900 placeholder-gray-400 outline-none focus:border-blue-400 focus:ring-2 focus:ring-blue-400/20 dark:border-white/10 dark:bg-gray-800 dark:text-white dark:placeholder-gray-500 dark:focus:border-blue-500"
        />
      </div>

      {{-- Add button --}}
      <button
        class="inline-flex items-center gap-1.5 rounded-lg bg-blue-600 px-3 py-1.5 text-sm font-medium text-white transition-colors hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600"
      >
        <svg
          class="h-4 w-4"
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="2"
          stroke="currentColor"
        >
          <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
        </svg>
        Tambah
      </button>
    </div>
  </div>

  {{-- Table --}}
  <div class="overflow-hidden rounded-xl border border-gray-200/80 shadow-xs dark:border-white/10">
    <div class="overflow-x-auto">
      <table class="min-w-full divide-y divide-gray-200 dark:divide-white/10">
        <thead class="bg-gray-50 dark:bg-gray-800/60">
          <tr>
            <th
              class="py-3 pr-3 pl-4 text-left text-xs font-semibold tracking-wide text-gray-500 uppercase sm:pl-6 dark:text-gray-400"
            >
              Pengguna
            </th>
            <th
              class="hidden px-3 py-3 text-left text-xs font-semibold tracking-wide text-gray-500 uppercase sm:table-cell dark:text-gray-400"
            >
              Email
            </th>
            <th
              class="px-3 py-3 text-left text-xs font-semibold tracking-wide text-gray-500 uppercase dark:text-gray-400"
            >
              Role
            </th>
            <th
              class="px-3 py-3 text-left text-xs font-semibold tracking-wide text-gray-500 uppercase dark:text-gray-400"
            >
              Status
            </th>
            <th
              class="hidden px-3 py-3 text-left text-xs font-semibold tracking-wide text-gray-500 uppercase lg:table-cell dark:text-gray-400"
            >
              Bergabung
            </th>
            <th class="relative py-3 pr-4 pl-3 sm:pr-6">
              <span class="sr-only">Aksi</span>
            </th>
          </tr>
        </thead>

        <tbody class="divide-y divide-gray-100 bg-white dark:divide-white/5 dark:bg-gray-900">
          <template x-if="filtered.length === 0">
            <tr>
              <td colspan="6" class="py-10 text-center text-sm text-gray-400 dark:text-gray-600">
                Tidak ada pengguna ditemukan.
              </td>
            </tr>
          </template>

          <template x-for="user in filtered" :key="user.id">
            <tr class="transition-colors hover:bg-gray-50/60 dark:hover:bg-white/3">
              {{-- Name --}}
              <td class="py-3 pr-3 pl-4 sm:pl-6">
                <span
                  class="text-sm font-medium text-gray-900 dark:text-white"
                  x-text="user.name"
                ></span>
              </td>

              {{-- Email --}}
              <td class="hidden px-3 py-3 sm:table-cell">
                <span
                  class="text-sm text-gray-500 dark:text-gray-400"
                  x-text="user.email"
                ></span>
              </td>

              {{-- Role --}}
              <td class="px-3 py-3">
                <span
                  :class="roleColor(user.role)"
                  class="inline-flex items-center rounded-md px-2 py-0.5 text-[11px] font-medium ring-1 ring-inset"
                  x-text="user.role"
                ></span>
              </td>

              {{-- Status --}}
              <td class="px-3 py-3">
                <span
                  :class="statusColor(user.status)"
                  class="inline-flex items-center gap-1 rounded-md px-2 py-0.5 text-[11px] font-medium capitalize ring-1 ring-inset"
                  x-text="user.status"
                ></span>
              </td>

              {{-- Joined --}}
              <td class="hidden px-3 py-3 lg:table-cell">
                <span
                  class="text-sm text-gray-500 dark:text-gray-400"
                  x-text="user.joined"
                ></span>
              </td>

              {{-- Actions --}}
              <td class="py-3 pr-4 pl-3 text-right text-sm sm:pr-6">
                <div class="flex justify-end gap-2">
                  <button
                    class="rounded-md p-1.5 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-white/10 dark:hover:text-gray-300"
                    title="Edit"
                  >
                    <svg
                      class="h-4 w-4"
                      xmlns="http://www.w3.org/2000/svg"
                      fill="none"
                      viewBox="0 0 24 24"
                      stroke-width="1.5"
                      stroke="currentColor"
                    >
                      <path
                        stroke-linecap="round"
                        stroke-linejoin="round"
                        d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125"
                      />
                    </svg>
                  </button>
                  <button
                    class="rounded-md p-1.5 text-gray-400 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-500/10 dark:hover:text-red-400"
                    title="Hapus"
                  >
                    <svg
                      class="h-4 w-4"
                      xmlns="http://www.w3.org/2000/svg"
                      fill="none"
                      viewBox="0 0 24 24"
                      stroke-width="1.5"
                      stroke="currentColor"
                    >
                      <path
                        stroke-linecap="round"
                        stroke-linejoin="round"
                        d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
                      />
                    </svg>
                  </button>
                </div>
              </td>
            </tr>
          </template>
        </tbody>
      </table>
    </div>

    {{-- Pagination strip --}}
    <div
      class="flex items-center justify-between border-t border-gray-200/80 bg-white px-4 py-3 dark:border-white/5 dark:bg-gray-900"
    >
      <p class="text-xs text-gray-500 dark:text-gray-400">
        Menampilkan
        <span x-text="filtered.length"></span>
        pengguna
      </p>
      <div class="flex gap-1">
        <button
          class="rounded-md border border-gray-200 px-2.5 py-1 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-50 disabled:opacity-40 dark:border-white/10 dark:text-gray-400 dark:hover:bg-white/5"
          disabled
        >
          <svg
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            stroke-width="1.5"
            stroke="currentColor"
            class="size-3"
          >
            <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
          </svg>
        </button>
        <button
          class="rounded-md border border-blue-200 bg-blue-50 px-2.5 py-1 text-xs font-medium text-blue-600 dark:border-blue-500/30 dark:bg-blue-500/10 dark:text-blue-400"
        >
          1
        </button>
        <button
          class="rounded-md border border-gray-200 px-2.5 py-1 text-xs font-medium text-gray-600 transition-colors hover:bg-gray-50 disabled:opacity-40 dark:border-white/10 dark:text-gray-400 dark:hover:bg-white/5"
          disabled
        >
          <svg
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
            stroke-width="1.5"
            stroke="currentColor"
            class="size-3"
          >
            <path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
          </svg>
        </button>
      </div>
    </div>
  </div>
</div>
{{-- User Table — Bootstrap 5 --}}
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
/>

<div class="container-fluid py-4">
  <!-- Toolbar -->
  <div class="d-flex flex-wrap align-items-center justify-content-between gap-3 mb-3">
    <div>
      <h5 class="mb-0 fw-semibold">Daftar Pengguna</h5>
      <small class="text-muted">Menampilkan 7 dari 7 pengguna</small>
    </div>
    <div class="d-flex align-items-center gap-2">
      <div class="input-group input-group-sm" style="width: 220px;">
        <span class="input-group-text bg-white border-end-0">
          <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
            <path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 15.803 7.5 7.5 0 0015.803 15.803z"/>
          </svg>
        </span>
        <input type="text" class="form-control border-start-0" placeholder="Cari pengguna…" />
      </div>
      <button class="btn btn-primary btn-sm d-inline-flex align-items-center gap-1">
        <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
          <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/>
        </svg>
        Tambah
      </button>
    </div>
  </div>

  <!-- Table card -->
  <div class="card border-0 shadow-sm rounded-3 overflow-hidden">
    <div class="table-responsive">
      <table class="table table-hover align-middle mb-0">
        <thead class="table-light">
          <tr>
            <th class="ps-4 text-uppercase small fw-semibold text-secondary" style="font-size:11px;">Pengguna</th>
            <th class="text-uppercase small fw-semibold text-secondary d-none d-sm-table-cell" style="font-size:11px;">Email</th>
            <th class="text-uppercase small fw-semibold text-secondary" style="font-size:11px;">Role</th>
            <th class="text-uppercase small fw-semibold text-secondary" style="font-size:11px;">Status</th>
            <th class="text-uppercase small fw-semibold text-secondary d-none d-lg-table-cell" style="font-size:11px;">Bergabung</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td class="ps-4"><span class="fw-medium small">Ahmad Fauzi</span></td>
            <td class="small text-muted d-none d-sm-table-cell">ahmad.fauzi@example.com</td>
            <td><span class="badge text-bg-primary rounded-pill fw-normal">Admin</span></td>
            <td><span class="badge text-bg-success rounded-pill fw-normal">active</span></td>
            <td class="small text-muted d-none d-lg-table-cell">12 Jan 2024</td>
            <td class="text-end pe-4">
              <button class="btn btn-sm btn-outline-secondary me-1" title="Edit">
                <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125"/></svg>
              </button>
              <button class="btn btn-sm btn-outline-danger" title="Hapus">
                <svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/></svg>
              </button>
            </td>
          </tr>
          <tr>
            <td class="ps-4"><span class="fw-medium small">Siti Rahayu</span></td>
            <td class="small text-muted d-none d-sm-table-cell">siti.rahayu@example.com</td>
            <td><span class="badge text-bg-secondary rounded-pill fw-normal">Editor</span></td>
            <td><span class="badge text-bg-success rounded-pill fw-normal">active</span></td>
            <td class="small text-muted d-none d-lg-table-cell">03 Mar 2024</td>
            <td class="text-end pe-4">
              <button class="btn btn-sm btn-outline-secondary me-1" title="Edit"><svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125"/></svg></button>
              <button class="btn btn-sm btn-outline-danger" title="Hapus"><svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/></svg></button>
            </td>
          </tr>
          <tr>
            <td class="ps-4"><span class="fw-medium small">Budi Santoso</span></td>
            <td class="small text-muted d-none d-sm-table-cell">budi.santoso@example.com</td>
            <td><span class="badge text-bg-secondary rounded-pill fw-normal">Viewer</span></td>
            <td><span class="badge text-bg-danger rounded-pill fw-normal">inactive</span></td>
            <td class="small text-muted d-none d-lg-table-cell">28 Jun 2023</td>
            <td class="text-end pe-4">
              <button class="btn btn-sm btn-outline-secondary me-1" title="Edit"><svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125"/></svg></button>
              <button class="btn btn-sm btn-outline-danger" title="Hapus"><svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/></svg></button>
            </td>
          </tr>
          <tr>
            <td class="ps-4"><span class="fw-medium small">Dewi Lestari</span></td>
            <td class="small text-muted d-none d-sm-table-cell">dewi.lestari@example.com</td>
            <td><span class="badge text-bg-secondary rounded-pill fw-normal">Editor</span></td>
            <td><span class="badge text-bg-success rounded-pill fw-normal">active</span></td>
            <td class="small text-muted d-none d-lg-table-cell">15 Sep 2023</td>
            <td class="text-end pe-4">
              <button class="btn btn-sm btn-outline-secondary me-1" title="Edit"><svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125"/></svg></button>
              <button class="btn btn-sm btn-outline-danger" title="Hapus"><svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/></svg></button>
            </td>
          </tr>
          <tr>
            <td class="ps-4"><span class="fw-medium small">Riko Prasetyo</span></td>
            <td class="small text-muted d-none d-sm-table-cell">riko.prasetyo@example.com</td>
            <td><span class="badge text-bg-secondary rounded-pill fw-normal">Viewer</span></td>
            <td><span class="badge text-bg-warning rounded-pill fw-normal">pending</span></td>
            <td class="small text-muted d-none d-lg-table-cell">07 Nov 2024</td>
            <td class="text-end pe-4">
              <button class="btn btn-sm btn-outline-secondary me-1" title="Edit"><svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125"/></svg></button>
              <button class="btn btn-sm btn-outline-danger" title="Hapus"><svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/></svg></button>
            </td>
          </tr>
          <tr>
            <td class="ps-4"><span class="fw-medium small">Nurul Hidayah</span></td>
            <td class="small text-muted d-none d-sm-table-cell">nurul.hidayah@example.com</td>
            <td><span class="badge text-bg-primary rounded-pill fw-normal">Admin</span></td>
            <td><span class="badge text-bg-success rounded-pill fw-normal">active</span></td>
            <td class="small text-muted d-none d-lg-table-cell">20 Feb 2024</td>
            <td class="text-end pe-4">
              <button class="btn btn-sm btn-outline-secondary me-1" title="Edit"><svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125"/></svg></button>
              <button class="btn btn-sm btn-outline-danger" title="Hapus"><svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/></svg></button>
            </td>
          </tr>
          <tr>
            <td class="ps-4"><span class="fw-medium small">Yusuf Hakim</span></td>
            <td class="small text-muted d-none d-sm-table-cell">yusuf.hakim@example.com</td>
            <td><span class="badge text-bg-secondary rounded-pill fw-normal">Viewer</span></td>
            <td><span class="badge text-bg-danger rounded-pill fw-normal">inactive</span></td>
            <td class="small text-muted d-none d-lg-table-cell">01 Apr 2023</td>
            <td class="text-end pe-4">
              <button class="btn btn-sm btn-outline-secondary me-1" title="Edit"><svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125"/></svg></button>
              <button class="btn btn-sm btn-outline-danger" title="Hapus"><svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/></svg></button>
            </td>
          </tr>
        </tbody>
      </table>
    </div>

    <!-- Pagination strip -->
    <div class="d-flex align-items-center justify-content-between px-3 py-2 border-top bg-white">
      <small class="text-muted">Menampilkan 7 pengguna</small>
      <nav>
        <ul class="pagination pagination-sm mb-0">
          <li class="page-item disabled"><a class="page-link d-flex align-items-center" href="#"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5"/></svg></a></li>
          <li class="page-item active"><a class="page-link" href="#">1</a></li>
          <li class="page-item disabled"><a class="page-link d-flex align-items-center" href="#"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5"/></svg></a></li>
        </ul>
      </nav>
    </div>
  </div>
</div>


<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
{{-- User Table — Vanilla CSS --}}
<style>
  *,
  *::before,
  *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }

  body {
    font-family:
      system-ui,
      -apple-system,
      sans-serif;
    background: #f8f9fb;
    color: #111827;
  }

  .tbl-wrapper {
    padding: 24px;
  }

  /* Toolbar */
  .tbl-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 16px;
  }
  .tbl-title {
    font-size: 15px;
    font-weight: 600;
    color: #111827;
  }
  .tbl-subtitle {
    font-size: 12px;
    color: #6b7280;
    margin-top: 2px;
  }
  .tbl-actions {
    display: flex;
    align-items: center;
    gap: 8px;
  }

  /* Search */
  .search-wrap {
    position: relative;
  }
  .search-wrap svg {
    position: absolute;
    left: 10px;
    top: 50%;
    transform: translateY(-50%);
    width: 14px;
    height: 14px;
    color: #9ca3af;
    pointer-events: none;
  }
  .search-input {
    padding: 6px 12px 6px 32px;
    border: 1px solid #e5e7eb;
    border-radius: 8px;
    font-size: 13px;
    color: #111827;
    background: #fff;
    outline: none;
    width: 220px;
    transition:
      border-color 0.15s,
      box-shadow 0.15s;
  }
  .search-input:focus {
    border-color: #3b82f6;
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
  }

  .btn-add {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 14px;
    background: #2563eb;
    color: #fff;
    border: none;
    border-radius: 8px;
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.15s;
  }
  .btn-add:hover {
    background: #1d4ed8;
  }
  .btn-add svg {
    width: 14px;
    height: 14px;
  }

  /* Card */
  .tbl-card {
    background: #fff;
    border: 1px solid rgba(229, 231, 235, 0.8);
    border-radius: 12px;
    overflow: hidden;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
  }
  .tbl-scroll {
    overflow-x: auto;
  }

  table {
    width: 100%;
    border-collapse: collapse;
  }
  thead tr {
    background: #f9fafb;
    border-bottom: 1px solid #e5e7eb;
  }
  th {
    padding: 10px 12px;
    text-align: left;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: #6b7280;
    white-space: nowrap;
  }
  th:first-child {
    padding-left: 20px;
  }
  th:last-child {
    padding-right: 20px;
  }

  tbody tr {
    border-bottom: 1px solid #f3f4f6;
    transition: background 0.12s;
  }
  tbody tr:last-child {
    border-bottom: none;
  }
  tbody tr:hover {
    background: rgba(249, 250, 251, 0.6);
  }
  td {
    padding: 10px 12px;
    font-size: 13px;
    color: #374151;
    white-space: nowrap;
    vertical-align: middle;
  }
  td:first-child {
    padding-left: 20px;
  }
  td:last-child {
    padding-right: 20px;
    text-align: right;
  }

  .user-cell {
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .user-name {
    font-weight: 500;
    color: #111827;
  }

  .email-cell {
    color: #6b7280;
  }

  /* Badges */
  .badge {
    display: inline-flex;
    align-items: center;
    padding: 2px 8px;
    border-radius: 9999px;
    font-size: 11px;
    font-weight: 500;
    border: 1px solid transparent;
  }
  .badge-admin {
    background: #eff6ff;
    color: #1d4ed8;
    border-color: rgba(29, 78, 216, 0.2);
  }
  .badge-editor {
    background: #f3f4f6;
    color: #374151;
    border-color: rgba(107, 114, 128, 0.25);
  }
  .badge-viewer {
    background: #f9fafb;
    color: #4b5563;
    border-color: rgba(75, 85, 99, 0.2);
  }
  .badge-active {
    background: #f0fdf4;
    color: #15803d;
    border-color: rgba(21, 128, 61, 0.2);
  }
  .badge-inactive {
    background: #fff1f2;
    color: #be123c;
    border-color: rgba(190, 18, 60, 0.2);
  }
  .badge-pending {
    background: #fff7ed;
    color: #c2410c;
    border-color: rgba(194, 65, 12, 0.2);
  }

  /* Action buttons */
  .btn-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border: none;
    border-radius: 6px;
    background: transparent;
    color: #9ca3af;
    cursor: pointer;
    transition:
      background 0.15s,
      color 0.15s;
  }
  .btn-icon:hover {
    background: #f3f4f6;
    color: #374151;
  }
  .btn-icon.danger:hover {
    background: #fef2f2;
    color: #dc2626;
  }
  .btn-icon svg {
    width: 14px;
    height: 14px;
  }
  .btn-group {
    display: flex;
    justify-content: flex-end;
    gap: 4px;
  }

  /* Pagination */
  .tbl-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 20px;
    border-top: 1px solid #e5e7eb;
    background: #fff;
  }
  .tbl-count {
    font-size: 12px;
    color: #6b7280;
  }
  .pagination {
    display: flex;
    gap: 4px;
  }
  .page-btn {
    padding: 4px 10px;
    font-size: 12px;
    font-weight: 500;
    border: 1px solid #e5e7eb;
    border-radius: 6px;
    background: #fff;
    color: #374151;
    cursor: pointer;
    transition:
      background 0.15s,
      border-color 0.15s;
  }
  .page-btn:hover {
    background: #f9fafb;
  }
  .page-btn.active {
    background: #eff6ff;
    border-color: rgba(59, 130, 246, 0.4);
    color: #2563eb;
  }
  .page-btn:disabled,
  .page-btn[disabled] {
    opacity: 0.4;
    cursor: default;
  }
</style>

<div class="tbl-wrapper">
  <!-- Toolbar -->
  <div class="tbl-toolbar">
    <div>
      <div class="tbl-title">Daftar Pengguna</div>
      <div class="tbl-subtitle">Menampilkan 7 dari 7 pengguna</div>
    </div>
    <div class="tbl-actions">
      <div class="search-wrap">
        <svg
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="1.5"
          stroke="currentColor"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 15.803 7.5 7.5 0 0015.803 15.803z"
          />
        </svg>
        <input class="search-input" type="text" placeholder="Cari pengguna…" />
      </div>
      <button class="btn-add">
        <svg
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="2"
          stroke="currentColor"
        >
          <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
        </svg>
        Tambah
      </button>
    </div>
  </div>

  <!-- Table Card -->
  <div class="tbl-card">
    <div class="tbl-scroll">
      <table>
        <thead>
          <tr>
            <th>Pengguna</th>
            <th>Email</th>
            <th>Role</th>
            <th>Status</th>
            <th>Bergabung</th>
            <th></th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <td>
              <span class="user-name">Ahmad Fauzi</span>
            </td>
            <td class="email-cell">ahmad.fauzi@example.com</td>
            <td><span class="badge badge-admin">Admin</span></td>
            <td><span class="badge badge-active">active</span></td>
            <td class="email-cell">12 Jan 2024</td>
            <td>
              <div class="btn-group">
                <button class="btn-icon" title="Edit">
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                    stroke-width="1.5"
                    stroke="currentColor"
                  >
                    <path
                      stroke-linecap="round"
                      stroke-linejoin="round"
                      d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125"
                    />
                  </svg>
                </button>
                <button class="btn-icon danger" title="Hapus">
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                    stroke-width="1.5"
                    stroke="currentColor"
                  >
                    <path
                      stroke-linecap="round"
                      stroke-linejoin="round"
                      d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
                    />
                  </svg>
                </button>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <span class="user-name">Siti Rahayu</span>
            </td>
            <td class="email-cell">siti.rahayu@example.com</td>
            <td><span class="badge badge-editor">Editor</span></td>
            <td><span class="badge badge-active">active</span></td>
            <td class="email-cell">03 Mar 2024</td>
            <td>
              <div class="btn-group">
                <button class="btn-icon" title="Edit">
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                    stroke-width="1.5"
                    stroke="currentColor"
                  >
                    <path
                      stroke-linecap="round"
                      stroke-linejoin="round"
                      d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125"
                    />
                  </svg>
                </button>
                <button class="btn-icon danger" title="Hapus">
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                    stroke-width="1.5"
                    stroke="currentColor"
                  >
                    <path
                      stroke-linecap="round"
                      stroke-linejoin="round"
                      d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
                    />
                  </svg>
                </button>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <span class="user-name">Budi Santoso</span>
            </td>
            <td class="email-cell">budi.santoso@example.com</td>
            <td><span class="badge badge-viewer">Viewer</span></td>
            <td><span class="badge badge-inactive">inactive</span></td>
            <td class="email-cell">28 Jun 2023</td>
            <td>
              <div class="btn-group">
                <button class="btn-icon" title="Edit">
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                    stroke-width="1.5"
                    stroke="currentColor"
                  >
                    <path
                      stroke-linecap="round"
                      stroke-linejoin="round"
                      d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125"
                    />
                  </svg>
                </button>
                <button class="btn-icon danger" title="Hapus">
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                    stroke-width="1.5"
                    stroke="currentColor"
                  >
                    <path
                      stroke-linecap="round"
                      stroke-linejoin="round"
                      d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
                    />
                  </svg>
                </button>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <span class="user-name">Dewi Lestari</span>
            </td>
            <td class="email-cell">dewi.lestari@example.com</td>
            <td><span class="badge badge-editor">Editor</span></td>
            <td><span class="badge badge-active">active</span></td>
            <td class="email-cell">15 Sep 2023</td>
            <td>
              <div class="btn-group">
                <button class="btn-icon" title="Edit">
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                    stroke-width="1.5"
                    stroke="currentColor"
                  >
                    <path
                      stroke-linecap="round"
                      stroke-linejoin="round"
                      d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125"
                    />
                  </svg>
                </button>
                <button class="btn-icon danger" title="Hapus">
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                    stroke-width="1.5"
                    stroke="currentColor"
                  >
                    <path
                      stroke-linecap="round"
                      stroke-linejoin="round"
                      d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
                    />
                  </svg>
                </button>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <span class="user-name">Riko Prasetyo</span>
            </td>
            <td class="email-cell">riko.prasetyo@example.com</td>
            <td><span class="badge badge-viewer">Viewer</span></td>
            <td><span class="badge badge-pending">pending</span></td>
            <td class="email-cell">07 Nov 2024</td>
            <td>
              <div class="btn-group">
                <button class="btn-icon" title="Edit">
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                    stroke-width="1.5"
                    stroke="currentColor"
                  >
                    <path
                      stroke-linecap="round"
                      stroke-linejoin="round"
                      d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125"
                    />
                  </svg>
                </button>
                <button class="btn-icon danger" title="Hapus">
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                    stroke-width="1.5"
                    stroke="currentColor"
                  >
                    <path
                      stroke-linecap="round"
                      stroke-linejoin="round"
                      d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
                    />
                  </svg>
                </button>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <span class="user-name">Nurul Hidayah</span>
            </td>
            <td class="email-cell">nurul.hidayah@example.com</td>
            <td><span class="badge badge-admin">Admin</span></td>
            <td><span class="badge badge-active">active</span></td>
            <td class="email-cell">20 Feb 2024</td>
            <td>
              <div class="btn-group">
                <button class="btn-icon" title="Edit">
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                    stroke-width="1.5"
                    stroke="currentColor"
                  >
                    <path
                      stroke-linecap="round"
                      stroke-linejoin="round"
                      d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125"
                    />
                  </svg>
                </button>
                <button class="btn-icon danger" title="Hapus">
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                    stroke-width="1.5"
                    stroke="currentColor"
                  >
                    <path
                      stroke-linecap="round"
                      stroke-linejoin="round"
                      d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
                    />
                  </svg>
                </button>
              </div>
            </td>
          </tr>
          <tr>
            <td>
              <span class="user-name">Yusuf Hakim</span>
            </td>
            <td class="email-cell">yusuf.hakim@example.com</td>
            <td><span class="badge badge-viewer">Viewer</span></td>
            <td><span class="badge badge-inactive">inactive</span></td>
            <td class="email-cell">01 Apr 2023</td>
            <td>
              <div class="btn-group">
                <button class="btn-icon" title="Edit">
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                    stroke-width="1.5"
                    stroke="currentColor"
                  >
                    <path
                      stroke-linecap="round"
                      stroke-linejoin="round"
                      d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125"
                    />
                  </svg>
                </button>
                <button class="btn-icon danger" title="Hapus">
                  <svg
                    xmlns="http://www.w3.org/2000/svg"
                    fill="none"
                    viewBox="0 0 24 24"
                    stroke-width="1.5"
                    stroke="currentColor"
                  >
                    <path
                      stroke-linecap="round"
                      stroke-linejoin="round"
                      d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
                    />
                  </svg>
                </button>
              </div>
            </td>
          </tr>
        </tbody>
      </table>
    </div>

    <!-- Pagination -->
    <div class="tbl-footer">
      <span class="tbl-count">Menampilkan 7 pengguna</span>
      <div class="pagination">
        <button class="page-btn" disabled>
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="12"
            height="12"
            fill="none"
            viewBox="0 0 24 24"
            stroke-width="1.5"
            stroke="currentColor"
          >
            <path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
          </svg>
        </button>
        <button class="page-btn active">1</button>
        <button class="page-btn" disabled>
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="12"
            height="12"
            fill="none"
            viewBox="0 0 24 24"
            stroke-width="1.5"
            stroke="currentColor"
          >
            <path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
          </svg>
        </button>
      </div>
    </div>
  </div>
</div>

Button

Tailwind CSS Bootstrap 5 Vanilla CSS

Kumpulan varian tombol: solid, outline, ghost, berbagai ukuran, ikon, dan state loading/disabled. Mendukung dark mode.

{{--
  Button Component
  Showcase of button variants: solid, outline, ghost, soft, and icon buttons across multiple colors and sizes.
--}}

<div class="space-y-8">
  {{-- Solid Variants --}}
  <div>
    <p
      class="mb-3 text-xs font-semibold tracking-widest text-gray-400 uppercase dark:text-gray-500"
    >
      Solid
    </p>
    <div class="flex flex-wrap items-center gap-3">
      <button
        class="inline-flex items-center justify-center rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-xs transition hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none dark:focus:ring-offset-gray-900"
      >
        Primary
      </button>
      <button
        class="inline-flex items-center justify-center rounded-lg bg-gray-800 px-4 py-2 text-sm font-medium text-white shadow-xs transition hover:bg-gray-700 focus:ring-2 focus:ring-gray-600 focus:ring-offset-2 focus:outline-none dark:bg-white dark:text-gray-900 dark:hover:bg-gray-100 dark:focus:ring-white/40 dark:focus:ring-offset-gray-900"
      >
        Secondary
      </button>
      <button
        class="inline-flex items-center justify-center rounded-lg bg-emerald-600 px-4 py-2 text-sm font-medium text-white shadow-xs transition hover:bg-emerald-700 focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2 focus:outline-none dark:focus:ring-offset-gray-900"
      >
        Success
      </button>
      <button
        class="inline-flex items-center justify-center rounded-lg bg-rose-600 px-4 py-2 text-sm font-medium text-white shadow-xs transition hover:bg-rose-700 focus:ring-2 focus:ring-rose-500 focus:ring-offset-2 focus:outline-none dark:focus:ring-offset-gray-900"
      >
        Danger
      </button>
      <button
        class="inline-flex items-center justify-center rounded-lg bg-amber-500 px-4 py-2 text-sm font-medium text-white shadow-xs transition hover:bg-amber-600 focus:ring-2 focus:ring-amber-400 focus:ring-offset-2 focus:outline-none dark:focus:ring-offset-gray-900"
      >
        Warning
      </button>
    </div>
  </div>

  {{-- Outline Variants --}}
  <div>
    <p
      class="mb-3 text-xs font-semibold tracking-widest text-gray-400 uppercase dark:text-gray-500"
    >
      Outline
    </p>
    <div class="flex flex-wrap items-center gap-3">
      <button
        class="inline-flex items-center justify-center rounded-lg border border-blue-500 px-4 py-2 text-sm font-medium text-blue-600 transition hover:bg-blue-50 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none dark:border-blue-400 dark:text-blue-400 dark:hover:bg-blue-500/10 dark:focus:ring-offset-gray-900"
      >
        Primary
      </button>
      <button
        class="inline-flex items-center justify-center rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition hover:bg-gray-50 focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:outline-none dark:border-white/20 dark:text-gray-300 dark:hover:bg-white/5 dark:focus:ring-offset-gray-900"
      >
        Secondary
      </button>
      <button
        class="inline-flex items-center justify-center rounded-lg border border-emerald-500 px-4 py-2 text-sm font-medium text-emerald-600 transition hover:bg-emerald-50 focus:ring-2 focus:ring-emerald-500 focus:ring-offset-2 focus:outline-none dark:border-emerald-400 dark:text-emerald-400 dark:hover:bg-emerald-500/10 dark:focus:ring-offset-gray-900"
      >
        Success
      </button>
      <button
        class="inline-flex items-center justify-center rounded-lg border border-rose-500 px-4 py-2 text-sm font-medium text-rose-600 transition hover:bg-rose-50 focus:ring-2 focus:ring-rose-500 focus:ring-offset-2 focus:outline-none dark:border-rose-400 dark:text-rose-400 dark:hover:bg-rose-500/10 dark:focus:ring-offset-gray-900"
      >
        Danger
      </button>
    </div>
  </div>

  {{-- Ghost Variants --}}
  <div>
    <p
      class="mb-3 text-xs font-semibold tracking-widest text-gray-400 uppercase dark:text-gray-500"
    >
      Ghost
    </p>
    <div class="flex flex-wrap items-center gap-3">
      <button
        class="inline-flex items-center justify-center rounded-lg px-4 py-2 text-sm font-medium text-blue-600 transition hover:bg-blue-50 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 focus:outline-none dark:text-blue-400 dark:hover:bg-blue-500/10 dark:focus:ring-offset-gray-900"
      >
        Primary
      </button>
      <button
        class="inline-flex items-center justify-center rounded-lg px-4 py-2 text-sm font-medium text-gray-600 transition hover:bg-gray-100 focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:outline-none dark:text-gray-300 dark:hover:bg-white/10 dark:focus:ring-offset-gray-900"
      >
        Secondary
      </button>
      <button
        class="inline-flex items-center justify-center rounded-lg px-4 py-2 text-sm font-medium text-rose-600 transition hover:bg-rose-50 focus:ring-2 focus:ring-rose-500 focus:ring-offset-2 focus:outline-none dark:text-rose-400 dark:hover:bg-rose-500/10 dark:focus:ring-offset-gray-900"
      >
        Danger
      </button>
    </div>
  </div>

  {{-- With Icons --}}
  <div>
    <p
      class="mb-3 text-xs font-semibold tracking-widest text-gray-400 uppercase dark:text-gray-500"
    >
      With Icons
    </p>
    <div class="flex flex-wrap items-center gap-3">
      <button
        class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-xs transition hover:bg-blue-700"
      >
        <svg
          class="h-4 w-4"
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="2"
          stroke="currentColor"
        >
          <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
        </svg>
        Tambah Data
      </button>
      <button
        class="inline-flex items-center gap-2 rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition hover:bg-gray-50 dark:border-white/20 dark:text-gray-300 dark:hover:bg-white/5"
      >
        <svg
          class="h-4 w-4"
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="2"
          stroke="currentColor"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3"
          />
        </svg>
        Unduh
      </button>
      <button
        class="inline-flex items-center gap-2 rounded-lg bg-rose-600 px-4 py-2 text-sm font-medium text-white shadow-xs transition hover:bg-rose-700"
      >
        <svg
          class="h-4 w-4"
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="2"
          stroke="currentColor"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
          />
        </svg>
        Hapus
      </button>
      {{-- Icon-only --}}
      <button
        class="inline-flex items-center justify-center rounded-lg bg-gray-100 p-2 text-gray-600 transition hover:bg-gray-200 dark:bg-white/10 dark:text-gray-300 dark:hover:bg-white/20"
      >
        <svg
          class="h-4 w-4"
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="2"
          stroke="currentColor"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.108-1.204l-.526-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z"
          />
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
          />
        </svg>
      </button>
    </div>
  </div>

  {{-- States --}}
  <div>
    <p
      class="mb-3 text-xs font-semibold tracking-widest text-gray-400 uppercase dark:text-gray-500"
    >
      States
    </p>
    <div class="flex flex-wrap items-center gap-3">
      <button
        class="inline-flex items-center justify-center rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-xs transition hover:bg-blue-700"
      >
        Default
      </button>
      <button
        disabled
        class="inline-flex cursor-not-allowed items-center justify-center rounded-lg bg-blue-400 px-4 py-2 text-sm font-medium text-white opacity-60"
      >
        Disabled
      </button>
      <button
        class="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white shadow-xs"
        disabled
      >
        <svg
          class="h-4 w-4 animate-spin"
          xmlns="http://www.w3.org/2000/svg"
          fill="none"
          viewBox="0 0 24 24"
        >
          <circle
            class="opacity-25"
            cx="12"
            cy="12"
            r="10"
            stroke="currentColor"
            stroke-width="4"
          ></circle>
          <path
            class="opacity-75"
            fill="currentColor"
            d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"
          ></path>
        </svg>
        Loading…
      </button>
    </div>
  </div>
</div>
{{--
  Button Component — Bootstrap 5
  Dep: bootstrap@5.3.3 CSS + JS bundle
--}}

<div class="p-4 d-flex flex-column gap-4">

  <!-- Solid -->
  <div>
    <p class="text-uppercase fw-semibold mb-2" style="font-size:.7rem;letter-spacing:.08em;color:#9ca3af">Solid</p>
    <div class="d-flex flex-wrap gap-2">
      <button type="button" class="btn btn-primary btn-sm px-3 py-2" style="font-size:.875rem;border-radius:.5rem">Primary</button>
      <button type="button" class="btn btn-dark btn-sm px-3 py-2"    style="font-size:.875rem;border-radius:.5rem">Secondary</button>
      <button type="button" class="btn btn-success btn-sm px-3 py-2" style="font-size:.875rem;border-radius:.5rem">Success</button>
      <button type="button" class="btn btn-danger btn-sm px-3 py-2"  style="font-size:.875rem;border-radius:.5rem">Danger</button>
      <button type="button" class="btn btn-warning btn-sm px-3 py-2 text-white" style="font-size:.875rem;border-radius:.5rem">Warning</button>
    </div>
  </div>

  <!-- Outline -->
  <div>
    <p class="text-uppercase fw-semibold mb-2" style="font-size:.7rem;letter-spacing:.08em;color:#9ca3af">Outline</p>
    <div class="d-flex flex-wrap gap-2">
      <button type="button" class="btn btn-outline-primary btn-sm px-3 py-2"   style="font-size:.875rem;border-radius:.5rem">Primary</button>
      <button type="button" class="btn btn-outline-secondary btn-sm px-3 py-2" style="font-size:.875rem;border-radius:.5rem">Secondary</button>
      <button type="button" class="btn btn-outline-success btn-sm px-3 py-2"   style="font-size:.875rem;border-radius:.5rem">Success</button>
      <button type="button" class="btn btn-outline-danger btn-sm px-3 py-2"    style="font-size:.875rem;border-radius:.5rem">Danger</button>
    </div>
  </div>

  <!-- Ghost -->
  <div>
    <p class="text-uppercase fw-semibold mb-2" style="font-size:.7rem;letter-spacing:.08em;color:#9ca3af">Ghost</p>
    <div class="d-flex flex-wrap gap-2">
      <button type="button" class="btn btn-link text-primary text-decoration-none px-3 py-2"   style="font-size:.875rem">Primary</button>
      <button type="button" class="btn btn-link text-secondary text-decoration-none px-3 py-2" style="font-size:.875rem">Secondary</button>
      <button type="button" class="btn btn-link text-danger text-decoration-none px-3 py-2"    style="font-size:.875rem">Danger</button>
    </div>
  </div>

  <!-- With Icons -->
  <div>
    <p class="text-uppercase fw-semibold mb-2" style="font-size:.7rem;letter-spacing:.08em;color:#9ca3af">With Icons</p>
    <div class="d-flex flex-wrap gap-2">
      <button type="button" class="btn btn-primary btn-sm d-inline-flex align-items-center gap-2 px-3 py-2" style="border-radius:.5rem">
        <svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/></svg>
        Tambah Data
      </button>
      <button type="button" class="btn btn-outline-secondary btn-sm d-inline-flex align-items-center gap-2 px-3 py-2" style="border-radius:.5rem">
        <svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3"/></svg>
        Unduh
      </button>
      <button type="button" class="btn btn-danger btn-sm d-inline-flex align-items-center gap-2 px-3 py-2" style="border-radius:.5rem">
        <svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/></svg>
        Hapus
      </button>
      <button type="button" class="btn btn-secondary btn-sm d-inline-flex align-items-center justify-content-center p-2" style="border-radius:.5rem;width:34px;height:34px" aria-label="Settings">
        <svg width="16" height="16" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.108-1.204l-.526-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
      </button>
    </div>
  </div>

  <!-- States -->
  <div>
    <p class="text-uppercase fw-semibold mb-2" style="font-size:.7rem;letter-spacing:.08em;color:#9ca3af">States</p>
    <div class="d-flex flex-wrap gap-2">
      <button type="button" class="btn btn-primary btn-sm px-3 py-2" style="border-radius:.5rem">Default</button>
      <button type="button" class="btn btn-primary btn-sm px-3 py-2" style="border-radius:.5rem;opacity:.6" disabled>Disabled</button>
      <button type="button" class="btn btn-primary btn-sm d-inline-flex align-items-center gap-2 px-3 py-2" style="border-radius:.5rem" disabled>
        <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
        Loading&hellip;
      </button>
    </div>
  </div>

</div>
{{-- Button Component — Vanilla CSS --}}
<style>
  *,
  *::before,
  *::after {
    box-sizing: border-box;
  }
  .vb-w {
    display: flex;
    flex-direction: column;
    gap: 2rem;
    padding: 1.5rem;
  }
  .vb-lbl {
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #9ca3af;
    margin-bottom: 0.5rem;
  }
  .vb-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
  }
  .vb {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    font-family: inherit;
    font-size: 0.875rem;
    font-weight: 500;
    line-height: 1;
    padding: 0.5rem 1rem;
    border-radius: 0.5rem;
    border: 1px solid transparent;
    cursor: pointer;
    text-decoration: none;
    transition:
      background 0.15s,
      color 0.15s,
      border-color 0.15s;
    vertical-align: middle;
    white-space: nowrap;
  }
  .vb:disabled {
    opacity: 0.6;
    cursor: not-allowed;
  }
  /* solid */
  .vb-p {
    background: #2563eb;
    color: #fff;
  }
  .vb-p:hover:not(:disabled) {
    background: #1d4ed8;
  }
  .vb-d {
    background: #1f2937;
    color: #fff;
  }
  .vb-d:hover:not(:disabled) {
    background: #374151;
  }
  .vb-s {
    background: #059669;
    color: #fff;
  }
  .vb-s:hover:not(:disabled) {
    background: #047857;
  }
  .vb-r {
    background: #dc2626;
    color: #fff;
  }
  .vb-r:hover:not(:disabled) {
    background: #b91c1c;
  }
  .vb-w2 {
    background: #d97706;
    color: #fff;
  }
  .vb-w2:hover:not(:disabled) {
    background: #b45309;
  }
  /* outline */
  .vb-op {
    background: transparent;
    border-color: #2563eb;
    color: #2563eb;
  }
  .vb-op:hover:not(:disabled) {
    background: #eff6ff;
  }
  .vb-os {
    background: transparent;
    border-color: #6b7280;
    color: #374151;
  }
  .vb-os:hover:not(:disabled) {
    background: #f9fafb;
  }
  .vb-og {
    background: transparent;
    border-color: #059669;
    color: #059669;
  }
  .vb-og:hover:not(:disabled) {
    background: #ecfdf5;
  }
  .vb-or {
    background: transparent;
    border-color: #dc2626;
    color: #dc2626;
  }
  .vb-or:hover:not(:disabled) {
    background: #fef2f2;
  }
  /* ghost */
  .vb-gp {
    background: transparent;
    color: #2563eb;
  }
  .vb-gp:hover:not(:disabled) {
    background: #eff6ff;
  }
  .vb-gs {
    background: transparent;
    color: #4b5563;
  }
  .vb-gs:hover:not(:disabled) {
    background: #f3f4f6;
  }
  .vb-gr {
    background: transparent;
    color: #dc2626;
  }
  .vb-gr:hover:not(:disabled) {
    background: #fef2f2;
  }
  /* icon-only */
  .vb-ico {
    padding: 0.5rem;
    border-radius: 0.5rem;
    background: #f3f4f6;
    color: #4b5563;
    border: none;
  }
  .vb-ico:hover {
    background: #e5e7eb;
  }
  /* spinner */
  .vb-spin {
    width: 14px;
    height: 14px;
    border: 2px solid rgba(255, 255, 255, 0.35);
    border-top-color: #fff;
    border-radius: 50%;
    animation: _s 0.6s linear infinite;
    flex-shrink: 0;
  }
  @keyframes _s {
    to {
      transform: rotate(360deg);
    }
  }
</style>

<div class="vb-w">
  <div>
    <p class="vb-lbl">Solid</p>
    <div class="vb-row">
      <button class="vb vb-p">Primary</button>
      <button class="vb vb-d">Secondary</button>
      <button class="vb vb-s">Success</button>
      <button class="vb vb-r">Danger</button>
      <button class="vb vb-w2">Warning</button>
    </div>
  </div>

  <div>
    <p class="vb-lbl">Outline</p>
    <div class="vb-row">
      <button class="vb vb-op">Primary</button>
      <button class="vb vb-os">Secondary</button>
      <button class="vb vb-og">Success</button>
      <button class="vb vb-or">Danger</button>
    </div>
  </div>

  <div>
    <p class="vb-lbl">Ghost</p>
    <div class="vb-row">
      <button class="vb vb-gp">Primary</button>
      <button class="vb vb-gs">Secondary</button>
      <button class="vb vb-gr">Danger</button>
    </div>
  </div>

  <div>
    <p class="vb-lbl">With Icons</p>
    <div class="vb-row">
      <button class="vb vb-p">
        <svg
          width="14"
          height="14"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="2"
          stroke="currentColor"
        >
          <path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
        </svg>
        Tambah Data
      </button>
      <button class="vb vb-os">
        <svg
          width="14"
          height="14"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="2"
          stroke="currentColor"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3"
          />
        </svg>
        Unduh
      </button>
      <button class="vb vb-r">
        <svg
          width="14"
          height="14"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="2"
          stroke="currentColor"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
          />
        </svg>
        Hapus
      </button>
      <button class="vb vb-ico" aria-label="Settings">
        <svg
          width="16"
          height="16"
          fill="none"
          viewBox="0 0 24 24"
          stroke-width="2"
          stroke="currentColor"
        >
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 011.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.56.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.893.149c-.425.07-.765.383-.93.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 01-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.397.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 01-.12-1.45l.527-.737c.25-.35.273-.806.108-1.204-.165-.397-.505-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.108-1.204l-.526-.738a1.125 1.125 0 01.12-1.45l.773-.773a1.125 1.125 0 011.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894z"
          />
          <path
            stroke-linecap="round"
            stroke-linejoin="round"
            d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
          />
        </svg>
      </button>
    </div>
  </div>

  <div>
    <p class="vb-lbl">States</p>
    <div class="vb-row">
      <button class="vb vb-p">Default</button>
      <button class="vb vb-p" disabled>Disabled</button>
      <button class="vb vb-p" disabled>
        <span class="vb-spin"></span>
        Loading&hellip;
      </button>
    </div>
  </div>
</div>

Color Palette

rgba CSS Variables

Palet warna yang diterapkan di seluruh proyek, dibagi berdasarkan mode tampilan. Format rgba() siap dipakai langsung di CSS.

Base

Background

#F9FAFB

rgba(249, 250, 251, 1)

bg-gray-50

Foreground

#09090B

rgba(9, 9, 11, 1)

text-zinc-950

Sidebar / Header / Footer

Sidebar

#FAFAFA

rgba(250, 250, 250, 1)

bg-zinc-50

Sidebar Foreground

#09090B

rgba(9, 9, 11, 1)

text-zinc-950

Sidebar Primary

#2563EB

rgba(37, 99, 235, 1)

bg-blue-600

Sidebar Primary FG

#EEF2FF

rgba(238, 242, 255, 1)

text-indigo-50

Sidebar Accent

#F4F4F5

rgba(244, 244, 245, 1)

bg-zinc-100

Sidebar Accent FG

#18181B

rgba(24, 24, 27, 1)

text-zinc-900

Sidebar Border

#E4E4E7

rgba(228, 228, 231, 1)

border-zinc-200

Sidebar Ring

#A1A1AA

rgba(161, 161, 170, 1)

ring-zinc-400

Border, Input & Ring

Border

#E4E4E7

rgba(228, 228, 231, 1)

border-zinc-200

Input

#E4E4E7

rgba(228, 228, 231, 1)

border-zinc-200

Ring

#A1A1AA

rgba(161, 161, 170, 1)

ring-zinc-400

Card & Surface

Card

#FFFFFF

rgba(255, 255, 255, 1)

bg-white

Card Foreground

#09090B

rgba(9, 9, 11, 1)

text-zinc-950

Popover

#FFFFFF

rgba(255, 255, 255, 1)

bg-white

Popover Foreground

#09090B

rgba(9, 9, 11, 1)

text-zinc-950

Primary

Primary

#2563EB

rgba(37, 99, 235, 1)

bg-blue-600

Primary Foreground

#EEF2FF

rgba(238, 242, 255, 1)

text-indigo-50

Secondary

Secondary

#F4F4F5

rgba(244, 244, 245, 1)

bg-zinc-100

Secondary Foreground

#18181B

rgba(24, 24, 27, 1)

text-zinc-900

Muted & Accent

Muted

#F4F4F5

rgba(244, 244, 245, 1)

bg-zinc-100

Muted Foreground

#71717A

rgba(113, 113, 122, 1)

text-zinc-500

Accent

#F4F4F5

rgba(244, 244, 245, 1)

bg-zinc-100

Accent Foreground

#18181B

rgba(24, 24, 27, 1)

text-zinc-900

Destructive

Destructive

#EF4444

rgba(239, 68, 68, 1)

bg-red-500

Base

Background

#020617

rgba(2, 6, 23, 1)

dark:bg-slate-950

Foreground

#F1F5F9

rgba(241, 245, 249, 1)

dark:text-slate-100

Sidebar / Header / Footer

Sidebar

#0F172A

rgba(15, 23, 42, 1)

dark:bg-slate-900

Sidebar Foreground

#E2E8F0

rgba(226, 232, 240, 1)

dark:text-slate-200

Sidebar Primary

#2563EB

rgba(37, 99, 235, 1)

dark:bg-blue-600

Sidebar Primary FG

#FFFFFF

rgba(255, 255, 255, 1)

dark:text-white

Sidebar Accent

#1E293B

rgba(30, 41, 59, 1)

dark:bg-slate-800

Sidebar Accent FG

#F1F5F9

rgba(241, 245, 249, 1)

dark:text-slate-100

Sidebar Border

white/8

rgba(255, 255, 255, 0.08)

dark:border-white/[0.08]

Sidebar Ring

#3B82F6

rgba(59, 130, 246, 1)

dark:ring-blue-500

Border, Input & Ring

Border

slate-400/15

rgba(148, 163, 184, 0.15)

dark:border-slate-400/15

Input

#334155

rgba(51, 65, 85, 1)

dark:bg-slate-700

Ring

#3B82F6

rgba(59, 130, 246, 1)

dark:ring-blue-500

Card & Surface

Card

#111827

rgba(17, 24, 39, 1)

dark:bg-gray-900

Card Foreground

#F1F5F9

rgba(241, 245, 249, 1)

dark:text-slate-100

Popover

#0F172A

rgba(15, 23, 42, 1)

dark:bg-slate-900

Popover Foreground

#F8FAFC

rgba(248, 250, 252, 1)

dark:text-slate-50

Primary

Primary

#2563EB

rgba(37, 99, 235, 1)

dark:bg-blue-600

Primary Foreground

#FFFFFF

rgba(255, 255, 255, 1)

dark:text-white

Secondary

Secondary

#1E293B

rgba(30, 41, 59, 1)

dark:bg-slate-800

Secondary Foreground

#E2E8F0

rgba(226, 232, 240, 1)

dark:text-slate-200

Muted & Accent

Muted

#1E293B

rgba(30, 41, 59, 1)

dark:bg-slate-800

Muted Foreground

#94A3B8

rgba(148, 163, 184, 1)

dark:text-slate-400

Accent

#1E293B

rgba(30, 41, 59, 1)

dark:bg-slate-800

Accent Foreground

#F1F5F9

rgba(241, 245, 249, 1)

dark:text-slate-100

Destructive

Destructive

#EF4444

rgba(239, 68, 68, 1)

dark:bg-red-500

:root {
      /* ── Radius ── */
      --radius: 0.65rem;

      /* ── Base ── */
      --background: rgba(249, 250, 251, 1); /* gray-50 */
      --foreground: rgba(9, 9, 11, 1); /* zinc-950 */

      /* ── Sidebar / Header / Footer ── */
      --sidebar: rgba(250, 250, 250, 1); /* zinc-50 */
      --sidebar-foreground: rgba(9, 9, 11, 1); /* zinc-950 */
      --sidebar-primary: rgba(37, 99, 235, 1); /* blue-600 */
      --sidebar-primary-foreground: rgba(238, 242, 255, 1); /* indigo-50 */
      --sidebar-accent: rgba(244, 244, 245, 1); /* zinc-100 */
      --sidebar-accent-foreground: rgba(24, 24, 27, 1); /* zinc-900 */
      --sidebar-border: rgba(228, 228, 231, 1); /* zinc-200 */
      --sidebar-ring: rgba(161, 161, 170, 1); /* zinc-400 */

      /* ── Border, Input & Ring ── */
      --border: rgba(228, 228, 231, 1); /* zinc-200 */
      --input: rgba(228, 228, 231, 1); /* zinc-200 */
      --ring: rgba(161, 161, 170, 1); /* zinc-400 */

      /* ── Card & Surface ── */
      --card: rgba(255, 255, 255, 1); /* white */
      --card-foreground: rgba(9, 9, 11, 1); /* zinc-950 */
      --popover: rgba(255, 255, 255, 1); /* white */
      --popover-foreground: rgba(9, 9, 11, 1); /* zinc-950 */

      /* ── Primary ── */
      --primary: rgba(37, 99, 235, 1); /* blue-600 */
      --primary-foreground: rgba(238, 242, 255, 1); /* indigo-50 */

      /* ── Secondary ── */
      --secondary: rgba(244, 244, 245, 1); /* zinc-100 */
      --secondary-foreground: rgba(24, 24, 27, 1); /* zinc-900 */

      /* ── Muted ── */
      --muted: rgba(244, 244, 245, 1); /* zinc-100 */
      --muted-foreground: rgba(113, 113, 122, 1); /* zinc-500 */

      /* ── Accent ── */
      --accent: rgba(244, 244, 245, 1); /* zinc-100 */
      --accent-foreground: rgba(24, 24, 27, 1); /* zinc-900 */

      /* ── Destructive ── */
      --destructive: rgba(239, 68, 68, 1); /* red-500 */
    }
.dark {
      /* ── Base ── */
      --background: rgba(2, 6, 23, 1); /* slate-950 */
      --foreground: rgba(241, 245, 249, 1); /* slate-100 */

      /* ── Sidebar / Header / Footer ── */
      --sidebar: rgba(15, 23, 42, 1); /* slate-900 */
      --sidebar-foreground: rgba(226, 232, 240, 1); /* slate-200 */
      --sidebar-primary: rgba(37, 99, 235, 1); /* blue-600 */
      --sidebar-primary-foreground: rgba(255, 255, 255, 1); /* white */
      --sidebar-accent: rgba(30, 41, 59, 1); /* slate-800 */
      --sidebar-accent-foreground: rgba(241, 245, 249, 1); /* slate-100 */
      --sidebar-border: rgba(255, 255, 255, 0.08); /* white/8 */
      --sidebar-ring: rgba(59, 130, 246, 1); /* blue-500 */

      /* ── Border, Input & Ring ── */
      --border: rgba(148, 163, 184, 0.15); /* slate-400/15 */
      --input: rgba(51, 65, 85, 1); /* slate-700 */
      --ring: rgba(59, 130, 246, 1); /* blue-500 */

      /* ── Card & Surface ── */
      --card: rgba(17, 24, 39, 1); /* gray-900 */
      --card-foreground: rgba(241, 245, 249, 1); /* slate-100 */
      --popover: rgba(15, 23, 42, 1); /* slate-900 */
      --popover-foreground: rgba(248, 250, 252, 1); /* slate-50 */

      /* ── Primary ── */
      --primary: rgba(37, 99, 235, 1); /* blue-600 */
      --primary-foreground: rgba(255, 255, 255, 1); /* white */

      /* ── Secondary ── */
      --secondary: rgba(30, 41, 59, 1); /* slate-800 */
      --secondary-foreground: rgba(226, 232, 240, 1); /* slate-200 */

      /* ── Muted ── */
      --muted: rgba(30, 41, 59, 1); /* slate-800 */
      --muted-foreground: rgba(148, 163, 184, 1); /* slate-400 */

      /* ── Accent ── */
      --accent: rgba(30, 41, 59, 1); /* slate-800 */
      --accent-foreground: rgba(241, 245, 249, 1); /* slate-100 */

      /* ── Destructive ── */
      --destructive: rgba(239, 68, 68, 1); /* red-500 */
    }