List View
Inject your own components around a collection's list — a header, a banner above the table, a summary below it — using the four list slots.
The collection list is where editors browse, filter, and act on records. You can't rebuild it, but you can add your own components around it: a header with a custom action, a banner above the table, a summary row or custom pagination below. Dyrected gives you four slots on every collection list for this — now aliased as operational view slots so the same content mounts inside table/kanban/calendar/cards/spreadsheet workspaces. See Routing and preferences for the mapping.
By the end of this page you should know the four list slots (and their view aliases), the rich set of props your slot components receive, and how to register them per collection. This page assumes the declare-then-provide pattern from the Custom Components overview.
The four slots (and their view aliases)
A collection list has four injection points, top to bottom. The names below are the legacy beforeList keys — they still work inside operational views via an alias, with new beforeView* names preferred:
| Legacy | View alias (preferred) | Where it renders |
|---|---|---|
beforeList | beforeViewHeader | Above everything, including the toolbar |
beforeListTable | beforeViewContent | Between the toolbar and the table/grid |
afterListTable | afterViewContent | Directly below the table/grid |
afterList | afterViewContent | At the very bottom |
Dyrected seamlessly bridges beforeList to beforeViewHeader and other slot keys, ensuring existing custom component registrations continue functioning across all operational layouts.
Each slot accepts an array of component keys so you can stack multiple extensions. Declare slot components per collection under admin.components:
import { defineCollection, defineTextField } from "@dyrected/core";
export const Posts = defineCollection({
slug: "posts",
admin: {
components: {
beforeList: ["posts-header"],
afterListTable: ["posts-summary"],
},
},
fields: [
defineTextField({ name: "title", label: "Title", required: true }),
defineTextField({ name: "slug", label: "Slug", unique: true }),
],
});Then register the components under the collectionList group when you mount the admin, matched by the same keys:
import { DyrectedAdmin } from "@dyrected/next/admin";
import { PostsHeader } from "./PostsHeader";
import { PostsSummary } from "./PostsSummary";
export default function AdminPage() {
return (
<DyrectedAdmin
components={{
collectionList: {
"posts-header": PostsHeader,
"posts-summary": PostsSummary,
},
}}
/>
);
}The registry key is the slot-key name you declared ("posts-header"), not the collection slug — the component renders wherever that key appears in a collection's slot lists. A key with no matching component is skipped (with a dev warning), and a component that throws is isolated by an error boundary so the list still renders.
These slots also appear on upload (media) collections — a upload: true collection's list uses the same four slots,
declared the same way on its admin.components.
View-specific component slots (defineView)
If a collection has multiple operational views (such as a Kanban pipeline, a Calendar schedule, and a standard Table), you might only want a custom widget on a single view rather than all of them.
You can declare component slots directly inside defineView:
import { defineView } from "@dyrected/core";
export const kanbanPipeline = defineView({
slug: "pipeline",
label: "Pipeline",
layout: "kanban",
groupBy: "status",
components: {
beforeViewHeader: ["kanban-legend"],
afterViewHeader: ["pipeline-kpi-summary"],
beforeViewContent: ["kanban-filter-banner"],
afterViewContent: ["kanban-footer-stats"],
},
});When both collection-wide slots (collection.admin.components) and view-specific slots (view.components) are defined, Dyrected cleanly merges them in hierarchical order (collection banners render first, followed by view-specific widgets). Register the components under components.collectionView or components.collectionList in <DyrectedAdmin />.
Collection-wide vs. View-specific slots
| Scope | Where to declare | Use case |
|---|---|---|
| Collection-wide | collection.admin.components | Notices, banners, or actions that should appear across all views in this collection (e.g. an "Archived collection" banner). |
| View-specific | defineView({ components: { ... } }) | Widgets that only make sense for a single view layout (e.g. a Kanban legend, a Calendar date switcher). |
What your component receives
List slot components get everything about the current list — the data, its loading and pagination state, the user's permissions, and links — so they can render summaries, actions, or context without re-fetching:
| Prop | What it is |
|---|---|
client | The authenticated Dyrected client, for any extra API calls |
user | The signed-in user document, or null |
collection | The full config of the collection being listed |
collectionSlug | The collection's slug (e.g. "posts") |
response | The raw paginated response from the last query (or undefined while loading) |
documents | The documents currently shown, unwrapped from response |
isLoading | true while the list query is in flight |
pagination | { page, totalPages, total, hasNextPage, hasPrevPage } |
permissions | { canRead, canCreate } for the current user on this collection |
urls | { collection, create } — admin links to the list and the new-document form |
For example, a summary that shows the total count below the table:
import type { CollectionListSlotProps } from "@dyrected/admin";
export function PostsSummary({ pagination, isLoading }: CollectionListSlotProps) {
if (isLoading) return null;
return (
<div className="dy-px-4 dy-py-2 dy-text-sm dy-text-muted-foreground">
{pagination.total} posts total
</div>
);
}Because you receive documents, pagination, and permissions, most list add-ons need no data fetching of their own — read what's already there. Reach for client only when you need something the list didn't load.
Slot props intentionally don't expose setters for the list's filters, sorting, or row selection — they're for adding
content alongside the list, not driving its internal state. Use client if you need to run your own query.
Slots inside operational views
When these slots render inside an operational view, your component receives the documents the active layout has loaded. documents and isLoading reflect what is on screen, and the view's own slot props (viewSlug, view) are included.
This changed. Before, documents was always an empty list and isLoading always false inside a view, so a slot
that checked for an empty list would have always taken the empty branch. They now carry real values, so review any
slot that relied on that.
The legacy list keys (beforeList, afterListTable, and the rest) are not quite the same as on a plain collection list. response is undefined, and pagination describes a single page with total equal to the number of documents loaded, not the server's total. For the true total, paging, filters, and selection, read the view's state with useCollectionView():
import { useCollectionView } from "@dyrected/react";
export function ArrivalsSummary() {
const { documents, total, isFetching } = useCollectionView();
return (
<p aria-live="polite">
{isFetching ? "Updating…" : `${documents.length} of ${total} guests loaded`}
</p>
);
}useCollectionView() is documented with the rest of the view hooks in Custom View Layouts. It works in any view slot, whichever layout the view uses.
Using Vue components
In a Vue or Nuxt app, register Vue components in the same collectionList map; they're wrapped automatically and receive the props above as Vue props. See the overview.
Recommended path
Use beforeList for a header or collection-level action, beforeListTable for a banner or notice, and afterListTable / afterList for summaries and totals. Read from the props you're given — documents, pagination, permissions — before reaching for client. For components on the dashboard instead, see Dashboard.
Dashboard
Add your own components above and below the built-in dashboard — a welcome banner, an analytics widget, a shortcut panel — using the dashboard slots.
Custom View Layouts
Draw a view's records your own way, such as a seating chart or a floor plan, while Dyrected keeps the header, filters, search, paging, and action dialogs.