Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Sticky Header Laravel Package

awcodes/filament-sticky-header

Makes Filament panel page headers sticky on scroll, with an optional floating theme and colored variant. Install via Composer, add the plugin to your panel provider, and import the provided CSS into your theme. Supports Filament 2.x through 5.x depending on package version.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Pick the right version line before you install. The plugin's compatibility matrix is the first thing to get right, because a wrong constraint is a silent waste of an afternoon:

Filament Package
2.x 1.x
3.x 2.x
4.x 3.x or 4.x
4.x & 5.x 4.x (as of v4.2.0)
# Filament 4 or 5
composer require awcodes/filament-sticky-header:^4.0

2. You must already have a custom theme. This plugin ships only a CSS file (resources/css/plugin.css) and a small JS asset; it does not inject styling for you. If you don't have a theme yet, create one first (php artisan make:filament-theme), register it on the panel, and confirm the panel renders with it before touching this package.

// app/Providers/Filament/AdminPanelProvider.php
->viteTheme('resources/css/filament/admin/theme.css')

3. Import the plugin CSS into that theme file.

/* resources/css/filament/admin/theme.css */
@import '../../../../vendor/awcodes/filament-sticky-header/resources/css/plugin.css';

@import 'tailwindcss'; /* keep your existing imports where they are */

Count the ../ from the theme file's real location — the vendor path in the README assumes resources/css/filament/admin/theme.css.

4. Register the plugin on the panel and rebuild assets.

use Awcodes\StickyHeader\StickyHeaderPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        ->viteTheme('resources/css/filament/admin/theme.css')
        ->plugins([
            StickyHeaderPlugin::make(),
        ]);
}
npm run build
php artisan optimize:clear

First use case to validate with: a resource EditRecord page for a long form, or a ListRecords page with many rows. Scroll down — the title, breadcrumb, and the Save/Delete/Create actions should stay reachable. If nothing changes, you have a theme/CSS problem, not a plugin problem (see Gotchas).

Where to look first when you need to customize: vendor/awcodes/filament-sticky-header/resources/css/plugin.css. It is short and readable — the class names there tell you exactly which Filament header selectors are being targeted, which is what you'll need for overrides and for debugging layout collisions.

Implementation Patterns

Baseline: enable once, treat it as panel-level chrome. This is not a per-page plugin; you enable it per panel and then subtract where it hurts. The common production shape:

StickyHeaderPlugin::make()
    ->floating()
    ->colored()
    ->stickOnListPages(false)
    ->disabledOn([
        App\Filament\Pages\DashboardBoard::class,   // kanban / full-bleed canvas
        App\Filament\Pages\ReportViewer::class,     // embedded iframe report
    ])

Per-user preference with closures is the headline feature. Both floating() and colored() accept Closure(): bool. Wire them to a user setting, but guard the unauthenticated case — login, 2FA, and password-reset screens render the panel too:

StickyHeaderPlugin::make()
    ->floating(fn (): bool => (bool) (auth()->user()?->prefers_floating_header ?? false))
    ->colored(fn (): bool => (bool) (auth()->user()?->prefers_colored_header ?? false))
    ->stickOnListPages(fn (): bool => (bool) (auth()->user()?->prefers_sticky_lists ?? true));

Keep these closures cheap — no queries, no service calls. Read from the already-loaded auth()->user() model or a cached settings object, because they run on every panel render.

Multi-panel apps. Register the plugin in each panel you want it in, and branch the closures on the current panel so the admin and the customer portal can behave differently:

->floating(fn (): bool => Filament::getCurrentPanel()?->getId() === 'admin')

Choosing between stickOnListPages(false) and disabledOn():

Need Use
Tables need the vertical space / sticky table headers conflict stickOnListPages(false)
One specific custom page (dashboard canvas, embedded chart, wizard) disabledOn([Page::class])
Runtime list (e.g. only pages flagged in config) disabledOn(fn () => [...])
Per-resource opt-out disabledOn() with the resource's page classes — it takes page class strings, not slugs

Compose with your own theme overrides, not with forks. Put any tuning in the same theme.css, after the @import, so you keep one build and one source of truth:

@import '../../../../vendor/awcodes/filament-sticky-header/resources/css/plugin.css';
@import 'tailwindcss';

/* Give phones their screen back */
@media (max-width: 640px) {
    .fi-header.fi-sticky { position: static; }
}

/* Anchors and in-page jumps shouldn't land under the header */
html { scroll-padding-top: 5rem; }

/* Don't let a sticky bar wreck printed reports */
@media print {
    .fi-header.fi-sticky { position: static; }
}

(Verify the exact selector against plugin.css in the vendor directory before shipping — that file is the contract.)

Workflow pairing with long forms. Sticky headers pay off most on EditRecord pages with a Save action in the header. If you also have a sticky table or sticky column elsewhere, decide per page which one wins rather than letting both fight — that's what disabledOn() is for.

QA checklist for every upgrade. Manual, but short: (1) scroll a long list page, (2) scroll a long edit form and click Save — title must update, (3) click between pages with wire:navigate and confirm the header doesn't double-render or stick wrongly, (4) open a modal from a header action, (5) trigger a notification while scrolled down, (6) check a phone viewport, (7) open a page in disabledOn().

Upgrade routine. composer update alone is not enough — the CSS lives in your compiled theme, so your pipeline must run npm ci && npm run build after every composer change. Watch the changelog specifically for CSS/JS fixes (v2.0.6's wire:navigate re-init, v3.1.0's disabledOn(), the "scope JS asset registration to panel context" fix); those are the releases that change behavior in a compiled-theme setup.

Gotchas and Tips

  • Silent no-op if you skip the theme import. No error, no exception — the header just doesn't stick. Before debugging anything else, confirm the theme is registered and the compiled CSS actually contains the plugin rules: grep -c 'fi-header' public/build/assets/*.css.
  • Wrong relative path in the @import. The README's ../../../../ assumes a specific theme location. If your theme is at resources/css/theme.css, it's ../../../vendor/.... A bad path in a CSS @import is easy to miss; a Vite build warning is usually your only clue.
  • @import ordering. CSS requires @import rules before other statements. If you paste the import at the bottom of your theme file it will be dropped.
  • Stale compiled assets are the #1 "it broke" cause. After composer update, run the frontend build. On deploy, make sure the build step runs after composer, not before.
  • Closures run early and often. auth()->user() can be null on login/2FA pages, and the closure is evaluated on panel render, not on scroll. Null-safe it and keep it side-effect free.
  • disabledOn() wants full class names. MyPage::class, not 'my-page' or a route slug. It also only knows about Filament page classes — nested/custom resource pages need to be listed explicitly, so test them.
  • stickOnListPages(false) only covers list pages. Table filters and sticky-column configurations on other pages can still collide; check those individually.
  • Z-index fights. A fixed header sits in a stacking context shared with notifications, modals, and dropdowns inside the header. Historical releases fixed notification overlay bugs and filter-icon z-index bugs, which is a hint about how fragile this area is. If you bump the header's z-index in your theme, re-test modals and notification toasts — you'll likely cover them.
  • Interplay with a sticky topbar. floating() positions the header relative to the viewport, so if you override topbar height in your theme, the header can overlap or float too high. Set offsets in the same override block, not in a scattered stylesheet.
  • Mobile is a real cost. A sticky header plus a sticky topbar can consume a fifth of a phone screen. CSS media query is the reliable lever; you can't detect viewport width in a server-side closure.
  • wire:navigate re-initialization. Fixed in the 2.x line, but if you add your own JS that observes or mutates .fi-header, you must re-run it on livewire:navigated too, or your tweaks silently die after the first SPA navigation.
  • Dynamic titles. Header titles update after a save (fixed in v2.0.2), but only if your heading lives in the actual header slot. Custom pages that render their own <h1> in the body won't be sticky — that's expected, not a bug.
  • Extension points are deliberately thin. There's no published config, no façade API beyond the four fluent methods, and no documented view publishing. Your levers are: the four plugin methods, CSS overrides in your theme, and your panel provider. Avoid forking the vendor views — the package's small surface is its main asset, and forks are expensive to keep in sync.
  • Low bus factor (83 stars, effectively one maintainer). Pin the version in composer.json, keep your overrides in a handful of lines at the bottom of theme.css, and if the package ever stalls, vendoring plugin.css into your own theme directory is a 30-minute exit ramp.
  • Debugging sequence, in order: panel registered with ->viteTheme() → theme file contains a valid @import → npm run build succeeded → built CSS contains the plugin rules → page source includes the plugin's JS asset → php artisan optimize:clear → inspect .fi-header classes in devtools while scrolled. That order resolves nearly every report.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings