Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Nova Cards Laravel Package

stepanenko3/nova-cards

Add customizable cards to your Laravel Nova dashboard with stepanenko3/nova-cards. Build flexible card layouts for metrics, links, and custom content to surface key information in the admin panel.

View on GitHub
Deep Wiki
Context7

Getting Started

Before you touch composer, answer two questions. This package is Nova-only — outside a Nova installation it does nothing, and it will not degrade gracefully. First, confirm your Nova major version. Second, read composer.json in the package and check its Nova constraint. Small card packages are the worst offenders for lagging behind Nova majors (4 → 5 is the usual trap), and the README won't tell you clearly. Verify against the constraint, not the description.

composer require stepanenko3/nova-cards --dev   # pin the exact version, no ^ or ~
php artisan nova:publish                        # if the package publishes Vue assets

Read vendor/stepanenko3/nova-cards/src/ before registering anything. The whole surface is a few hundred lines of PHP and one Vue component. In ten minutes you should be able to answer:

  1. What does the base Card class expect you to override — a view(), a component name, props?
  2. Does it render your Blade view with data you pass, or does it ship its own configured component?
  3. Does it escape output, or render operator-authored content raw?

Answer (3) with a single grep '{!!' vendor/stepanenko3/nova-cards/ — that's the realistic risk class for an info-card package and it's the only audit you need.

First use case: the environment banner. It's the lowest-risk, highest-signal card you can ship, and it exercises the full loop — card class, view, registration, sizing — in about twenty lines. Register it:

// app/Providers/NovaServiceProvider.php
public function cards(Request $request)
{
    return [
        (new EnvironmentBanner)->width('full'),
    ];
}

Confirm the builder method names (width, title, etc.) against the card class in src/, not against a blog post. The shape — extend Card, return from cards(), render a Vue component — is standard Nova; the specifics are the package's.

Checklist before you call the integration done: card appears on the dashboard, renders the right content, respects canSee() when you log in as a non-admin, and doesn't blow up on a browser hard-refresh after deploy.

Implementation Patterns

One card class per concern. Resist the urge to build a DashboardInfoCard with a type switch. EnvironmentBanner, ReleaseNotes, SupportLinks, OnCallRota — each as its own class — are cheaper to test, cheaper to gate, cheaper to delete when a stakeholder changes their mind. The scaffolding cost is small and the clarity is worth it.

Keep content in Blade views, not config arrays. Anything beyond a one-liner belongs in resources/views/nova/cards/release-notes.blade.php and is referenced from the card's view(). You get partials, translations, {{ }} escaping, and syntax highlighting for free, and the content lives where the next developer will actually look for it.

class ReleaseNotes extends Card
{
    public function component() { return 'nova-card-view'; } // confirm name in src/

    public function view()
    {
        return 'nova.cards.release-notes';
    }

    public function with()
    {
        return [
            'notes' => config('admin.release_notes', []),
        ];
    }
}

Register per-dashboard once you have more than one. NovaServiceProvider::cards() puts the card on every dashboard, including ones you didn't design. Dashboard::cards() gives per-surface control and is the right default for anything not genuinely cross-cutting. Reserve global registration for org-wide notices (maintenance windows, "staging — don't trust these numbers").

class Operations extends Dashboard
{
    public function cards()
    {
        return [
            (new EnvironmentBanner)->onlyOnDetail()->width('full'),
            (new ReleaseNotes)->width('1/2'),
            (new SupportLinks)->width('1/2'),
        ];
    }
}

Scope audiences with canSee(). Role-gated notices (compliance reminders for admins, onboarding checklists for new operators, financial links for finance-only) belong in the card's canSee(), not scattered @if blocks in the Blade. This is one gate, in one place, testable in isolation.

public function canSee(NovaRequest $request): bool
{
    return $request->user()?->hasRole('admin') ?? false;
}

Compose links, don't hardcode them. Escalation paths, runbooks, and support channels change. Pull them from config('admin.links.runbook') or a small helper so an update is a config change, not a code change plus deploy.

Test the view, not the package. A feature test that renders your Blade view with representative data catches ~90% of what actually breaks. A second test asserting the card appears in Dashboard::cards() catches the rest. You don't need to test vendor code, and you don't need to reproduce Nova's dashboard test scaffolding — a plain Blade render assertion is enough.

public function test_release_notes_card_renders()
{
    $view = $this->blade('<x-release-notes :notes="$notes" />', [
        'notes' => ['v1.2 removes legacy endpoint'],
    ]);

    $view->assertSee('v1.2 removes legacy endpoint');
    $view->assertDontSee('<script>', false); // escape check
}

Day-to-day content update loop. Once the cards exist, daily use is: edit the config or Blade view, run your test, ship. There's no dashboard rebuild, no Nova publish, and no dependency change involved in content edits — that's the whole point of the pattern.

Gotchas and Tips

Static means static. This is the trap the package sets up for itself. If you find yourself wanting a trial-expiry count, a revenue number, or a sparkline in a card, stop — that's Nova's first-party Value, Metric, Partition, or Trend territory. A static card impersonating a metric is a credibility problem, and a dashboard nobody trusts is worse than no dashboard. The day-to-day version of this rule: your info cards are text and links, full stop.

canSee() gates rendering, not computation. If you compute anything eagerly in cards() or the constructor — a scoped count, a filtered query feeding a link — it runs for every user regardless of the gate, and derived drill-down links leak the result. Compute lazily in with(), or restrict the data by tenant and user, and test as a non-admin in staging. This is the one authorization finding worth hunting.

Grep the Blade for {!! !!} before you merge. Info-card packages render operator- or config-authored content, and unescaped rendering of anything a non-trusted contributor can influence is an XSS vector inside your admin panel. One grep is the whole audit. If you find raw output, prefer escaping or sanitising at the config boundary rather than trusting the input.

Stale assets after composer update are the classic failure. If the package ships compiled Vue and you update it, the card often shows as an empty box until you nova:publish again or hard-refresh the browser. Establish which command refreshes assets and put it in the deploy notes — you will hit this, and it will look like a broken package when it's a caching issue.

Width doesn't compose the way demos claim. A 1/3-width card looks fine next to two others in a demo; next to a full-width metric it wraps badly. Validate layout at your team's standard viewport in staging, not at whatever the package demo used.

Nova upgrades are package upgrades. Add "run the dashboards in staging after a Nova major" to your upgrade checklist and pre-decide the fork trigger — e.g., "if a card is broken more than two weeks after a Nova major release, fork it." MIT makes that a day's work; the decision should already be made when it happens, not discovered mid-incident.

Zero community corpus. 33 stars, no dependents, single maintainer. No Stack Overflow answers, no issue threads that predate your problem. Read the source, keep notes in the repo (docs/nova-cards.md), and treat that file as the team's documentation. It's cheaper than re-learning the package during an outage.

Classify it correctly and keep it there. No auth, routing, or persistence surface — blast radius is confined to dashboard rendering, so a failure is a cosmetic incident. That's the argument for adopting it, and also the reason it doesn't belong in your strategic dependency list. Pin the exact version, budget the fork, and if you find yourself wanting more than two or three cards or any dynamic content, own a small internal card component instead — at that volume the dependency stops paying for itself.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource