Skip to content

Row Status

RowStatusPlugin turns boolean columns into status checkboxes. The boolean remains in your source model, while the plugin adds semantic attributes to rendered rows and provides a default strike-through treatment.

Source code
TypeScript ts
import { defineCustomElements } from '@revolist/revogrid/loader';
import { currentTheme } from '../composables/useRandomData';
import {
  createRowStatusColumns,
  createRowStatusRows,
  ROW_STATUS_PLUGINS,
  ROW_STATUS_TREE,
  sortCompletedRows,
  type RowStatusDemoRow,
} from './row-status-demo.shared';
import './row-status-demo.css';

defineCustomElements();

export function load(parentSelector: string, rows?: RowStatusDemoRow[]) {
  const shell = document.createElement('div');
  const grid = document.createElement('revo-grid');
  const { isDark } = currentTheme();

  shell.className = 'row-status-demo-shell';
  grid.className = 'row-status-demo cell-border';
  grid.dataset.theme = isDark() ? 'dark' : 'light';
  grid.columns = createRowStatusColumns();
  grid.plugins = ROW_STATUS_PLUGINS;
  grid.columnTypes = {};
  grid.tree = ROW_STATUS_TREE;
  grid.theme = isDark() ? 'darkMaterial' : 'material';
  grid.hideAttribution = true;
  grid.addEventListener('rowstatuschange', event => {
    void sortCompletedRows(grid, event);
  });

  shell.appendChild(grid);
  document.querySelector(parentSelector)?.appendChild(shell);
  grid.source = createRowStatusRows(rows);

  return () => {
    grid.remove();
    shell.remove();
  };
}
Vue vue
<template>
  <div class="row-status-demo-shell">
    <RevoGrid
      ref="gridRef"
      class="row-status-demo rounded-lg overflow-hidden cell-border"
      :data-theme="isDark ? 'dark' : 'light'"
      :source="rows"
      :columns="columns"
      :plugins="plugins"
      :column-types="columnTypes"
      :tree="tree"
      :theme="isDark ? 'darkMaterial' : 'material'"
      hide-attribution
      @rowstatuschange="handleRowStatusChange"
    />
  </div>
</template>

<script setup lang="ts">
import RevoGrid from '@revolist/vue3-datagrid';
import { ref } from 'vue';
import { currentThemeVue } from '../composables/useRandomData';
import {
  createRowStatusColumns,
  createRowStatusRows,
  ROW_STATUS_PLUGINS,
  ROW_STATUS_TREE,
  sortCompletedRows,
  type RowStatusDemoRow,
} from './row-status-demo.shared';
import './row-status-demo.css';

const props = defineProps<{ rows?: RowStatusDemoRow[] }>();
const { isDark } = currentThemeVue();
const gridRef = ref<InstanceType<typeof RevoGrid> | null>(null);
const rows = ref(createRowStatusRows(props.rows));
const columns = createRowStatusColumns();
const plugins = ROW_STATUS_PLUGINS;
const tree = ROW_STATUS_TREE;
const columnTypes = {};

function handleRowStatusChange(event: CustomEvent) {
  void sortCompletedRows(gridRef.value?.$el as HTMLRevoGridElement | undefined, event as any);
}
</script>
React tsx
import React, { useMemo, useRef } from 'react';
import { RevoGrid } from '@revolist/react-datagrid';
import { currentTheme } from '../composables/useRandomData';
import {
  createRowStatusColumns,
  createRowStatusRows,
  ROW_STATUS_PLUGINS,
  ROW_STATUS_TREE,
  sortCompletedRows,
  type RowStatusDemoRow,
} from './row-status-demo.shared';
import './row-status-demo.css';

