pdmfc/nova-info-card
A Laravel Nova card for surfacing informational content on your dashboard or resource screens. Add a custom info panel to display key metrics, notices, or contextual guidance alongside your other Nova tools, keeping important details visible where your team needs them.
Install the card package into a Nova-enabled Laravel app:
composer require pdmfc/nova-cards
There is normally nothing to publish — Nova auto-registers the card's compiled assets. If the card renders as an empty box after install/upgrade, run php artisan nova:publish and clear caches (see Gotchas).
Where to look first
vendor/pdmfc/nova-cards/src/ — the PHP card classes. After v1.0 the generic card is Pdmfc\NovaCards\Info (it was previously Pdmfc\NovaCards\Card). Sibling classes follow the same namespacing for the other themes (Success, Warning, Danger).vendor/pdmfc/nova-cards/resources/js/ — the Vue component, if you need to confirm which fluent setter maps to which template slot.First use case — show an informational banner on a resource's detail screen:
use Pdmfc\NovaCards\Info;
use Laravel\Nova\Http\Requests\NovaRequest;
public function cards(NovaRequest $request): array
{
return [
(new Info)
->text('Staging environment')
->body('Changes here are not customer-visible.'),
];
}
Two things to internalise immediately:
Pdmfc\NovaCards\Info is a breaking change. Any v0.x code using Pdmfc\NovaCards\Card must update its use statement.<a>, <strong>, <br> now work instead of being printed as literal text. That is the feature most day-to-day value comes from — and the one that needs a security habit attached to it.Treat cards as a presentation layer over a small query. The most common real usage is "surface a number or status the resource page doesn't otherwise show":
public function cards(NovaRequest $request): array
{
$pending = Order::where('status', 'pending')->count();
return [
(new Info)
->text('Order queue')
->body("{$pending} orders awaiting fulfilment.")
->width('1/2'),
];
}
Because cards() is evaluated on each resource request, wrap anything non-trivial in Cache::remember() — a card is not worth an N+1 on every detail page load.
Choose the theme semantically. Reach for Danger for states that need action (failed sync, expired credentials), Warning for degraded-but-working, Info for context, Success for confirmation. v1.0 added a proper icon for the danger theme, so it now reads correctly at a glance rather than looking like an unstyled Info card.
Use raw HTML for links, not for layout. The v1.0 HTML support is best spent on anchors and inline emphasis:
(new Warning)->text('Maintenance window')
->body('Read the <a href="https://wiki.example.com/runbook" target="_blank">runbook</a> before deploying.');
For anything longer than a sentence or two, render a Blade partial to a string instead of concatenating HTML in PHP:
$html = view('nova.cards.runbook', ['step' => $step])->render();
(new Info)->text('Runbook')->body($html);
That keeps markup diffable and lets you {{ }}-escape interpolated values for free.
Register cards at the right scope.
cards() on the Resource class (detail/index of that resource only).NovaServiceProvider::cards() or Nova::cards(), visible everywhere.cards() method.Scope matters: a "billing is down" banner belongs globally; a "this order is refunded" card belongs on the resource.
Branch on detail vs index using the request, since the same cards() method serves both:
if ($request->resourceId === null) {
return []; // index view — keep it clean
}
Gate with canSee() rather than conditional array building, so the intent is declarative:
(new Danger)->text('Impersonating')
->canSee(fn ($request) => $request->user()->isAdmin())
Test it cheaply. Assert on the array returned by cards() — class, and the output of the fluent getters — instead of doing browser assertions on a Nova screen.
The v1.0 rename will bite you silently. Pdmfc\NovaCards\Card → Pdmfc\NovaCards\Info. A stale import produces a "class not found" at runtime, not at install time, so grep your codebase for NovaCards\Card before bumping. Pin "pdmfc/nova-cards": "^1.0" if you support older Nova apps that can't take the rename yet.
Raw HTML changes the escaping contract. Anything you route into body() from user input, model attributes, or third-party APIs is now an XSS vector. Escape explicitly (e($value), or {{ }} inside a rendered view) and only pass trusted markup raw. This is the single most important behavioural difference from pre-1.0 and it won't produce an error — just a vulnerability.
Card not appearing / stale UI after upgrade. Nova compiles a component manifest and the browser caches the JS bundle. In order: php artisan nova:publish --force, php artisan view:clear, php artisan config:clear, then a hard refresh. If the card renders but looks like the old version (missing danger icon), it's almost always a cached bundle, not a broken release.
Icons and spacing changed for danger. Cosmetic-only, but if you had CSS or a screenshot test asserting on the card's layout, expect a diff.
Fluid API is not validated for you. Cards are permissive about ordering (text() before or after body()); an empty body() typically renders an empty slot rather than erroring, so a card that "doesn't show the message" is usually a setter you forgot to call. Dump the card instance in cards() to confirm what you built.
Extension points. Subclassing a card class is the cheapest customisation — override the component property or the fluent defaults. Deeper visual changes mean either publishing the package's Vue assets or writing your own card; forking the JS for a colour tweak is rarely worth it when the four built-in themes already map to Nova's palette.
Check Nova version compatibility in composer.json of the package before upgrading in a lockfile-heavy project. Nova major upgrades are the usual reason card packages break, and it will show up as a Vue render failure with no useful PHP stack trace — check the browser console first when debugging.
How can I help you explore Laravel packages today?