epartment/nova-dependency-container
Laravel Nova package that adds a dependency container for conditionally showing or hiding groups of fields. Use dependsOn() to react to another field's value, keeping forms dynamic and clutter-free without custom JavaScript. Ideal for building cleaner, context-aware Nova forms.
Install
composer require epartment/nova-dependency-container
No config file, no migrations, no publishing required — the service provider is auto-discovered and registers the compiled JS asset with Nova. Add the field to a resource and you're done. Pin at least ^1.3.3 if you're on Nova 3 and share fields() between create/update and fieldsForIndex()/fieldsForDetail() — see the notes on 1.3.3 below.
Where to look first
vendor/epartment/nova-dependency-container/src/NovaDependencyContainer.php — the whole server side is essentially one field class. It stores a dependsOn array in the field's meta and proxies fill/resolve/hydrate to the child fields.vendor/epartment/nova-dependency-container/resources/js/components/DependencyContainer.vue — the visibility logic. It reads the dependsOn meta, watches the live form values for those attributes, and renders (or doesn't render) the slot. If something behaves oddly, this file is the ground truth.First use case — a conditional block on a form:
use Epartment\NovaDependencyContainer\NovaDependencyContainer;
use Laravel\Nova\Fields\Select;
use Laravel\Nova\Fields\Text;
public function fields(NovaRequest $request)
{
return [
Select::make('Type', 'type')->options([
'person' => 'Person',
'company' => 'Company',
]),
NovaDependencyContainer::make([
Text::make('First name', 'first_name'),
Text::make('Last name', 'last_name'),
])->dependsOn('type', 'person'),
NovaDependencyContainer::make([
Text::make('Company name', 'company_name'),
Text::make('VAT number', 'vat_number'),
])->dependsOn('type', 'company'),
];
}
That is ~90% of real-world usage: pick a discriminator field, wrap the conditional fields in a container, done.
Multiple conditions are ANDed. Pass an associative array; every pair must match before the container shows.
NovaDependencyContainer::make([
Text::make('Refund reason', 'refund_reason'),
DateTime::make('Refunded at', 'refunded_at'),
])->dependsOn([
'status' => 'refunded',
'is_manual' => true,
]);
Model "OR" with sibling containers. One container per branch of the condition is clearer than a giant multi-condition container, and it maps one-to-one with the business rule.
NovaDependencyContainer::make([...])->dependsOn('status', 'refunded');
NovaDependencyContainer::make([...])->dependsOn('status', 'partially_refunded');
Pair every container with server-side conditional validation. Since the client only hides the block, the rules must be conditional too:
Text::make('Company name', 'company_name')
->rules([
Rule::requiredIf(fn () => $request->input('type') === 'company'),
])
->nullable(),
Note $request->input(...) rather than $this->type — on the create screen $this is an empty model.
Nest containers for progressive disclosure. A container's children can include another NovaDependencyContainer, so you can do "refunded → manual → requires approver" without flattening every combination into its own condition.
Wrap in Panels like any other field. NovaDependencyContainer goes wherever a field would: inside Panel::make([...]), alongside Field::make('')->help(), or in a tabbed form.
Extract reusable field sets. Keep the conditional fields in a method so the same block can be used in a full section and in a conditional section without duplication:
protected function companyFields(): array
{
return [Text::make('Company name'), Text::make('VAT number')];
}
// ...
Panel::make('Company', $this->companyFields()),
NovaDependencyContainer::make($this->companyFields())->dependsOn('type', 'company'),
The same extraction is what makes reuse across fields() and fieldsForIndex()/fieldsForDetail() safe — share the field arrays, not the whole container, if you want predictable index/detail columns.
Depend on the raw attribute, not the label. For BelongsTo searchable selects and Select fields the stored value is the ID/key; ->dependsOn('status', 'Refunded') will never match. Always use the attribute name (status) and the stored value.
Test the behavior, not the widget. Nova's feature tests (Nova::actingAs($user) + $this->post('/nova-api/.../resources', [...])) never exercise the Vue layer. Write two tests: payload with the dependency satisfied must pass validation, payload with it unsatisfied must also pass (proving your rules are conditional). That's the regression that actually matters.
Hidden fields still submit. The container is a UI wrapper; the inputs are not removed from the form payload, so required, unique, exists and custom rules on hidden fields all still fire. This is the number-one source of "works on screen, fails on save". Always make rules conditional (sometimes, required_if, Rule::requiredIf) and make the columns nullable if the block is optional.
Value comparison is literal, so mind types and empty strings. The watcher compares the form's live value against the value you passed to dependsOn. Booleans versus 1/'1', selects that submit strings versus ints, and an unset field defaulting to '' or null are the usual mismatches. If a container refuses to appear, add ->dependsOn('field', $value) with the exact value the field holds and inspect the dependsOn meta in the JSON response for that resource's form (DevTools → Network → the create/update form request → fields[].dependsOn). That tells you whether the problem is server meta or client reactivity.
Attribute name, not label. Text::make('First Name', 'first_name') means you depend on first_name. Typos here fail silently.
Dependencies must live in the same form. Cross-resource dependencies (a field on a related resource rendered in a modal, or a field in a different form context) won't react, because the component only watches the current form's field values. Self-referencing and circular dependencies are likewise not handled.
No native OR logic. Multi-condition arrays are AND-only. Use one container per OR branch, or subclass NovaDependencyContainer and compute dependsOn yourself in jsonSerialize if you need richer semantics.
It is not a security boundary. Nothing server-side enforces that a field inside a hidden container is absent. If visibility implies permission (e.g. "only admins see margin"), enforce it in fields()/authorizedToUpdate() — not in a container.
Index/detail views on Nova 3. 1.3.3 fixed compatibility with fieldsForIndex() / fieldsForDetail(), so returning a resource's fields() (container included) from those methods no longer errors on Nova 3. That does not make the dependency work there: index and detail have no live form values to watch, so the container just renders its children unconditionally. If you need different columns per view, define fieldsForIndex() / fieldsForDetail() explicitly with the plain fields; if you're on an older pin and see errors when a container leaks into a table, upgrade to 1.3.3 first.
Version pinning matters. The package is on the 1.3.x line; 1.3.3 is a targeted Nova 3 compatibility fix, not a sign of broad Nova 4/5 support. Nova 4/5 compatibility remains the first thing to verify in a new project: install, load an edit screen, confirm the block toggles, and check an index and a detail page if you reuse fields(). If a tagged version misbehaves on your Nova version, check dev-master/open issues before assuming your code is wrong, and consider pinning or forking — the surface area is one PHP class and one Vue component, so maintaining a fork is cheap.
Stale assets after an upgrade. If dependsOn meta is present in the JSON but nothing toggles, suspect the compiled asset. A hard refresh, then clearing vendor and re-running composer install, resolves most cases; the package ships prebuilt dist files, so it doesn't participate in your app's npm run dev. Note that bumping to 1.3.3 changes PHP-side behaviour only — no JS rebuild is needed for that fix, but a stale dist will still break toggling for unrelated reasons.
Extension points, in order of cost: ->withMeta([...]) on the container for extra flags you read in your own JS; a subclass of NovaDependencyContainer for new server-side semantics (OR groups, role-based conditions); a fork of the Vue component if you want animation, v-show instead of conditional rendering, or custom transitions. The component is small enough that forking is a legitimate day-to-day option rather than a last resort.
How can I help you explore Laravel packages today?