Views
Twelve ways to show the same records, when to use each one, and what to install.
A view shows a table's records in a given way. Eleven views are display modes of one table: they share its records, search, filters, sorting, permissions and actions, are saved with each saved view and in the URL, and people switch between them under View settings › Display mode. The twelfth, the Dashboard, is a separate component that puts saved views of several tables on one grid. React and Vue offer the same views.
List the modes a table offers in table.displayModes and choose the first one with table.defaultDisplayMode. Most views ship in the table's own registry item. Calendar, Chart, Map and Dashboard are optional items, so their libraries stay out of your bundle until you install them; the three optional display modes are offered only when you pass their renderer in displayModeRenderers.
Try every view
This preview lists every display mode of one table except Gantt and File tree, which need their own data (a planning graph and a folder hierarchy). Open View settings, choose a Display mode, then open its settings in the same menu. Each mode keeps its own settings.
Sample data. Changes stay in this preview.
import { defineTableConfig } from "@/components/ui/yayaw-table";
import { projectMap } from "../shared/projects";
import { projectConfig } from "./project-config";
// Calendar, Chart and Map also need their renderers in `displayModeRenderers`.
export const exampleConfig = defineTableConfig({
...projectConfig,
table: {
...projectConfig.table,
displayModes: [
"table",
"list",
"kanban",
"gallery",
"calendar",
"chart",
"form",
"feed",
"map",
],
defaultDisplayMode: "table",
list: { titleColumn: "name", cardColumnIds: ["status", "budget", "start"] },
kanban: {
groupBy: "status",
titleColumn: "name",
cardColumnIds: ["category", "budget"],
},
gallery: { titleColumn: "name", cardColumnIds: ["category", "status"] },
calendar: { dateColumn: "start", endColumn: "end", colorColumn: "status" },
chart: { xColumn: "category", metric: "sum", metricColumn: "budget" },
feed: {
authorColumn: "author",
dateColumn: "postedAt",
bodyColumn: "update",
},
map: projectMap,
},
});Sample data. Changes stay in this preview.
import { defineTableConfig } from "@/components/ui/yayaw-table-vue";
import { projectMap } from "../shared/projects";
import { projectConfig } from "./project-config";
// Calendar, Chart and Map also need their renderers in `displayModeRenderers`.
export const exampleConfig = defineTableConfig({
...projectConfig,
table: {
...projectConfig.table,
displayModes: [
"table",
"list",
"kanban",
"gallery",
"calendar",
"chart",
"form",
"feed",
"map",
],
defaultDisplayMode: "table",
list: { titleColumn: "name", cardColumnIds: ["status", "budget", "start"] },
kanban: {
groupBy: "status",
titleColumn: "name",
cardColumnIds: ["category", "budget"],
},
gallery: { titleColumn: "name", cardColumnIds: ["category", "status"] },
calendar: { dateColumn: "start", endColumn: "end", colorColumn: "status" },
chart: { xColumn: "category", metric: "sum", metricColumn: "budget" },
feed: {
authorColumn: "author",
dateColumn: "postedAt",
bodyColumn: "update",
},
map: projectMap,
},
});The twelve views
| View | Mode | Shows | Use it to | Ships in | Guide |
|---|---|---|---|---|---|
| Table | table | Rows and columns, with inline editing, grouping and totals | Compare values, edit several fields, select and export | Core item | Layout, Columns |
| List | list | One compact line per record: a title and a few properties | Scan many records on any screen size | Core item | List |
| Board | kanban | Cards in lanes, one lane per option of a column | Move work through stages | Core item | Kanban |
| Gallery | gallery | Cards led by an image, with a media viewer | Browse products, photos and files | Core item | Gallery |
| Timeline | gantt | Bars on a timeline, with hierarchy and dependencies | Plan dated work and see the impact of a change | Core item | Gantt |
| Calendar | calendar | Month, week or list of dated records | Place events and deadlines, drag them to another day | Optional: yayaw-table-calendar, yayaw-table-vue-calendar | Calendar |
| Chart | chart | Bars, a line, a donut or one number, grouped on your server | Show totals and trends of the records in the view | Optional: yayaw-table-chart, yayaw-table-vue-chart | Chart view |
| Form | form | A form that creates one record per response | Collect requests, also on a public link | Core item | Form view |
| Feed | feed | Posts with an author, a date, a body and media | Follow updates and announcements, newest first | Core item | Feed view |
| Map | map | Markers from a location column, with clusters and popups | Find records by place, search an area | Optional: yayaw-table-map, yayaw-table-vue-map | Map view |
| Dashboard | YayawDashboard | Saved views of several tables, numbers and notes on a grid | Give an overview across tables, with shared filters | Optional: yayaw-table-dashboard, yayaw-table-vue-dashboard | Dashboard |
| File tree | filetree | Folders and files linked by a parent column | Browse and reorganize an asset library | Core item | File tree view |
Every guide above opens with a live React and Vue preview of its view, with the code that runs it.
Choose a view
Reading and editing values: Table, or List on narrow screens and for long records.
A process with stages: Board. With dates and dependencies: Timeline.
Dates: Calendar for events and deadlines, Timeline for plans with durations and dependencies.
Pictures and files: Gallery for thumbnails, File tree for folders.
Numbers: Chart inside a table's views, Dashboard to combine several tables.
Collecting data: Form, which can also be shared on a public link.
News: Feed, for records written as posts.
Places: Map, from a location column.
A saved view stores one display mode with its settings, so the same table can keep, for example, a "Due this month" calendar, a "Budget by category" chart and a "By status" board side by side.
Example setup
The view previews in these guides share one set of records: store-opening projects with a category, a status, a budget, start and end dates, a site, an owner and a latest update. Their records and columns and the folders and files of the File tree preview are plain TypeScript, shared by React and Vue. Dates are relative to today, so the calendar and the feed always show current records.
Each view example spreads this table configuration:
import { defineTableConfig } from "@/components/ui/yayaw-table";
import { projectColumns, projectVisibleColumns } from "../shared/projects";
/** The Projects table every view example starts from. */
export const projectConfig = defineTableConfig({
id: "projects",
translations: { namespace: "projects", keys: { title: "Store openings" } },
columns: {
definitions: projectColumns,
order: projectColumns.map((column) => column.id),
visible: projectVisibleColumns,
mandatory: ["name"],
sort: [{ id: "start", desc: false }],
},
table: {
allowCreate: true,
allowEdit: true,
allowDelete: false,
allowDuplicate: false,
allowBulkEdit: false,
allowBulkDelete: false,
enableRowSelection: false,
enableViews: false,
syncUrl: false,
defaultPageSize: 10,
},
});import { defineTableConfig } from "@/components/ui/yayaw-table-vue";
import { projectColumns, projectVisibleColumns } from "../shared/projects";
/** The Projects table every view example starts from. */
export const projectConfig = defineTableConfig({
id: "projects",
translations: { namespace: "projects", keys: { title: "Store openings" } },
columns: {
definitions: projectColumns,
order: projectColumns.map((column) => column.id),
visible: projectVisibleColumns,
mandatory: ["name"],
sort: [{ id: "start", desc: false }],
},
table: {
allowCreate: true,
allowEdit: true,
allowDelete: false,
allowDuplicate: false,
allowBulkEdit: false,
allowBulkDelete: false,
enableRowSelection: false,
enableViews: false,
syncUrl: false,
defaultPageSize: 10,
},
});The previews keep the records in memory: list pages, searches, filters and sorts them, and applies the filters of a dashboard. Reset reloads the preview and its records. Replace this host with your own server actions; views that need every record, such as Calendar, Map, Chart and File tree, also accept a scope your server can apply (see each guide).
import type { TableActions } from "@/components/ui/yayaw-table/providers/table-provider";
import {
matchesContractFilter,
recordValue,
} from "@/components/ui/yayaw-table/utils/table-contracts";
import { listRecords } from "./list-records";
type MemoryRow = Record<string, unknown> & { id: string };
/**
* An in-memory host for the view examples. Replace it with authenticated
* server actions; views that need every row (calendar, map, chart, file tree)
* filter the loaded rows themselves when `list` ignores their `scope`.
*/
export function createMemoryActions<T extends MemoryRow>(
rows: T[],
extra: Partial<TableActions> = {}
): TableActions {
let created = 0;
return {
list: (params) => {
// Dashboard filters arrive as `requiredFilters`, always joined with AND.
const { requiredFilters } = recordValue(params);
const required = Array.isArray(requiredFilters)
? requiredFilters.map(recordValue)
: [];
return listRecords(
rows.filter((row) =>
required.every((rule) =>
matchesContractFilter(row[String(rule.columnId)], rule)
)
),
params
);
},
create: (values) => {
created += 1;
const row = { ...values, id: `new-${created}` } as T;
rows.push(row);
return Promise.resolve({ success: true, data: structuredClone(row) });
},
update: (id, patch) => {
const row = rows.find((item) => item.id === id);
if (!row) {
return Promise.resolve({ success: false, error: "Record not found." });
}
Object.assign(row, patch);
return Promise.resolve({ success: true, data: structuredClone(row) });
},
delete: (id) => {
const index = rows.findIndex((item) => item.id === id);
if (index >= 0) {
rows.splice(index, 1);
}
return Promise.resolve({ success: index >= 0 });
},
...extra,
};
}import {
matchesContractFilter,
recordValue,
} from "@/components/ui/yayaw-table-vue/table-contracts";
import type { TableActions } from "@/components/ui/yayaw-table-vue/types";
import { listRecords } from "./list-records";
type MemoryRow = Record<string, unknown> & { id: string };
/**
* An in-memory host for the view examples. Replace it with authenticated
* server actions; views that need every row (calendar, map, chart, file tree)
* filter the loaded rows themselves when `list` ignores their `scope`.
*/
export function createMemoryActions<T extends MemoryRow>(
rows: T[],
extra: Partial<TableActions> = {}
): TableActions {
let created = 0;
return {
list: (params) => {
// Dashboard filters arrive as `requiredFilters`, always joined with AND.
const { requiredFilters } = recordValue(params);
const required = Array.isArray(requiredFilters)
? requiredFilters.map(recordValue)
: [];
return listRecords(
rows.filter((row) =>
required.every((rule) =>
matchesContractFilter(row[String(rule.columnId)], rule)
)
),
params
);
},
create: (values) => {
created += 1;
const row = { ...values, id: `new-${created}` } as T;
rows.push(row);
return Promise.resolve({ success: true, data: structuredClone(row) });
},
update: (id, patch) => {
const row = rows.find((item) => item.id === id);
if (!row) {
return Promise.resolve({ success: false, error: "Record not found." });
}
Object.assign(row, patch);
return Promise.resolve({ success: true, data: structuredClone(row) });
},
delete: (id) => {
const index = rows.findIndex((item) => item.id === id);
if (index >= 0) {
rows.splice(index, 1);
}
return Promise.resolve({ success: index >= 0 });
},
...extra,
};
}The table receives the configuration, the actions and the optional renderers:
import { DataTable } from "@/components/ui/yayaw-table";
import { calendarRenderer } from "@/components/ui/yayaw-table-calendar/calendar-renderer";
import { chartRenderer } from "@/components/ui/yayaw-table-chart/chart-renderer";
import { mapRenderer } from "@/components/ui/yayaw-table-map/map-renderer";
import { createProjectRows, geocodeProjectPlaces } from "./projects";
import { createMemoryActions } from "./memory-actions";
import { exampleConfig } from "./all-views";
const actions = createMemoryActions(createProjectRows(), {
geocode: geocodeProjectPlaces,
});
export function ProjectsTable() {
return (
<DataTable
displayModeRenderers={{
calendar: calendarRenderer,
chart: chartRenderer,
map: mapRenderer,
}}
getRowId={(row) => String(row.id)}
getTableActions={() => actions}
getTableConfig={() => exampleConfig}
tableType="projects"
/>
);
}<script setup lang="ts">
import { YayawDataTable } from "@/components/ui/yayaw-table-vue";
import { calendarRenderer } from "@/components/ui/yayaw-table-vue/calendar/calendar-renderer";
import { chartRenderer } from "@/components/ui/yayaw-table-vue/chart/chart-renderer";
import { mapRenderer } from "@/components/ui/yayaw-table-vue/map/map-renderer";
import { createProjectRows, geocodeProjectPlaces } from "./projects";
import { createMemoryActions } from "./memory-actions";
import { exampleConfig } from "./all-views";
const actions = createMemoryActions(createProjectRows(), {
geocode: geocodeProjectPlaces,
});
const renderers = { calendar: calendarRenderer, chart: chartRenderer, map: mapRenderer };
</script>
<template>
<YayawDataTable
table-type="projects"
:config="exampleConfig"
:get-table-actions="() => actions"
:get-row-id="(row) => String(row.id)"
:display-mode-renderers="renderers"
/>
</template>Like every table, the React edition renders inside your QueryClientProvider and nuqs adapter, and the Vue edition after VueQueryPlugin; see the React and Vue quick starts.