devletes / filament-progress-bar
Reusable progress bar components for Filament 5 tables and infolists.
Requires
- php: ^8.2
- filament/filament: ^5.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- laravel/pint: ^1.18
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.8
- pestphp/pest-plugin-laravel: ^3.0
README
Reusable progress bar components for Filament 5 tables and infolists.
- Single shared API across
ProgressBarColumnandProgressBarEntry - Three-tier (success / warning / danger) coloring out of the box, with custom colors via CSS values
ascendinganddescendingthreshold directions for low-is-bad metrics (fuel, stock, battery)- Optional threshold map for any number of states with custom names
- Three sizes, two text positions, custom border radius
- Self-contained stylesheet, dark-mode aware
- Proper
role="progressbar"semantics
Requirements
- PHP
^8.2 - Filament
^5.0
Installation
composer require devletes/filament-progress-bar
Then publish Filament assets so the package stylesheet is available to your panel:
php artisan filament:assets
The package ships its own stylesheet via Filament's asset manager — there is nothing to add to your custom Tailwind theme.
Quick start
Table column
use Devletes\FilamentProgressBar\Tables\Columns\ProgressBarColumn; ProgressBarColumn::make('used') ->maxValue(fn ($record) => $record->quota) ->showProgressValue() ->showPercentage();
Infolist entry
use Devletes\FilamentProgressBar\Infolists\Components\ProgressBarEntry; ProgressBarEntry::make('leave_progress') ->label('Sick Leave') ->icon('heroicon-o-heart') ->iconColor('primary') ->getStateUsing(fn ($record) => [ 'progress' => $record->leave_used, 'total' => $record->leave_total, ]);
Providing data
The component accepts two styles of state:
1. A numeric value paired with maxValue(...):
ProgressBarColumn::make('used') ->maxValue(fn ($record) => $record->quota);
2. A structured array containing both the current value and the total:
ProgressBarColumn::make('leave_progress') ->state(fn ($record) => [ 'progress' => $record->used_days, 'total' => $record->allocated_days, ]);
When the structured form is used, both keys are looked up flexibly. The first matching key wins:
| Role | Accepted keys |
|---|---|
| Current value | progress, current, value, used |
| Total | total, max, available, quota |
A missing or zero total resolves to 0% rather than throwing.
Display
Size
Method: size('sm' | 'md' | 'lg')
Controls the bar height and inside-text size. Defaults to sm. Invalid values fall back to the default.
![]() |
![]() |
Text position
Method: textPosition('inside' | 'outside')
inside(default): the value/percentage is rendered on top of the fill, centered horizontally. Best for compact rows where you want the number visually anchored to the bar.outside: the value/percentage is rendered in a row beneath the bar, right-aligned. Best when you want a clean bar visual without text overlap, or when the value would be hard to read at low percentages.
![]() |
![]() |
Show / hide value & percentage
Methods: showPercentage() / hidePercentage() / showProgressValue() / hideProgressValue()
Toggle each portion of the display text independently. When both are visible the text reads value / max (percentage). When only one is visible it appears alone. When both are hidden the bar renders without any text.
![]() |
![]() |
Border radius
Method: borderRadius(string $value)
Override the bar's corner radius with any valid CSS length — pixels, rems, percentages, custom properties, or calc() expressions:
->borderRadius('4px') ->borderRadius('0.5rem') ->borderRadius('calc(var(--radius) * 2)')
Pass null (or omit the call) to keep the default pill shape (9999px).
![]() |
![]() |
Values containing
;,<,>,{,}, or quotes are silently dropped to prevent inline-style injection. Stick to standard CSS length syntax.
Thresholds and coloring
The bar resolves a status from the percentage and renders a color for that status. Two threshold APIs are available — start with the simple one and reach for the map only when three states aren't enough.
Three-state mode (default)
success → warning → danger, with thresholds you can tune individually:
ProgressBarColumn::make('cpu') ->warningThreshold(70) // ≥ 70% → warning ->dangerThreshold(90); // ≥ 90% → danger
Defaults: warning = 70, danger = 90.
![]() |
![]() |
Threshold direction
By default higher percentages escalate toward danger (CPU, memory, used quota). For metrics where lower is worse (fuel, stock, battery), flip the direction:
ProgressBarColumn::make('battery_percent') ->thresholdDirection('descending') ->warningThreshold(30) // ≤ 30% → warning ->dangerThreshold(10); // ≤ 10% → danger
In descending mode the package uses sane defaults (warning = 30, danger = 10) and clamps danger ≤ warning.
![]() |
![]() |
Threshold map (advanced)
For more than three states, or for non-monotonic mappings, pass a map of floor => status. The status name can be anything you want:
ProgressBarColumn::make('score') ->thresholds([ 80 => 'success', 60 => 'warning', 40 => 'info', 0 => 'danger', ]);
Keys are interpreted as percentage floors — the highest matching floor wins. The example above maps ≥80 to success, 60–79 to warning, 40–59 to info, and <40 to danger.
![]() |
![]() |
If you omit a 0 floor, the lowest-defined status extends down to 0. For example, [80 => 'success', 10 => 'info'] resolves 0–79 to info (no implicit success fallback below the lowest floor).
The three legacy statuses — success, warning, danger — keep their package defaults (var(--primary-500), var(--warning-500), var(--danger-500)). Any other status name (e.g. info, gray, secondary, or a custom one registered via your panel's ->colors([...])) auto-resolves to var(--{status}-500). No extra configuration needed.
For one-off colors that don't map to a Filament color (e.g. you want a specific 'excellent' status to be a particular shade of green), use statusColors([...]):
->statusColors([ 'excellent' => 'rgb(16 185 129)', ])
Likewise, custom labels for any status name go through statusLabels([...]):
->statusLabels([ 'info' => fn (int $percentage) => "Watch ({$percentage}%)", ])
Colors
Three named setters cover the default statuses. Each accepts any CSS color value or a closure:
->successColor('rgb(16 185 129)') ->warningColor(fn ($record) => $record->is_critical ? '#ff0000' : 'orange') ->dangerColor('var(--my-danger-color)')
Defaults reference Filament's CSS variables (var(--primary-500), var(--warning-500), var(--danger-500)), so the bar inherits your panel's theme automatically.
For custom statuses (map mode), use statusColors([...]). When both statusColors and the named setters define the same status, the map wins — named setters fill in any keys the map omits.
Labels
Per-status labels are shown above the bar in infolist entries only (table columns intentionally skip them for compact rows). They accept a string or a closure receiving useful context:
->successLabel('On track') ->warningLabel(fn (int $percentage) => "Watch ({$percentage}%)") ->dangerLabel(fn (float $current, ?float $total) => "{$current} / {$total} used")
For custom statuses (map mode), use statusLabels([...]).
Closure parameters
Most setters accept a closure. The following parameters are injected when present:
$state— the raw column/entry state$record— the Eloquent model$current— the resolved current value (float)$total— the resolved total (float|null)$percentage— the integer percentage (0–100)$status— the resolved status name
The percentage- and status-aware parameters are populated after the bar's value has been resolved, so closures used for colors and labels can branch on the final percentage.
Recipes
Battery / fuel (low is bad):
ProgressBarColumn::make('battery_percent') ->thresholdDirection('descending') ->warningThreshold(30) ->dangerThreshold(10);
![]() |
![]() |
Multi-state quality score (using Filament's built-in colors):
ProgressBarColumn::make('quality') ->thresholds([ 90 => 'success', 70 => 'info', 40 => 'warning', 0 => 'danger', ]);
![]() |
![]() |
Squared bars matching a card design system:
ProgressBarColumn::make('used') ->maxValue(fn ($record) => $record->quota) ->borderRadius('4px') ->size('md');
![]() |
![]() |
Compact column without any text overlay:
ProgressBarColumn::make('leave_progress') ->state(fn ($record) => [ 'progress' => $record->leave_used, 'total' => $record->leave_total, ]) ->hideProgressValue() ->hidePercentage();
![]() |
![]() |
Dynamic per-record color:
ProgressBarColumn::make('progress') ->successColor(fn ($record) => $record->is_priority ? '#7c3aed' : null);
Infolist entry with icon, inline label, and rich danger text:
ProgressBarEntry::make('inventory') ->label('Stock remaining') ->icon('heroicon-o-cube') ->iconColor('primary') ->inlineLabel() ->getStateUsing(fn ($record) => [ 'progress' => $record->stock, 'total' => $record->capacity, ]) ->thresholdDirection('descending') ->warningThreshold(40) ->dangerThreshold(15) ->dangerLabel(fn (float $current, ?float $total) => "Only {$current} left of {$total}");
API reference
Value
| Method | Notes |
|---|---|
maxValue(int|float|Closure|null) |
Used with a numeric state value |
state(...) / getStateUsing(...) |
Filament native; pass a structured array for current+total |
Display
| Method | Notes |
|---|---|
size('sm'|'md'|'lg') |
Default sm |
textPosition('inside'|'outside') |
Default inside |
showPercentage(bool|Closure) / hidePercentage(bool|Closure) |
Default shown |
showProgressValue(bool|Closure) / hideProgressValue(bool|Closure) |
Default shown |
borderRadius(string|Closure|null) |
Any CSS length; default pill |
Thresholds
| Method | Notes |
|---|---|
warningThreshold(int|float|Closure) |
Default 70 (or 30 in descending) |
dangerThreshold(int|float|Closure) |
Default 90 (or 10 in descending) |
thresholdDirection('ascending'|'descending'|Closure) |
Default ascending |
thresholds(array|Closure) |
Tier overrides OR a [floor => status] map |
Colors
| Method | Notes |
|---|---|
successColor(string|Closure|null) |
Default var(--primary-500) |
warningColor(string|Closure|null) |
Default var(--warning-500) |
dangerColor(string|Closure|null) |
Default var(--danger-500) |
statusColors(array|Closure|null) |
Map of status => color for custom statuses |
Labels (infolist entries only)
| Method | Notes |
|---|---|
successLabel(string|Closure|null) |
Hidden by default |
warningLabel(string|Closure|null) |
Hidden by default |
dangerLabel(string|Closure|null) |
Hidden by default |
statusLabels(array|Closure|null) |
Map of status => label for custom statuses |
Infolist-only
The ProgressBarEntry extends Filament's Entry and supports the standard label(...), inlineLabel(), hiddenLabel(), icon(...), and iconColor(...) methods.
Integration examples
A ProgressBarColumn rendered inside a Filament resource table:
![]() |
![]() |
A ProgressBarEntry inside an infolist with icons, custom labels, and a descending-threshold "Stock remaining" bar:
![]() |
![]() |
Need something custom?
We build production Filament panels and plugins for teams that want to ship fast without compromising on polish. If you need a custom feature, an extended variant of this package, or a fully bespoke component built for your stack, we can help.
- Browse the rest of our Filament work: filament.devletes.com
- Get in touch: salman@devletes.com
Typical engagements: new Filament plugins, custom resources/widgets/actions, theme + UX work, integrations with your existing services, and one-off tailored forks of our open-source packages.
Credits
License
MIT. See LICENSE.md.

























