interaction-design-foundation/nova-html-card
A Laravel Nova card that renders custom HTML or Blade content directly on your dashboards and resource pages. Drop in markup, embed views, and build flexible info panels without writing a full custom card component.
Install the card with Composer (no service provider registration, config publishing, or migrations are required):
composer require interaction-design-foundation/nova-html-card
The first place to look is vendor/interaction-design-foundation/nova-html-card/src/ — it is a single Nova Card subclass plus a compiled Vue component. Read that class and the README before wiring anything up; there is no configuration surface, so the only API you need is the content setter and Nova's inherited card methods (width, canSee, onlyOnDetail, etc.).
Register the card in your main dashboard via app/Providers/NovaServiceProvider.php:
use InteractionDesignFoundation\NovaHtmlCard\HtmlCard;
public function cards(): array
{
return [
(new HtmlCard)
->html('<h2>Team Docs</h2><p>Deploy runbook: <a href="https://wiki.example.com/deploy" target="_blank" rel="noopener">wiki</a></p>')
->width('full'),
];
}
That is the canonical first use case: a low-effort "inline docs" panel — onboarding links, a support banner, a checklist, or a pointer to runbooks — living directly on the Nova dashboard where operators already are. Reload /nova and the card should render before you touch anything else.
Keep markup in Blade, not PHP strings. Long HTML literals are unmaintainable and quote-escaping becomes a hazard. Render a partial and hand the string to the card:
(new HtmlCard)
->html(view('nova.cards.quick-links', ['user' => request()->user()])->render())
->width('1/2'),
This also gives you Blade's @if, translations, and route() helpers for free.
Compose a dashboard from multiple small cards. Multiple HtmlCard instances with ->width('1/3'), '1/2', or 'full' let you build a grid of focused panels instead of one wall of text. Array order in cards() is display order, so treat that method as your layout.
Reuse the same card on resources and custom dashboards. Nova cards are generic, so the same class works in NovaServiceProvider::cards(), in a Dashboard class's cards(), and in a resource's cards(NovaRequest $request) method. Scope it with Nova's inherited helpers:
public function cards(NovaRequest $request): array
{
return [
(new HtmlCard)
->html(view('nova.cards.user-guide')->render())
->onlyOnDetail()
->canSee(fn ($request) => $request->user()->isAdmin()),
];
}
Make content dynamic but cheap. Pulling the current user, a config flag, or a cached settings row into the view is the natural extension point. If the content comes from the database, cache it — see the next section.
Treat Markdown as a content source. For prose-heavy docs, store Markdown and convert at render time (Str::markdown($content)) rather than hand-writing HTML. Sanitize anything not authored by you.
v-html), so it is not escaped. Never pass request input, user profiles, or database fields you don't control directly into ->html(). For anything dynamic, sanitize first (e.g. mews/purifier) or build markup in a Blade partial where escaping is explicit.{{ $value }} escapes; use {!! $value !!} only for trusted HTML. If the whole partial is HTML, view(...)->render() is cleaner than mixing escaped and unescaped output in one string.v-html does not execute <script> tags and does not compile directives. If you need interactivity, buttons, or data fetching, write a proper custom Nova card instead of trying to smuggle JavaScript through this one.composer.json and smoke-test after Nova upgrades.->html() doesn't exist on the installed release, check the README in vendor/ — the method is the only thing you need to get right, and the class is short enough to read in a minute.cards() runs on each page load — cache aggressively or move logic into a cached view composer./nova-api/... (or view source) to confirm your string is present. If it's present but not visible, the issue is CSS/width/Vue rendering, not PHP. If it's absent, your cards() method isn't being hit or canSee is returning false.<details>/collapsibles for long docs. A card with 40 lines of text dominates the dashboard. Wrap verbose content in <details><summary>...</summary>...</details> or link out to a Nova page/resource instead.target="_blank" rel="noopener", and inline styles may be blocked by a strict Content-Security-Policy — if styles vanish in production but work locally, check CSP before blaming the package.How can I help you explore Laravel packages today?