Docs
Views

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.

Expand ↗

Sample data. Changes stay in this preview.

Expand ↗

Sample data. Changes stay in this preview.

The twelve views

ViewModeShowsUse it toShips inGuide
TabletableRows and columns, with inline editing, grouping and totalsCompare values, edit several fields, select and exportCore itemLayout, Columns
ListlistOne compact line per record: a title and a few propertiesScan many records on any screen sizeCore itemList
BoardkanbanCards in lanes, one lane per option of a columnMove work through stagesCore itemKanban
GallerygalleryCards led by an image, with a media viewerBrowse products, photos and filesCore itemGallery
TimelineganttBars on a timeline, with hierarchy and dependenciesPlan dated work and see the impact of a changeCore itemGantt
CalendarcalendarMonth, week or list of dated recordsPlace events and deadlines, drag them to another dayOptional: yayaw-table-calendar, yayaw-table-vue-calendarCalendar
ChartchartBars, a line, a donut or one number, grouped on your serverShow totals and trends of the records in the viewOptional: yayaw-table-chart, yayaw-table-vue-chartChart view
FormformA form that creates one record per responseCollect requests, also on a public linkCore itemForm view
FeedfeedPosts with an author, a date, a body and mediaFollow updates and announcements, newest firstCore itemFeed view
MapmapMarkers from a location column, with clusters and popupsFind records by place, search an areaOptional: yayaw-table-map, yayaw-table-vue-mapMap view
DashboardYayawDashboardSaved views of several tables, numbers and notes on a gridGive an overview across tables, with shared filtersOptional: yayaw-table-dashboard, yayaw-table-vue-dashboardDashboard
File treefiletreeFolders and files linked by a parent columnBrowse and reorganize an asset libraryCore itemFile 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:

project-config.ts
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,
  },
});
project-config.ts
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).

memory-actions.ts
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,
  };
}
memory-actions.ts
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:

projects-table.tsx
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"
    />
  );
}
ProjectsTable.vue
<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.