export default function RowStatus({ rows }: { rows?: RowStatusDemoRow[] }) {
  const gridRef = useRef<HTMLRevoGridElement | null>(null);
  const { isDark } = currentTheme();
  const source = useMemo(() => createRowStatusRows(rows), [rows]);
  const columns = useMemo(() => createRowStatusColumns(), []);
  const plugins = useMemo(() => ROW_STATUS_PLUGINS, []);
  const columnTypes = useMemo(() => ({}), []);

  return (
    <div className="row-status-demo-shell">
      <RevoGrid
        ref={gridRef as any}
        className="row-status-demo rounded-lg overflow-hidden cell-border"
        data-theme={isDark() ? 'dark' : 'light'}
        source={source}
        columns={columns}
        plugins={plugins}
        columnTypes={columnTypes}
        tree={ROW_STATUS_TREE}
        theme={isDark() ? 'darkMaterial' : 'material'}
        hideAttribution
        onRowstatuschange={event => void sortCompletedRows(gridRef.current, event)}
      />
    </div>
  );
}
Angular ts
import {
  Component,
  ElementRef,
  NO_ERRORS_SCHEMA,
  ViewChild,
  ViewEncapsulation,
} from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
import { currentTheme } from '../composables/useRandomData';
import {
  createRowStatusColumns,
  createRowStatusRows,
  ROW_STATUS_PLUGINS,
  ROW_STATUS_TREE,
  sortCompletedRows,
} from './row-status-demo.shared';
import './row-status-demo.css';

@Component({
  selector: 'row-status-grid',
  standalone: true,
  imports: [RevoGrid],
  encapsulation: ViewEncapsulation.None,
  // Allows direct Pro plugin props that are not generated wrapper inputs yet.
  schemas: [NO_ERRORS_SCHEMA],
  template: `
    <revo-grid
      #grid
      class="row-status-demo rounded-lg overflow-hidden cell-border"
      [attr.data-theme]="isDark ? 'dark' : 'light'"
      [source]="source"
      [columns]="columns"
      [plugins]="plugins"
      [columnTypes]="columnTypes"
      [tree]="tree"
      [theme]="isDark ? 'darkMaterial' : 'material'"
      [hideAttribution]="true"
      (rowstatuschange)="handleRowStatusChange($event)"
    ></revo-grid>
  `,
})
export class RowStatusGridComponent {
  @ViewChild('grid', { read: ElementRef }) grid?: ElementRef<HTMLRevoGridElement>;

  readonly isDark = currentTheme().isDark();
  readonly source = createRowStatusRows();
  readonly columns = createRowStatusColumns();
  readonly plugins = ROW_STATUS_PLUGINS;
  readonly columnTypes = {};
  readonly tree = ROW_STATUS_TREE;

  handleRowStatusChange(event: CustomEvent) {
    void sortCompletedRows(this.grid?.nativeElement, event as any);
  }
}
Shared ts
import type { ColumnRegular } from '@revolist/revogrid';
import {
  ColumnStretchPlugin,
  RowStatusPlugin,
  TreeDataPlugin,
  type RowStatusChangeEvent,
  type TreeConfig,
} from '@revolist/revogrid-pro';

export type RowStatusDemoRow = {
  id: number;
  parentId: number | null;
  task: string;
  owner: string;
  due: string;
  done: boolean;
  archived: boolean;
};

export const ROW_STATUS_TREE: TreeConfig = {
  idField: 'id',
  parentIdField: 'parentId',
  rootParentId: null,
  expandAll: false,
  expandedRowIds: new Set([1]),
};

export const ROW_STATUS_PLUGINS = [
  TreeDataPlugin,
  RowStatusPlugin,
  ColumnStretchPlugin,
];

const DEFAULT_ROWS: RowStatusDemoRow[] = [
  { id: 1, parentId: null, task: 'Launch documentation refresh', owner: 'Mia', due: 'Jul 18', done: false, archived: false },
  { id: 2, parentId: 1, task: 'Audit API examples', owner: 'Noah', due: 'Jul 15', done: true, archived: false },
  { id: 3, parentId: 1, task: 'Record migration notes', owner: 'Ava', due: 'Jul 16', done: false, archived: false },
  { id: 4, parentId: 1, task: 'Publish framework snippets', owner: 'Liam', due: 'Jul 18', done: false, archived: false },
  { id: 5, parentId: null, task: 'Retire legacy onboarding', owner: 'Sophia', due: 'Jul 22', done: false, archived: false },
  { id: 6, parentId: 5, task: 'Export old checklist', owner: 'Ethan', due: 'Jul 19', done: true, archived: true },
  { id: 7, parentId: 5, task: 'Redirect setup guide', owner: 'Olivia', due: 'Jul 20', done: false, archived: false },
  { id: 8, parentId: 5, task: 'Remove deprecated screenshots', owner: 'Lucas', due: 'Jul 22', done: false, archived: false },
];

export function createRowStatusRows(rows?: RowStatusDemoRow[]) {
  const source = rows?.length ? rows : DEFAULT_ROWS;
  return source.map(row => ({ ...row }));
}

export function createRowStatusColumns(): ColumnRegular[] {
  return [
    {
      prop: 'done',
      name: 'Done',
      size: 76,
      sortable: true,
      order: 'asc',
      rowStatus: { attribute: 'data-done' },
    },
    {
      prop: 'archived',
      name: 'Archived',
      size: 92,
      rowStatus: { attribute: 'data-archived' },
    },
    { prop: 'task', name: 'Task', size: 290, tree: true },
    { prop: 'owner', name: 'Owner', size: 120 },
    { prop: 'due', name: 'Due', size: 100 },
  ];
}

export async function sortCompletedRows(
  grid: HTMLRevoGridElement | null | undefined,
  event?: CustomEvent<RowStatusChangeEvent>,
) {
  if (!grid || (event && event.detail.prop !== 'done')) {
    return;
  }
  await grid.updateColumnSorting({ prop: 'done' }, 'asc', false);
}
Styles css
.demo-preview-fill-root:has(.row-status-demo),
.preview-surface-live:has(.row-status-demo) {
  width: calc(100% - 16px) !important;
  height: calc(100% - 16px) !important;
  margin: 8px;
}

.row-status-demo-shell {
  display: flex;
  flex: 1 1 auto;
  height: 100%;
  min-height: 0;
  width: 100%;
}

.row-status-demo {
  min-height: 420px;
}

.row-status-demo[data-theme='dark'] {
  --revo-row-status-color: rgba(226, 232, 240, 0.62);
}

.row-status-demo .rgRow[data-archived] .rgCell:not(.row-status-cell) {
  --revo-row-status-text-decoration: line-through double;
}

Key behavior

  • Status values are ordinary source booleans, so they can be saved, filtered, exported, and sorted.
  • Every status column defines its own row attribute.
  • Multiple status columns work independently.
  • Tree parents cascade changes to descendants and reflect partial completion with an indeterminate checkbox.
  • Completed-row sorting is application policy and stays outside the plugin.

Register the plugin and add rowStatus to a boolean column. Only the strict value true activates the status.

import { RowStatusPlugin } from '@revolist/revogrid-pro';
grid.plugins = [RowStatusPlugin];
grid.columns = [
{
prop: 'done',
name: 'Done',
rowStatus: { attribute: 'data-done' },
},
{ prop: 'task', name: 'Task' },
];
grid.source = [
{ task: 'Write release notes', done: false },
{ task: 'Publish the build', done: true },
];

An active row receives both [data-done] and the common [row-status] attribute. The common attribute drives the built-in strike-through style.

The checkbox cell stays interactive while normal row cells receive the status treatment. Override the CSS variables on the grid when a different presentation is needed:

revo-grid.my-todos {
--revo-row-status-color: #64748b;
--revo-row-status-opacity: 0.7;
--revo-row-status-text-decoration: line-through;
--revo-row-status-checkbox-size: 20px;
--revo-row-status-checkbox-radius: 50%;
}
revo-grid.my-todos .rgRow[data-archived] .rgCell:not(.row-status-cell) {
--revo-row-status-text-decoration: line-through double;
}

