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 Radio Field Laravel Package

norman-huth/nova-radio-field

A Laravel Nova radio field that lets you present selectable options as radio buttons inside forms. Provides a simple, native-feeling alternative to dropdowns for single-choice fields, making options visible at a glance and improving clarity for small sets of choices.

View on GitHub
Deep Wiki
Context7

Getting Started

Before you write a line of resource code, check two things in this order.

  1. What Nova major are you on? Look at laravel/nova in composer.lock. Nova 4.27+/5.x ship a native Radio field. If you have it, use it — this package is archived (last release December 2023) and adds nothing you don't already own.
  2. What is the real namespace? The Composer name, the GitHub org (Muetze42), and the PHP namespace (NormanHuth\...) do not agree. Open vendor/norman-huth/nova-radio-field/src/ and read the actual class and autoload.psr-4 block before you write a use statement. A wrong guess produces a class-not-found deep inside a resource definition, which is a miserable place to debug from.

Install (dependency route, only if policy requires it):

composer require norman-huth/nova-radio-field   # pin an exact version in composer.json
php artisan nova:publish

composer require succeeding tells you nothing about whether the field works. The package ships a compiled dist/ bundle, and a prebuilt bundle compiled against a different Nova minor can load a 404 or blow up at runtime while the PHP side is perfectly fine. Open the browser, watch the network tab for dist/js/field.js, and watch the console before you debug anything in PHP.

The vendor-first route is usually better here. The MIT license lets you copy the field class and its Vue component into app/Nova/Fields/ with the copyright notice intact. That converts an abandoned remote dependency into first-party code you can lint, test, and patch without a supply-chain tail. Budget roughly a day including the test matrix below — that is the real cost of either route.

First use case: one resource, one genuinely bounded enum (2–5 values), on the form and detail views only.

use NormanHuth\NovaRadioField\Radio; // verify against src/

public function fields(NovaRequest $request): array
{
    return [
        Radio::make('Priority', 'priority')
            ->options([
                'low'    => 'Low',
                'normal' => 'Normal',
                'high'   => 'High',
            ])
            ->default('normal')
            ->rules('required', 'in:low,normal,high'),
    ];
}

No migration, no cast, no table. The stored value is an ordinary string column; the field only swaps the input widget and the detail-view rendering.

Implementation Patterns

Options live in one place, always. The recurring bug with every radio field is drift between the options() array, the validation rule, and whatever the model does with the value. Back the field with an enum:

enum TicketPriority: string
{
    case Low = 'low';
    case Normal = 'normal';
    case High = 'high';

    public static function options(): array
    {
        return collect(self::cases())
            ->mapWithKeys(fn ($c) => [$c->value => $c->name])
            ->all();
    }
}

Then ->options(TicketPriority::options()) in the resource and Rule::enum(TicketPriority::class) in the FormRequest. One definition, three consumers, zero drift.

Wrap it in a named subclass once more than one resource needs the same enum. This is also the exact shape of the vendored option — copy upstream, rename into your namespace, add an attribution header:

namespace App\Nova\Fields;

class PriorityRadio extends Radio
{
    public function __construct($name = 'Priority', $attribute = null, $resolveCallback = null)
    {
        parent::__construct($name ?? 'Priority', $attribute, $resolveCallback);
        $this->options(TicketPriority::options())
             ->default(TicketPriority::Normal->value);
    }
}

Now the resource reads PriorityRadio::make('Priority') and the options, default, and label set are identical everywhere.

Server-side validation is the trust boundary, not the field. ->rules() on a Nova field is a UX affordance and a client hint. A crafted request bypasses it. Enforce the same constraint in the FormRequest (or Resource::validationRules if you don't use FormRequests) with Rule::enum(...) or in:....

Detail and index behave differently — verify, don't assume. The package resolves the label on the detail view. The index column often renders the raw stored value. If operators need the label in the index too:

->displayUsing(fn ($value) => TicketPriority::tryFrom((string) $value)?->name)

Test the index rendering separately from the detail rendering; they are different code paths.

Nullable and default are not the same thing. A radio has no inherent "empty" state. If the column is nullable and the field is optional, add an explicit empty option and assert what gets persisted when nothing is picked — null, '', or a missing key from the request. Write the expected behavior down; an archived package will never document it for you, and the difference matters to downstream queries.

Conditional fields are the classic failure surface. If the resource uses dependsOn/DependsOn or ->hide()/->show() driven by other fields, verify the radio re-renders and re-validates correctly when it appears and disappears. This is precisely the class of bug upstream will not fix, and it is common enough that you should include it in your staging spike by default.

Roll out behind one resource. Ship on a single resource, run it through one full deploy cycle, then widen. The rollback is a one-line change back to Select, and the data stays valid because it's a plain column. That reversibility is the strongest reason to try it at all.

Gotchas and Tips

  • Archived means the maintenance clock starts on install day. No CVE pipeline, no issue triage, no compatibility fix when a future Nova bump changes the field contract or asset pipeline. If you depend on it, pin an exact version in composer.json (no caret, no *) and keep a vendored source copy next to the lockfile so builds never need a remote that can vanish.
  • The asset bundle fails far more often than the PHP class. dist/js/field.js was built against some Nova minor and some Vue/Mix-or-Vite toolchain. If the field renders blank or the console throws, it's the bundle — not your resource. php artisan nova:publish copies files, it does not rebuild them.
  • Numeric option keys get stringified by HTML. Radio <input value> attributes are strings. options([0 => 'Draft', 1 => 'Live']) submits "0" and "1". On MySQL in strict mode or on Postgres this can fail the insert or the enum cast. Use string enum values, or cast explicitly in the model.
  • Don't pass a list as options. options(['Low', 'Normal', 'High']) gives you numeric keys and the labels become the values. Always pass an explicit key => label map.
  • Radios do not scale. Past roughly 5–7 options they dominate the form and are slower to scan than a <select>. Large or dynamic option sets (countries, categories) want a searchable select, not this.
  • Test the full matrix explicitly: create, update, detail, index, conditional visibility, and null/default save-and-reload. Put it in the PR as a checklist — for an archived component, that checklist is the documentation.
  • Standard Nova field helpers work — hideFromIndex, readonly, help, rules, default, resolveUsing all come from the base Field. Reach for them before reaching for custom code; most "the field doesn't support X" complaints resolve to a base-class method.
  • If you vendor, inventory it. Keep the MIT notice in the file header, add the class to your internal component list with a named owner and an explicit retirement trigger ("delete when the native Nova radio covers our Nova version"). A vendored file with no owner becomes archaeology in eighteen months.
  • Low dependents is not low risk. Zero downstream users means nobody else has absorbed the discovery cost for this field's quirks. Everything you learn here, you learn first-hand.
  • The exit is cheap by design. Values are ordinary columns, so swapping back to Select or a native Radio is a one-line resource change with no data migration. Scope the decision accordingly — it deserves an ADR paragraph, not a sprint.
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.
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
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin