awcodes/dimmer
A lightweight Filament plugin from awcodes that adds a dimmer overlay to your panel, letting you fade or spotlight areas of the UI so key content stands out. Simple to install and easy to drop into any Livewire view.
Dimmer is a small utility package for dimming the rest of the page while spotlighting a single element — useful for onboarding tours, "look here" callouts, and surfacing the first invalid form field. It is a thin service provider plus Blade/Alpine assets, so there is very little to wire up.
composer require awcodes/dimmer
Then, if you want to tweak behaviour locally:
php artisan vendor:publish --tag="dimmer-config"
php artisan vendor:publish --tag="dimmer-views" # only if you truly need to edit markup
Look here first, in this order:
README.md — the public API surface is tiny and the README is the canonical reference for component/attribute names.src/DimmerServiceProvider.php — tells you which views, config keys, and asset tags exist. If a feature "doesn't work", confirm the provider is auto-discovered (composer dump-autoload after odd upgrades).resources/views/ — the Blade component. Reading it is faster than guessing at which attributes are forwarded to the root element.config/dimmer.php — defaults for overlay opacity, transitions, and class names (key names vary by version; read the shipped file rather than copying snippets from blog posts).First use case — spotlight a target on click. Give the element you want to highlight an ID, drop the Dimmer component into your layout, and drive it from Alpine state:
<div x-data="{ spotlight: false }">
<x-dimmer :active="spotlight" target="#new-filter" />
<button type="button" @click="spotlight = true">What's this?</button>
<select id="new-filter" wire:model.live="filter">…</select>
</div>
Verify in the browser that the overlay renders above your content and below the target. That single check catches most integration problems.
Declarative toggling via Alpine. The package is Alpine-first; the happy path is Boolean state in x-data bound to the component's active prop. Keep the state on a wrapper that also contains the target so the target is guaranteed to be in the DOM when the overlay mounts.
Event-driven control for multi-step tours. Rather than threading props through several components, emit a browser event from anywhere (window.dispatchEvent(new CustomEvent('dimmer:show', { detail: { target: '#step-2' } })) or Alpine's $dispatch) and let a single Dimmer instance in the layout listen. Confirm the exact event names in the README before committing — this is the one part of the API worth pinning in a test.
Step sequences. For onboarding, hold an array of selectors in Alpine and advance on "Next":
steps: ['#nav', '#search', '#profile'],
i: 0,
next() { this.i++; this.i < this.steps.length ? this.$dispatch('dimmer:show', { target: this.steps[this.i] }) : this.$dispatch('dimmer:hide') }
Persist completion server-side (a users.onboarded_at column) so you don't re-run the tour on every visit.
Form validation spotlight. After a Livewire/Filament validation round-trip, target the first invalid input:
<x-dimmer :active="$errors->any()" :target="'#' . array_key_first($errors->toArray())" />
This is where Dimmer earns its keep in admin panels — the error summary alone is easy to miss on long forms.
Filament panel integration. Register the package assets on the panel (->assets([...]) in the panel provider) or via a render hook so the overlay exists on every page; then trigger it from a custom action or from $wire after saving. Keeping one Dimmer instance in the panel rather than per-page avoids stacking-context surprises.
Styling without forking. Override the CSS custom properties / classes the package exposes rather than editing published views. If you do publish views, treat them as your code from that point on — vendor:publish --force will silently overwrite your edits on the next upgrade.
Upgrading. Pin ^1.2 in composer.json. v1.2.0 is a non-breaking chore release (it drops an unnecessary illuminate/contracts requirement) — no application code changes are needed to move from 1.1.0. Run composer update awcodes/dimmer and your existing suite is sufficient verification.
transform, filter, perspective, or a z-index creates a new stacking context, and the overlay can end up trapped beneath a sticky header or a Filament topbar. If the dimmer covers everything except the thing you expected it to cover, or covers your nav, look at parent styles before touching the package config.$nextTick() or a requestAnimationFrame.wire:ignore to the target or re-show the overlay on morph.updated. This is the single most common "works once, then breaks" report with overlay packages.Escape to dismiss, honour prefers-reduced-motion by disabling the fade, and make sure the spotlighted content is still keyboard reachable — a full-screen overlay that swallows Tab is a regression you will ship if you don't test with the keyboard.illuminate/contracts. If your app was relying on it arriving via Dimmer, that was always an accident — declare it (or better, rely on laravel/framework) explicitly. composer why illuminate/contracts after upgrading will show you whether anything else still provides it.1 and give it a bright background to confirm it is mounting at all. php artisan about plus a published config file is usually enough to confirm the provider registered.How can I help you explore Laravel packages today?