Open source · MIT

Filament Progress Bar

A polished, dark-mode-aware progress bar column and infolist entry for Filament 5. Ships with three-tier coloring out of the box, ascending/descending threshold directions for low-is-bad metrics, and an advanced threshold-map mode for any number of states.

PHP ^8.2 Filament ^5.0
$ composer require devletes/filament-progress-bar
GitHub
  • Single shared API across ProgressBarColumn and ProgressBarEntry
  • Three-tier (success / warning / danger) coloring out of the box, with custom colors via CSS values
  • ascending and descending threshold 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

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:

RoleAccepted keys
Current valueprogress, current, value, used
Totaltotal, 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.

Sizes (light)Sizes (dark)

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.
Text position (light)Text position (dark)

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.

Visibility (light)Visibility (dark)

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).

Border radius (light)Border radius (dark)

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)

successwarningdanger, with thresholds you can tune individually:

ProgressBarColumn::make('cpu')
    ->warningThreshold(70)   // ≥ 70% → warning
    ->dangerThreshold(90);   // ≥ 90% → danger

Defaults: warning = 70, danger = 90.

Default three-state thresholds (light)Default three-state thresholds (dark)

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.

Descending direction (light)Descending direction (dark)

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.

Threshold map (light)Threshold map (dark)

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);
Battery recipe (light)Battery recipe (dark)

Multi-state quality score (using Filament’s built-in colors):

ProgressBarColumn::make('quality')
    ->thresholds([
        90 => 'success',
        70 => 'info',
        40 => 'warning',
        0  => 'danger',
    ]);
Quality recipe (light)Quality recipe (dark)

Squared bars matching a card design system:

ProgressBarColumn::make('used')
    ->maxValue(fn ($record) => $record->quota)
    ->borderRadius('4px')
    ->size('md');
Squared bars recipe (light)Squared bars recipe (dark)

Compact column without any text overlay:

ProgressBarColumn::make('leave_progress')
    ->state(fn ($record) => [
        'progress' => $record->leave_used,
        'total' => $record->leave_total,
    ])
    ->hideProgressValue()
    ->hidePercentage();
Compact recipe (light)Compact recipe (dark)

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

MethodNotes
maxValue(int|float|Closure|null)Used with a numeric state value
state(...) / getStateUsing(...)Filament native; pass a structured array for current+total

Display

MethodNotes
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

MethodNotes
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

MethodNotes
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)

MethodNotes
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:

Table integration (light)Table integration (dark)

A ProgressBarEntry inside an infolist with icons, custom labels, and a descending-threshold “Stock remaining” bar:

Infolist integration (light)Infolist integration (dark)

Need something custom?

If you’d like custom features, extensions, or a tailored variant of this package built for your project, reach out at salman@devletes.com.