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

Nova Progressbar Field Laravel Package

signifly/nova-progressbar-field

A Laravel Nova field that displays values as a customizable progress bar, making percentages and completion metrics easy to visualize in Nova resources. Simple to install and configure.

View on GitHub
Deep Wiki
Context7

Getting Started

Before you install anything, check your Nova version. This is the single decision that determines whether the rest of this document is relevant. The package's last release was September 2019, which places it squarely in the Nova 1.x/2.x / Vue 2 / Laravel Mix era. On Nova 4+ (Vue 3 / Vite) the compiled component will not mount — you get a blank cell, an unknown custom element, or asset compile noise, not a helpful error.

composer why-not laravel/nova signifly/nova-progressbar-field
# or, without installing:
composer show signifly/nova-progressbar-field --all

If the constraints exclude your Nova major, stop here and reimplement. It is a PHP field class plus a small Vue component — roughly 50 lines plus a single-file component. Owning that is cheaper than owning an archived dependency you cannot upgrade.

If compatible, the install is three steps:

composer require signifly/nova-progressbar-field
php artisan vendor:publish --provider="Signifly\NovaProgressbarField\FieldServiceProvider"

Then register the compiled asset in NovaServiceProvider::boot() if the package does not auto-register it:

Nova::script('nova-progressbar-field', __DIR__.'/../vendor/signifly/nova-progressbar-field/dist/js/field.js');

Where to look first: read src/ProgressBar.php and the Vue component in resources/js/ before writing a single resource line. The README is thin and the package is unmaintained; the source is the only accurate specification of the API surface, the prop names the component actually reads, and — critically — whether the width is clamped. Do this before you trust any method name in a blog post or in this document.

First use case: a read-only completion bar on an index or detail view for a value you already store.

use Signifly\NovaProgressbarField\ProgressBar;

public function fields(Request $request)
{
    return [
        ProgressBar::make('Completion')
            ->resolveUsing(fn ($resource) => $resource->completion_percentage)
            ->min(0)
            ->max(100),
    ];
}

Point it at a computed accessor, not a raw column, and confirm the bar renders before you add a second one. One field instance per metric — do not try to multiplex several bars through a single field.

Implementation Patterns

Resolve to a computed value, never to a raw nullable column. The bar is a presentational widget; the number behind it should be owned by the model.

// app/Models/Onboarding.php
public function getCompletionAttribute(): int
{
    $total = $this->tasks()->count();

    return $total === 0
        ? 0
        : (int) round($this->completedTasks()->count() / $total * 100);
}

The $total === 0 guard is not optional. A division over an empty collection yields NAN or throws, and the result is either an uncaught exception or a bar that silently renders nothing — both of which you will misdiagnose as a front-end problem.

Clamp in PHP, not in the component. Assume the component does width: {value}% with no bounds check. Negative values, values over the max, and high-precision floats will produce overflowing or visually broken bars.

protected function progress(array $metrics): int
{
    $percentage = $metrics['used'] / max($metrics['capacity'], 1) * 100;

    return (int) max(0, min(100, round($percentage)));
}

Eager-load anything the resolver touches, or you will N+1 the index view. A resolver that counts a relation runs once per row.

public function indexQuery(NovaRequest $request, $query)
{
    return $query->withCount(['tasks', 'completedTasks']);
}

Then resolve against the loaded counts rather than lazy-loading:

ProgressBar::make('Completion')
    ->resolveUsing(function ($model) {
        $total = $model->tasks_count;

        return $total === 0 ? 0 : (int) round($model->completed_tasks_count / $total * 100);
    })
    ->min(0)
    ->max(100);

Prefer a model accessor over a per-row closure whenever the same metric appears in more than one resource — it centralises the denominator guard and keeps index and detail views from drifting apart.

Use thresholds at the field level, not in the component. Since the package gives you a color, drive it from the resolved value:

ProgressBar::make('SLA Burn')
    ->resolveUsing(fn ($model) => $model->sla_burn)
    ->min(0)->max(100)
    ->color($model->sla_burn > 80 ? '#e3342f' : '#38c172');

Verify the exact method name and whether it accepts a closure in src/ProgressBar.php — this is precisely the kind of thing the README does not pin down.

Keep the package's PHP class, replace its component. If the field class is adequate but the front-end is unusable (stale build, wrong Nova version, a11y requirements), point it at your own component without forking the PHP:

ProgressBar::make('Completion')->component('my-progress-bar')

That is the cheapest escape hatch and preserves the fluent API you have already written into a dozen resources.

Pair with a Number field on detail views. The bar answers "how full"; the number answers "how full, exactly". A read-only progress bar with no adjacent figure is a poor interface for anything an operator has to act on.

Document the intended semantics next to the field. Resolvers drift; bars keep rendering plausible-looking numbers after the underlying metric changes. A one-line comment stating what 100% means and where the denominator comes from is the cheapest insurance you have.

Gotchas and Tips

A blank field is almost never a Vue bug. Work the ladder in this order: (1) is your Nova major compatible with a 2019 Vue 2 component; (2) is the script registered and is the compiled asset actually present at that path; (3) what does /nova-api/{resource}/{id} return for the field — if the JSON has a value and the cell is empty, it is front-end; if the JSON has null, it is your resolver. Check the API response first; it collapses the problem in one request.

displayUsing() does nothing here. It formats text, not bar width. Expect at least one "the formatting doesn't work" ticket from a developer reaching for the wrong tool.

Out-of-range values overflow rather than being clamped. -4, 150, and 87.333333 all render as something broken or imprecise. Clamp and cast to int in PHP before the value ever reaches the field.

Null is not zero. A null resolved value typically renders as an empty bar, which reads to an operator as "0% complete" — a materially different and often wrong statement. Decide explicitly: hide the field with ->hideWhenUpdating()/->canSee(), or coerce to 0 at the resolver, and write down which.

Stale compiled assets after any rebuild. Nova caches hashed asset names. After forking, rebuilding, or reinstalling, run php artisan nova:publish, clear caches, and hard-refresh the browser. A stale manifest is the most common false negative in this whole workflow.

Do not ship it as an unmodified archived dependency. If you keep the package, fork it into an internal namespace behind a path repository or private VCS, preserve the original license header, and add a replace entry so future maintainers can see what happened. If you rebuild, copy src/ProgressBar.php as the starting point — it is small enough to read in one sitting.

Add the field to your Nova upgrade checklist. This is the actual long-term cost of adoption: every Nova major bump requires revalidating that the component mounts and the assets still build. Without a regression check, a Nova upgrade silently blanks a corner of the admin and nobody notices for a month.

Write the three tests the package does not have. Zero denominator returns 0; over-max clamps to max; negative clamps to 0. Three assertions that cover the entire failure surface of the field's business logic.

Do not use it for input. It is read-only by design. Editable percentages belong in a Number field with ->min(0)->max(100) and server-side validation; a progress bar that accepts input is a bug.

When in doubt, rebuild. With 5 stars, 0 dependents, an archived repo, and a 2019 release, the honest default is a first-party field: your PHP class, your Vue component (Vue 3 for Nova 4+), your asset pipeline, your clamping rules, your ownership. The package's best use is as a working reference implementation to copy from, not as a dependency to carry.

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.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle