idf/nova-html-card
idf/nova-html-card adds a Laravel Nova dashboard card that renders any HTML you give it, handy for notices, links, embeds, and lightweight widgets. Install via Composer, register the card, and pass your own markup to customize the Nova dashboard with custom content, no custom card class needed.
Architecture fit. The package is essentially one class — Idf\NovaHtmlCard\HtmlCard — that subclasses Nova's card lifecycle and renders whatever string content() returns. There is no config, no routes, no migrations, no publishable assets, and no service provider surface to reason about. Architecturally this is not a framework dependency; it is a convention with a thin Nova glue layer. Everything you actually depend on (cards(), canSee(), $width, onlyOnDashboards()) belongs to Nova, not to this package. That distinction matters for risk: the package's own failure surface is nearly zero, and the real coupling is to a commercial, closed-source platform whose card API has churned across majors.
Integration feasibility. Three commands and one provider edit: composer require, generate a card class, extend HtmlCard instead of Nova's Card, register it in NovaServiceProvider::cards(). No schema changes, no data migration, no asset build. A single engineer can validate the whole thing in a staging branch inside a day, including a CSP and dark-mode pass. Reversibility is the strongest property in the evaluation — exit is deleting one subclass and one array entry.
Technical risk. Four items dominate, in order:
content() becomes stored XSS on the highest-privilege session in the estate. Mitigation is a governance decision (engineers only, Blade files in version control, {{ }} everywhere) rather than a technical control the package provides.composer.json constraints must be checked against the actual target Nova and PHP versions before any upgrade. Budget a fork-and-patch contingency — the card is forkable in an afternoon only if subclass diffs stay minimal.3.4.* and upgrade deliberately. Note that 3.4.2 is literally an asset rebuild plus CI changes — a patch bump is not a feature signal.content() likewise renders an empty wrapper. Both are worse than a visible error.The composite score (6.48, opportunity 25.63) is consistent with this reading: a low-cost, low-ceiling convenience layer with contained but non-trivial platform coupling.
Key questions to resolve before adoption.
Stack fit. Laravel + Nova only. Outside an existing Nova estate there is nothing to evaluate — Nova is a paid prerequisite, and this package delivers zero value without it. Inside Nova, the fit is exact: it occupies the same slot as a hand-written Vue card but collapses the build step. The decision is therefore not "adopt a library" but "adopt a default for static dashboard chrome."
Migration path. None required, and that is the point. There is no state to migrate, no schema, no persistence. Adoption is additive; rollback is a provider edit. The one deliberate design choice that protects the exit is keeping all markup in normal Blade views under resources/views/nova/cards/ rather than inline PHP strings — that makes a future migration or fork mechanical rather than archaeological. Do this from the first card, not after the fifth.
Compatibility work. Pin "idf/nova-html-card": "3.4.*". Validate against the exact Nova and PHP target in CI, not by reading release notes. Test in staging with production CSP headers, since a permissive staging CSP will hide the blank-card failure entirely. Check both Nova light and dark themes — hardcoded hex values and background: #fff are the most common cosmetic complaint about hand-authored card HTML.
Sequencing.
{{ }} for every interpolation, raw markup reserved for strings we authored. This is a product decision, and it gates everything downstream.NoticeCard with a $width and a notice() hook, thin concrete subclasses beneath. Prevents markup duplication across dashboards.content() runs on every dashboard request for every user who sees the card. Cache anything non-trivial with a short TTL; key file-derived content on filemtime().Maintenance. Code volume is negligible; the maintenance cost is concentrated entirely in the Nova upgrade tax and in content governance. Name an internal owner on day one — a single-maintainer upstream with zero dependents means no one else is watching. Pinning plus deliberate upgrades is the whole maintenance strategy, and it is sufficient at this scale. Set expectations that this is a convenience widget layer: no roadmap commitment should depend on it.
Support. There is no support relationship. 68 stars and one maintainer will fail most enterprise procurement bars, and there is no security review trail. Internal support means a runbook entry covering the two diagnostic steps that resolve most incidents: is content() returning a non-empty string, and is the card blank because of CSP or because of a silent Blade exception. Add both to the on-call notes.
Scaling. Cost scales linearly with card count, not with the package. Every card's content() executes per dashboard request per viewer, so a naive implementation quietly turns /nova into the slowest page in the admin panel with the query buried inside something nobody thinks to profile. Mitigations: cache with short TTLs, cap iframe embeds at roughly one per dashboard (each embed is both a page-load cost and an unmonitored external dependency), and treat dashboard p95 latency as the tracked metric — not card count. The $width property makes a three-card 1/3 header row (status, links, announcement) a cheap way to make a stock Nova dashboard look intentional.
Failure modes to plan for.
content() or an outbound HTTP call on every dashboard hit; content() must stay pure and cheap, no writes, no Artisan::call(), no network.Ramp-up. Near-zero for a Laravel developer — one file, four extension points. The learning curve is entirely in the review discipline, not the API. Codify a short reviewer checklist: every interpolation escaped or explicitly justified, caching present for anything beyond a trivial lookup, both themes checked, CSP implications reviewed, $width chosen intentionally. Once that checklist exists, the package is genuinely an hours-level task per card and the build-vs-buy case for static dashboard content is settled in its favor. The moment a request needs a refresh button or live data, treat it as escalation to a bespoke Nova card rather than a reason to bend this one.
How can I help you explore Laravel packages today?