Open Source UI Kit
Kumpulan komponen siap pakai berbasis AlpineJS & Tailwind CSS, Bootstrap 5, dan
Vanilla CSS.
Copy, paste, dan sesuaikan langsung di proyek Anda.
Navigasi responsif dengan sidebar toggle, nama layanan reaktif, dark mode toggle, dan responsive offset. Berbasis AlpineJS.
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.
| Library | Tailwind | Bootstrap 5 | Vanilla CSS |
|---|---|---|---|
Alpine.js v3
|
✓ Wajib | ✓ Wajib | ✓ Wajib |
Bootstrap 5 CSS+JS
|
— | ✓ Wajib | — |
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>
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 }"
>
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>
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 | 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>
Nested sidebar dengan sub-panel dan sub-sub-panel. Mendukung menu bertingkat, dark mode, dan animasi slide.
Filter Menu Sidebar
resources/js/
├── sidebar.js ← file utama sidebar (jangan diubah)
└── menus/
├── {Nama Aplikasi}/
│ └── {Nama Layanan}/
│ ├── {Role 1}.json
│ ├── {Role 2}.json
│ └── ...
└── registry.js
Sidebar mendukung dua format JSON: Item (daftar menu biasa) dan Group (menu dikelompokkan dalam kategori). Kedua format tidak boleh dicampur dalam satu file.
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
.
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) |
[
{
"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"
}
]
[
{
"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" }
]
}
]
resources/js/menus/my-menu.json
app.js
lalu assign ke
currentMenu
di dalam store (lihat contoh di bawah)
npm run build
(atau
npm run dev
) agar Vite mem-bundle file JSON baru
label
dan salah satu dari
href
atau
children
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 di bagian bawah halaman dengan layout responsif dan dukungan dark mode. fixed
<nav
class="h-17 content-center border-t border-gray-200 bg-white shadow dark:border-slate-800 dark:bg-slate-900"
>
<div class="py-4 pl-5 text-xs dark:text-gray-200/50">
© Copyright 2026 Direktorat Jenderal Perhubungan Udara. Hak cipta dilindungi undang-undang.
</div>
</nav>
{{--
Footer — Bootstrap 5
Dep: bootstrap@5.3.3 CSS
Mendukung dark & light mode (class="dark" on <html>).
--}}
<style>
.bs-footer { background: #fff; border-top: 1px solid #e5e7eb; box-shadow: 0 -1px 3px rgba(0,0,0,.07); }
.bs-footer__text { color: #9ca3af; }
.dark .bs-footer { background: #0f172a; border-top-color: #1e293b; box-shadow: 0 -1px 3px rgba(0,0,0,.3); }
.dark .bs-footer__text { color: #e5e7eb; }
</style>
<footer class="bs-footer d-flex align-items-center" style="height:68px">
<div class="bs-footer__text" style="padding-left:1.25rem;padding-right:1.5rem;font-size:.75rem;width:100%">
© Copyright 2026 Direktorat Jenderal Perhubungan Udara. Hak cipta dilindungi undang-undang.
</div>
</footer>
{{--
Footer — Vanilla CSS
Mendukung dark & light mode (class="dark" on <html>).
--}}
<style>
.vf {
height: 68px;
display: flex;
align-items: center;
background: #fff;
border-top: 1px solid #e5e7eb;
box-shadow: 0 -1px 3px rgba(0, 0, 0, 0.07);
}
.vf__inner {
padding-left: 1.25rem;
padding-right: 1.5rem;
font-size: 0.75rem;
color: #9ca3af;
width: 100%;
}
/* dark mode */
.dark .vf {
background: #0f172a;
border-top-color: #1e293b;
box-shadow: 0 -1px 3px rgba(0, 0, 0, 0.3);
}
.dark .vf__inner {
color: #e5e7eb;
}
</style>
<footer class="vf" role="contentinfo">
<div class="vf__inner">
© Copyright 2026 Direktorat Jenderal Perhubungan Udara. Hak cipta dilindungi undang-undang.
</div>
</footer>
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>
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>
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…
</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…
</button>
</div>
</div>
</div>
Palet warna yang diterapkan di seluruh proyek, dibagi berdasarkan mode tampilan. Format
rgba()
siap dipakai langsung di CSS.
Background
#F9FAFB
rgba(249, 250, 251, 1)
bg-gray-50
Foreground
#09090B
rgba(9, 9, 11, 1)
text-zinc-950
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
#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
#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
#2563EB
rgba(37, 99, 235, 1)
bg-blue-600
Primary Foreground
#EEF2FF
rgba(238, 242, 255, 1)
text-indigo-50
Secondary
#F4F4F5
rgba(244, 244, 245, 1)
bg-zinc-100
Secondary Foreground
#18181B
rgba(24, 24, 27, 1)
text-zinc-900
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
#EF4444
rgba(239, 68, 68, 1)
bg-red-500
Background
#020617
rgba(2, 6, 23, 1)
dark:bg-slate-950
Foreground
#F1F5F9
rgba(241, 245, 249, 1)
dark:text-slate-100
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
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
#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
#2563EB
rgba(37, 99, 235, 1)
dark:bg-blue-600
Primary Foreground
#FFFFFF
rgba(255, 255, 255, 1)
dark:text-white
Secondary
#1E293B
rgba(30, 41, 59, 1)
dark:bg-slate-800
Secondary Foreground
#E2E8F0
rgba(226, 232, 240, 1)
dark:text-slate-200
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
#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 */
}