Tailwind 4
resources/css/panda-panel.css adalah stylesheet Tailwind 4 dan tidak dapat dikompilasi dengan versi lain. File ini dimulai dengan @import 'tailwindcss', mendeklarasikan dark variant melalui @custom-variant, memetakan token di @theme inline, dan menambahkan dua content root dengan @source—empat directive yang tidak dipahami Tailwind 3. Gunakan halaman ini ketika build gagal di file tersebut, panel tampil tanpa styling, atau class yang Anda set dari PHP terlihat di DOM tetapi tidak menghasilkan perubahan.
Pastikan versinya terlebih dahulu
npm ls tailwindcss @tailwindcss/vite├── @tailwindcss/vite@4.1.11
└── tailwindcss@4.1.112
Jika salah satunya masih 3.x, itulah penyebabnya. Package mendeklarasikan keduanya pada ^4.1.0, dan php artisan panel:install akan mencantumkannya sebagai dependency yang belum dimiliki aplikasi:
npm install tailwindcss@^4.1.0 @tailwindcss/vite@^4.1.0 tw-animate-css@^1.2.0
npm run build2
Node harus >=20.19.
1. Build gagal di dalam panda-panel.css
Gejala. npm run build melaporkan unknown at-rule, @theme yang tidak dikenali, atau menghasilkan stylesheet tanpa warna panel.
Penyebab. Tailwind 3. Konfigurasi v3 dan v4 berada di tempat berbeda dan masing-masing tidak membaca konfigurasi versi lainnya. v3 menggunakan tailwind.config.js serta directive @tailwind base; @tailwind components; @tailwind utilities;, sedangkan v4 menempatkan theme, variant, dan content scan langsung di CSS.
Empat directive yang dibutuhkan stylesheet panel:
| Directive | Di panda-panel.css | Fungsi |
|---|---|---|
@import 'tailwindcss' | baris 1 | memasukkan Tailwind; v3 tidak menggunakan import ini |
@source '…' | dua baris | menambahkan path ke content scan, relatif terhadap stylesheet |
@custom-variant dark (&:is(.dark *)) | satu baris | mendefinisikan variant dark: sebagai class-descendant selector |
@theme inline { … } | block token | mengubah custom property menjadi utility Tailwind |
@import 'tailwindcss';
@import 'tw-animate-css';
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
@source '../../storage/framework/views/*.php';
@custom-variant dark (&:is(.dark *));2
3
4
5
6
7
8
Tidak ada compatibility path untuk v3. Tailwind 4 merupakan requirement wajib, dengan alasan yang sama seperti Laravel 12: stylesheet ditulis untuk versi tersebut dan mendukung v3 berarti memelihara dua implementasi.
2. Tailwind 4 sudah terpasang tetapi build tetap tidak memproses CSS
Penyebab. Pipeline build v3 masih tersisa. Tailwind 4 memindahkan PostCSS plugin ke package terpisah (@tailwindcss/postcss), sehingga postcss.config.js yang masih mencantumkan tailwindcss sebagai plugin menggunakan konfigurasi untuk versi yang sudah tidak terpasang.
Daftar dependency package menggunakan @tailwindcss/vite, yaitu integrasi native Vite yang tidak membutuhkan konfigurasi PostCSS:
// vite.config.ts
import tailwindcss from '@tailwindcss/vite';
import vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [vue(), tailwindcss()],
});2
3
4
5
6
7
8
tailwind.config.js yang masih tersedia tidak berbahaya; Tailwind 4 tidak membacanya kecuali stylesheet secara eksplisit menggunakan @config. Menghapusnya hanya menghilangkan file yang terlihat penting padahal sudah tidak digunakan.
3. Seluruh layar panel tampil sebagai HTML tanpa styling
Penyebab, sesuai urutan pemeriksaan. Build belum dijalankan, atau stylesheet tidak berada di entrypoint mana pun.
npm run build # or npm run devpanda-panel.css dipublish ke aplikasi, dan ada dua pendekatan yang wajar. Laravel Vue starter kit membawa app.css yang hampir sama; bagian khusus panel terutama token --sidebar-* dan class komponen frozen-column:
- Tetap gunakan
app.cssmilik aplikasi dan salin bagian yang belum dimiliki. Tidak perlu konfigurasi lain; komponen panel menggunakan theme utility biasa. - Build
panda-panel.csssebagai entrypoint terpisah, lalu deklarasikan pada panel agar hanya dimuat di halaman panel tersebut:
$panel->assets('resources/css/panda-panel.css');// vite.config.ts
input: ['resources/css/app.css', 'resources/js/app.ts', 'resources/css/panda-panel.css'],2
| Member | Signature |
|---|---|
assets | assets(string ...$entrypoints): self |
getAssets | getAssets(): list<string> |
Dibutuhkan dua perubahan secara sengaja. Panel::assets() menerima path, bukan built file, sehingga entry yang tidak ada di input Vite akan gagal saat request dengan Unable to locate file in Vite manifest. Kegagalan tersebut tepat—asset yang dideklarasikan tetapi tidak pernah dibangun memang merupakan kesalahan—dan itulah sebabnya konfigurasi ini bukan perubahan satu baris. Entrypoint bersifat akumulatif antar pemanggilan dan list-nya tidak dikirim ke frontend; browser menerima tag hasil build, bukan daftar yang menghasilkan tag tersebut.
4. Warna dari colors() tidak berpengaruh
$panel->colors(
light: ['primary' => '#4f46e5', 'sidebar' => 'oklch(0.98 0 0)'],
dark: ['primary' => '#818cf8'],
);2
3
4
Tidak perlu rebuild untuk ini. Value light diterapkan pada atribut style di root shell sebagai --primary dan --sidebar, sementara stylesheet sudah membaca custom property tersebut.
<div class="panel-shell" style="--primary: #4f46e5; --sidebar: oklch(0.98 0 0)">Hal ini bekerja karena theme block menggunakan @theme **inline**. Dengan inline, utility menggunakan variable yang dirujuk secara langsung—bg-primary menjadi var(--primary)—sehingga value yang ditetapkan lebih dalam pada DOM tree tetap berlaku. Tanpa inline, value akan dibekukan pada build time dan runtime palette tidak mungkin digunakan.
Jika warna tidak diterapkan, salah satu dari dua validasi membuangnya secara diam-diam. Satu warna yang salah tidak seharusnya membuat seluruh panel gagal dirender.
$panel->getTheme();
// ['light' => ['primary' => '#4f46e5'], 'dark' => ['primary' => '#818cf8']]2
| Penyebab | Contoh |
|---|---|
| Property tidak dikenal stylesheet | 'primry' => '#fff' |
| Value bukan sintaks warna yang dikenali | 'red; content: url(https://evil.test)' |
Delapan belas property yang dapat ditetapkan panel—tulis tanpa prefix --, yang akan dihapus:
primary | primary-foreground | secondary |
secondary-foreground | accent | accent-foreground |
background | foreground | muted |
muted-foreground | destructive | border |
ring | sidebar | sidebar-foreground |
sidebar-primary | sidebar-accent | sidebar-border |
Sintaks value yang diterima: hex 3–8 digit, rgb()/rgba(), hsl()/hsla(), dan oklch(). Value lain dibuang karena nilai tersebut akan ditempatkan di atribut style; arbitrary string pada posisi itu bukan lagi sekadar warna tetapi dapat menjadi stylesheet.
| Member | Signature |
|---|---|
Panel::colors | colors(array $light, array $dark = []): self |
Panel::getTheme | getTheme(): array{light: array<string, string>, dark: array<string, string>} |
PanelTheme::light / dark | light(array $colors): self / dark(array $colors): self |
PanelTheme::isEmpty | isEmpty(): bool |
PanelTheme::toArray | toArray(): array |
Dark palette diserialisasi tetapi tidak diterapkan. Inline style tidak dapat menyatakan "hanya berlaku di bawah .dark", sehingga value dark dikirim ke frontend sebagai panel.theme.dark dan tidak ada komponen bawaan yang menerapkannya. Theme yang harus berbeda berdasarkan color scheme sebaiknya ditulis di stylesheet:
/* resources/css/panels/admin.css */
.panel-shell {
--primary: #4f46e5;
}
.dark .panel-shell {
--primary: #818cf8;
}2
3
4
5
6
7
8
5. Class dari PHP ada di DOM tetapi tidak memberikan efek
Penyebab. Tailwind memindai source file untuk menemukan class name. String yang ditulis di panel provider PHP tidak berada dalam file yang discan secara default, sehingga class sampai ke DOM tetapi rule CSS-nya tidak pernah dibuat. Tidak ada log, dan DOM inspector tetap menunjukkan class tersebut, sehingga masalah ini mudah menyesatkan.
$panel->cssHooks([
'topbar' => 'border-b-2 border-amber-500',
'table-row' => 'hover:bg-amber-50',
]);2
3
4
Tiga solusi, sesuai urutan yang disarankan:
Tambahkan provider ke source Tailwind. @source menerima path relatif terhadap stylesheet:
/* resources/css/app.css */
@import 'tailwindcss';
@source '../../app/Panels';2
3
4
Gunakan class yang sudah muncul pada template aplikasi. hover:bg-muted, border-b-2, dan shadow-sm kemungkinan besar sudah ikut dikompilasi.
Tulis plain CSS terhadap stable class. Setiap bagian shell sudah memiliki class panel-* yang ditulis langsung di komponen, bukan digenerate, sehingga class selalu tersedia:
.panel-topbar {
border-bottom: 2px solid #f59e0b;
}2
3
| Member | Signature | Catatan |
|---|---|---|
Panel::cssHooks | cssHooks(array $classes): self | keyed berdasarkan hook name; pemanggilan bersifat akumulatif |
Panel::getCssHooks | getCssHooks(): array<string, string> | [] jika panel tidak mendeklarasikan apa pun |
CssHooks::HOOKS | public const HOOKS | closed allowlist nama hook |
Nama hook yang tidak dikenal dibuang tanpa warning, karena allowlist adalah bagian dari kontrak. Pastikan spelling terhadap PandaPanel\Support\CssHooks::HOOKS sebelum menyalahkan build.
6. Class yang dibangun melalui interpolasi tidak ada di bundle
Penyebab. bg-${color}-100 tidak terlihat oleh compiler, sehingga class tersebut tidak pernah dibuat. Kegagalan ini diam-diam, sehingga panel menuliskan setiap Tailwind class secara literal dan memetakan token ke literal pada beberapa tempat.
| Value | Dipetakan di | Record literal |
|---|---|---|
| Badge color | @/panel/palette | BADGE_CLASSES, ICON_CLASSES, SELECTED_CLASSES |
| Grid column dan span | @/panel/lib/grid | GRID_CLASSES, MD_SPAN_CLASSES, LG_SPAN_CLASSES |
| Widget column span | panel/widgets/WidgetGrid.vue | SPAN_CLASSES |
| Content width | panel/composables/usePanel.ts | MAX_WIDTH_CLASSES |
import { BADGE_CLASSES, ICON_CLASSES, SELECTED_CLASSES } from '@/panel/palette';
import type { BadgeColorName } from '@/panel/palette';
// 'neutral' | 'success' | 'warning' | 'danger' | 'info'
BADGE_CLASSES.success;
// 'bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300'2
3
4
5
6
// usePanel.ts
const MAX_WIDTH_CLASSES = {
full: 'max-w-full',
'7xl': 'max-w-7xl',
'6xl': 'max-w-6xl',
'5xl': 'max-w-5xl',
'4xl': 'max-w-4xl',
'3xl': 'max-w-3xl',
} as const;2
3
4
5
6
7
8
9
Itulah sebabnya maxContentWidth('5xl') adalah token, bukan class, dan BadgeColor merupakan closed enum di server. Value di luar record akan fallback—max-w-full untuk width dan neutral badge untuk color—alih-alih menghasilkan class yang tidak tersedia.
Gunakan pola yang sama untuk custom component. Custom column atau widget yang membangun class dari server value memerlukan literal record:
const STATUS_CLASSES = {
draft: 'bg-muted text-muted-foreground',
live: 'bg-emerald-100 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300',
} as const;
const classes = STATUS_CLASSES[status] ?? STATUS_CLASSES.draft;2
3
4
5
6
Dua value yang tampak seperti class tetapi sengaja bukan class: width dan collapsedWidth milik sidebar, yang merupakan CSS length pada --sidebar-width dan --sidebar-width-icon, serta width() milik table column yang diterapkan inline untuk alasan yang sama.
7. Utility dark: tidak bekerja
Penyebab. Variant didefinisikan sebagai class descendant, bukan media query:
@custom-variant dark (&:is(.dark *));Ada tiga konsekuensi:
.darkharus berada pada ancestor. ComposableuseAppearancedari starter kit menempatkannya pada<html>, sehingga light/dark toggle panel bekerja tanpa panel harus mengelolanya sendiri. Aplikasi yang menghapus composable tersebut, atau memakai atributdata-theme, tidak mendapatkan styling dark milik panel.- Elemen yang membawa
.darktidak mencocokkan dirinya sendiri.&:is(.dark *)adalah descendant selector. Meletakkan.darklangsung pada elemen yang sedang distyling tidak mengaktifkan dark style untuk elemen itu sendiri. prefers-color-schemetidak dibaca langsung. Class adalah satu-satunya signal; pengaturan OS hanya sampai ke panel melalui mekanisme aplikasi yang menulis class tersebut.
Dua palette yang dipilih merupakan block custom property biasa pada stylesheet yang sama—:root untuk light dan .dark untuk dark—termasuk seluruh token --sidebar-* yang dibaca shell.
8. Pemisah frozen column tidak terlihat
Gejala. Table dengan pinned column dapat discroll tetapi column lain melintas di bawah frozen group tanpa batas visual.
Penyebab. .panel-table-frozen-edge merupakan component class di panda-panel.css. Aplikasi yang mempertahankan app.css sendiri tetapi tidak menyalin block tersebut memiliki class di markup tanpa rule CSS yang mengaturnya.
@layer components {
.panel-table-frozen-edge {
position: relative;
}
.panel-table-frozen-edge::after {
content: '';
position: absolute;
top: 0;
bottom: 0;
width: 0.75rem;
pointer-events: none;
}
/* Pinned left: the seam sits on the right of the cell. */
.panel-table-frozen-edge:not([style*='right'])::after {
left: 100%;
border-left: 1px solid var(--border);
background: linear-gradient(to right, rgb(0 0 0 / 0.06), transparent);
}
/* Pinned right: mirrored. */
.panel-table-frozen-edge[style*='right']::after {
right: 100%;
border-right: 1px solid var(--border);
background: linear-gradient(to left, rgb(0 0 0 / 0.06), transparent);
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Pemisah menggunakan pseudo-element, bukan border, karena border akan menggeser content cell satu pixel ketika table mulai discroll. Tujuan marker justru memberi batas visual tanpa membuat bagian lain terlihat bergerak.
Isi stylesheet
Ada lima bagian, sesuai urutan file. Semuanya dipublish sehingga semuanya dapat diedit oleh aplikasi.
| Bagian | Isi |
|---|---|
| Import dan source | @import 'tailwindcss', @import 'tw-animate-css', dua baris @source |
| Dark variant | @custom-variant dark (&:is(.dark *)) |
| Theme mapping | @theme inline { … } — pemetaan token ke utility |
| Palette | :root { … } dan .dark { … }, ditambah compatibility block border-color di @layer base dan font block di @layer utilities |
| Component layer | .panel-table-frozen-edge |
Family yang dipetakan: background, foreground, card, popover, primary, secondary, muted, accent, destructive, border, input, ring, chart-1 sampai chart-5, delapan warna sidebar-*, serta --font-sans dan tiga tingkat radius yang diturunkan dari --radius.
Dua baris @source menambahkan lokasi yang tidak dijangkau automatic detection Tailwind: pagination view milik Laravel di vendor/ dan compiled Blade view di storage/framework/views. Kedua path relatif terhadap stylesheet, sehingga memindahkan file berarti path tersebut juga harus diperbaiki.
Setelah package diupgrade
Copy hasil publish tidak memperbarui dirinya sendiri, dan stylesheet merupakan file yang paling mungkin pernah diedit:
php artisan panel:assets # what is behind, what you changed, what conflicts
php artisan panel:assets --update # write only the files you have never touched
npm run build2
3
Stylesheet yang Anda edit dilaporkan sebagai yours dan dibiarkan, yang juga berarti perbaikan upstream tidak masuk otomatis. Jika file berubah di kedua sisi, statusnya CONFLICT dan tidak ditulis—lihat konflik asset.
Catatan
- Warna yang dibuang tidak menghasilkan warning, begitu juga nama hook yang tidak dikenal. Periksa
getTheme()dangetCssHooks()sebelum mencurigai build. colors()tidak membutuhkan rebuild;cssHooks()biasanya membutuhkan rebuild. Yang pertama mengubah custom property value, yang kedua mengubah class name—dan class harus sudah ada dalam bundle.--sidebardan--sidebar-backgroundadalah property berbeda. Theme block memetakan--color-sidebarke--sidebar-background, sementara:rootmendefinisikan keduanya.colors(['sidebar' => …])menetapkan--sidebar.- Mengedit
:rootdipanda-panel.cssmengubah seluruh aplikasi, termasuk screen starter kit. Warna per-panel sebaiknya berada dicolors()atau stylesheet khusus panel. tw-animate-cssadalah dependency stylesheet, bukan optional dependency.panel:installakan melaporkannya jika aplikasi belum mendeklarasikannya.- Hook
shelldan theme diterapkan pada elemen yang sama. Hook class pada elemen tersebut dapat menggunakan custom property panel yang sudah berada dalam scope. - Package tidak mengirim compiled CSS.
vendor/chocoalano/panelhanya membawa source; setiap layar panel bergantung pada build aplikasi. vite.config.tsmilik package bukan konfigurasi aplikasi Anda. File tersebut membangun generated glob terhadap seluruh tree kebuild/frontend, tanpa minify, dan output-nya tidak dikonsumsi aplikasi—itu compile check, bukan deliverable.
Lihat juga
- Theme Tailwind, CSS hooks
- Struktur asset hasil publish, memperbarui published assets, component tree
- Asset panel, branding, layout
- Frontend requirements, compatibility matrix
- Frontend build, production checklist
- Frontend assets, component registry
- Error build Vite, konflik asset, host module yang hilang, icon yang tidak tampil
panel:assets,panel:install