DEV Community

Cover image for Wirelist: cards, divided lists and grids for Laravel and Livewire
Eduardo Lázaro
Eduardo Lázaro

Posted on

Wirelist: cards, divided lists and grids for Laravel and Livewire

A list of cases, tasks or properties is not a table, as each record reads as a card, and forcing it into columns leaves gaps and odd widths.

Wirelist draws those lists. Records as divided rows, separate cards or a grid, each one opening its record, with a row actions menu, load more and the 11 themes of the wire* family. It is built on Wiretables, so tables and lists share their look and sit together on one screen.

Install

It requires Wiretables, which Composer installs with it. Publish both CSS:

composer require edulazaro/wirelist
php artisan vendor:publish --tag=wiretables-assets
php artisan vendor:publish --tag=wirelist-assets
Enter fullscreen mode Exit fullscreen mode

Load the cores and, if you want one, the theme of the same name from each package. With Vite:

@import "../../vendor/edulazaro/wiretables/resources/css/wiretables-core.css";
@import "../../vendor/edulazaro/wiretables/resources/css/themes/studio.css";
@import "../../vendor/edulazaro/wirelist/resources/css/wirelist-core.css";
@import "../../vendor/edulazaro/wirelist/resources/css/themes/studio.css";
Enter fullscreen mode Exit fullscreen mode

Wiretables' theme file brings the colours both share; Wirelist's adds what only a list needs.

A list

Each record is an item with a title, a muted line and slots around them:

<x-wirelist items="separated">
    @forelse ($cases as $case)
        <x-wirelist.item wire:key="case-{{ $case->id }}" :title="$case->title" :subtitle="$case->company" :href="route('cases.show', $case)">
            <x-slot:badges>
                @foreach ($case->tags as $tag)
                    <span class="badge">{{ $tag->name }}</span>
                @endforeach
            </x-slot:badges>
            <x-slot:aside>
                <span>{{ $case->members_count }} members</span>
                <span class="status">{{ $case->status }}</span>
            </x-slot:aside>
            <x-slot:actions>
                <x-wiretable.menu :label="'Actions of '.$case->title">
                    <x-wiretable.menu-item wire:click="archive({{ $case->id }})" danger>Archive…</x-wiretable.menu-item>
                </x-wiretable.menu>
            </x-slot:actions>
        </x-wirelist.item>
    @empty
        <x-wirelist.empty>No cases yet.</x-wirelist.empty>
    @endforelse
</x-wirelist>
Enter fullscreen mode Exit fullscreen mode

The slots cover what these lists usually carry:

Slot
leading An avatar or an icon; in a grid, the cover image
badges Beside the title: tags, a draft mark, a category
aside On the right: counts, a status, a date
actions The "⋯" menu or a button
header A strip above the record
default More lines under the subtitle

Three shapes, one markup

The same items draw three different lists:

  • items="divided", the default, puts every record in one frame with a line between them: a queue or a short dashboard list.
  • items="separated" draws each record as a card of its own.
  • layout="grid" puts the cards side by side, as many as fit. The leading slot becomes a cover running edge to edge on top, and the aside lines up at the foot of every card in the row.

Since the markup does not change, a list/grid switch is one Livewire property:

<x-wirelist :layout="$view">…</x-wirelist>
Enter fullscreen mode Exit fullscreen mode

A card that opens without nesting buttons

The usual way to make a whole card clickable is to wrap it in an <a>, and then a delete button ends up inside a link. HTML does not allow it, keyboards and screen readers trip on it, and the click goes to whichever element the browser picks.

With href, Wirelist stretches the title's link over the item instead:

.wtl-item-link::after {
    content: "";
    position: absolute;
    inset: 0;
}
Enter fullscreen mode Exit fullscreen mode

A click anywhere on the card opens the record, while the badges, the aside, the actions and any link or button in the content sit above the stretched link and keep their own clicks. The markup stays valid: the menu is a sibling of the link, never inside it.

On a phone

Lists adapt without configuration: the actions stay beside the title and the aside goes under both. The grid drops to as many columns as fit, down to one.

Load more and sorting

Wiretables' traits work the same with a list:

use EduLazaro\Wiretables\Concerns\WithLoadMore;

$cases = $this->loadMoreFrom(LegalCase::query()->latest());
Enter fullscreen mode Exit fullscreen mode
<x-wirelist>…</x-wirelist>
<x-wiretable.load-more :show="$hasMore" />
Enter fullscreen mode Exit fullscreen mode

The list re-reads from the start on each load, so nothing is repeated or lost when records change meanwhile, and the client cannot ask for more pages than it loaded.

Themes

The theme is the same attribute as the rest of the family:

<html data-wire-theme="minimal">
Enter fullscreen mode Exit fullscreen mode

Colours, lines and radius come from Wiretables' --wtb-* variables, so one setting reaches tables and lists alike. The list's own shape lives in --wtl-*:

:root {
    --wtl-item-padding: 14px 20px;
    --wtl-grid-min: 18rem;
    --wtl-cover-ratio: 4 / 3;
}
Enter fullscreen mode Exit fullscreen mode

Links

👉 Source: Wirelist on GitHub
👉 Packagist: Wirelist on Packagist
👉 Documentation: Wirelist Documentation

Top comments (0)