Row-status controls are circular by default so they remain visually distinct from regular checkbox editors and row-selection checkboxes. Set --revo-row-status-checkbox-radius to another value if a different treatment is preferred.

Each column uses its own source prop and attribute. A row keeps [row-status] while any configured status is active.

grid.columns = [
{
prop: 'done',
name: 'Done',
rowStatus: { attribute: 'data-done' },
},
{
prop: 'archived',
name: 'Archived',
rowStatus: { attribute: 'data-archived' },
},
{ prop: 'task', name: 'Task' },
];

Add TreeDataPlugin and normal tree configuration. Propagation is isolated per status column.

import { RowStatusPlugin, TreeDataPlugin } from '@revolist/revogrid-pro';
grid.plugins = [TreeDataPlugin, RowStatusPlugin];
grid.tree = {
idField: 'id',
parentIdField: 'parentId',
rootParentId: null,
expandAll: true,
};
grid.columns = [
{ prop: 'done', name: 'Done', rowStatus: { attribute: 'data-done' } },
{ prop: 'task', name: 'Task', tree: true },
];

| Action | Result | | --- | --- | | Check a parent | The parent and every descendant become complete, including collapsed or filtered descendants. | | Clear a parent | The parent and every descendant become incomplete. | | Complete every child | Ancestors become complete from the deepest parent to the root. | | Complete only some children | The parent value remains false and its checkbox renders indeterminate. | | Load inconsistent initial values | The source is not rewritten; subtree state is displayed and affected ancestors reconcile on the next edit. |

Classic grouping works automatically when grid.grouping is configured. Every synthetic group label renders one checkbox for each row-status column; no extra plugin option is required.

grid.plugins = [RowStatusPlugin];
grid.grouping = {
props: ['department', 'team'],
expandedAll: true,
};
grid.columns = [
{ prop: 'done', name: 'Done', rowStatus: { attribute: 'data-done' } },
{ prop: 'archived', name: 'Archived', rowStatus: { attribute: 'data-archived' } },
{ prop: 'task', name: 'Task' },
{ prop: 'department', name: 'Department' },
{ prop: 'team', name: 'Team' },
];

| Grouping action | Result | | --- | --- | | Check a group status | Every real descendant receives that boolean value, including descendants hidden by collapsed groups or filters. | | Edit one child | Ancestor group checkboxes recompute as checked, unchecked, or indeterminate. | | Use multiple statuses | Each group checkbox and source prop remains independent. | | Sort, filter, or regroup | Status remains attached to the real source model through its original physical index. | | Listen for rowstatuschange | Only real models and application-source indexes are included; synthetic group rows are excluded. |

Normal data rows receive the configured attribute directly. Because core grouping headers are synthetic rows rendered through the group-label template, their derived attributes are placed on .row-status-group-label instead. The same [row-status] styling is applied when every descendant is complete for at least one status.

The directly clicked checkbox uses the regular beforeedit and afteredit events. Listen for rowstatuschange when you need the complete hierarchy batch:

grid.addEventListener('rowstatuschange', event => {
const {
prop,
value,
physicalIndexes,
models,
} = event.detail;
persistStatusChanges({ prop, value, physicalIndexes, models });
});

Sorting is intentionally configured by the application. Set ascending boolean sorting (false before true) and reapply it after a completed-status batch:

grid.columns = [
{
prop: 'done',
name: 'Done',
sortable: true,
order: 'asc',
rowStatus: { attribute: 'data-done' },
},
{ prop: 'task', name: 'Task', tree: true },
];
grid.addEventListener('rowstatuschange', event => {
if (event.detail.prop === 'done') {
void grid.updateColumnSorting({ prop: 'done' }, 'asc', false);
}
});

See the generated RowStatusPlugin API reference for exported types, constants, and the full JSDoc example.