abordage/nova-html-card
A lightweight Laravel Nova card for rendering custom HTML on dashboards and resource pages. Add notices, links, embeds, metrics, or any markup without building a custom card from scratch—simple configuration and works anywhere Nova cards are supported.
Install into the same app as Nova (Nova 4/5 assumed — check the README's compatibility table before committing):
composer require abordage/nova-html-card
The package ships pre-compiled JS in dist/, so there is no npm install/npm run dev step in your app.
Confirm the exact class FQCN before writing code — this is a 12-star package, so copy it from the README or vendor/abordage/nova-html-card/src/ rather than trusting memory. It will be a Laravel\Nova\Card subclass (a name like Abordage\NovaHtmlCard\HtmlCard) exposing a fluent setter such as ->html(...).
Register it on a dashboard in app/Providers/NovaServiceProvider.php:
public function cards(): array
{
return [
(new HtmlCard)->html('<p class="text-green-500">All systems operational.</p>'),
];
}
php artisan nova:publish if the README asks for it, then hard-refresh. If the card renders but appears empty, jump straight to the Gotchas section — that is almost always an escaping or asset-manifest issue rather than a registration problem.
First real use case: a dashboard card with a hand-written "quick links" or "on-call rota" block. It is the fastest possible Nova card: no Vue, no build, no API endpoint. Only reach for ->html() on dashboards you control, not on content that comes from users.
Render Blade, don't concatenate strings. The package's value proposition collapses the moment you start building HTML with sprintf. Keep markup in a Blade partial and pass the rendered string:
(new HtmlCard)
->html(view('nova.cards.system-status', [
'failed' => Queue::failedCount(),
'jobs' => Queue::size(),
])->render())
->width('1/2'),
This gives you syntax highlighting, @foreach, and @lang for free, and keeps the PHP side down to a data query.
Small reports. Compute the numbers in the provider or a dedicated DashboardMetrics class, render a tiny table, and let Nova's layout handle placement. Cap the query cost — see Gotchas.
Notification/activity lists. Query the last N records and render an <ul>. Always e() (or {{ }} inside the Blade partial) every user-derived field; you are rendering into v-html, so Blade's auto-escaping is your only line of defence.
Resource index cards. For cards attached via Resource::cards(), you typically have no model instance. Read the request context (NovaRequest, $request->resourceId, $request->viaResource) if you need to scope the content, and return empty/neutral HTML on the index rather than throwing.
Gate and scope. Because content is computed server-side, canSee() is a genuine security boundary, not cosmetics:
->canSee(fn ($request) => $request->user()->hasRole('admin'))
Use it for anything financial, PII, or internal-ops flavoured, and never rely on hiding it client-side.
Composition. If the block needs to be interactive (filters, accordions, live polling), this package is the wrong tool — write a proper Vue card or a Nova custom tool. Use HTML Card for read-only snapshots.
Version control the Blade partial alongside the provider so the card has a reviewable diff trail; the card class itself is throwaway glue.
@click. If the README exposes a refresh button/meta, use it; otherwise document that users must reload the page.->html() bypasses escaping by design. Any interpolation of $user->name, a log line, or an exception message is an XSS vector. Sanitise or escape explicitly — {!! !!} habits from Blade do not transfer safely here.v-html does not compile {{ }} or @click attributes, so half-broken Alpine snippets will silently no-op. That is a feature, but it confuses people.->html('<x-badge>') outputs the literal tag. Either view(...)->render() or Blade::render(...).count, group by, limit), never ->get() into the view.Nova::style() keyed off Nova's dark-mode selector.width() semantics between majors. If the card is mis-sized, check the Nova version, not the package.dist/ is committed, an upgrade can require a browser cache bust; run nova:publish and hard-refresh before filing a bug against your own code.assertStringContainsString on the rendered partial covers the rest.composer.json and be prepared to vendor the two files if it goes unmaintained — that is cheaper than migrating a dozen dashboards later.Help card, a plain Nova::script/Nova::style injection, or a 40-line custom card. If you find yourself adding a second HtmlCard subclass, you have outgrown the package.How can I help you explore Laravel packages today?