Skip to content

Dropdown Editor

The ColumnDropdown column type renders the Pro dropdown editor inside a grid cell. Use it for single-select values, or enable config.multiSelect when the cell value is an array.

Register ColumnDropdown in columnTypes, set columnType: 'dropdown' on a column, and provide a dropdown.source list with { value, label } options.

Source code
TypeScriptts
import { defineCustomElements } from '@revolist/revogrid/loader';
import type { ColumnRegular } from '@revolist/revogrid';
import { ColumnDropdown } from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';

defineCustomElements();

const statusOptions = [
  { value: 'Open', label: 'Open' },
  { value: 'In Review', label: 'In Review' },
  { value: 'Complete', label: 'Complete' },
];

const tagOptions = [
  { value: 'Design', label: 'Design' },
  { value: 'Frontend', label: 'Frontend' },
  { value: 'Backend', label: 'Backend' },
  { value: 'QA', label: 'QA' },
];

const columns: ColumnRegular[] = [
  { prop: 'task', name: 'Task', size: 220 },
  {
    prop: 'status',
    name: 'Status',
    size: 180,
    columnType: 'dropdown',
    dropdown: {
      source: statusOptions,
      syncCellTemplate: true,
      placeholder: 'Select status',
      config: {
        search: true,
      },
    },
  },
  {
    prop: 'tags',
    name: 'Tags',
    size: 260,
    columnType: 'dropdown',
    dropdown: {
      source: tagOptions,
      syncCellTemplate: true,
      placeholder: 'Select tags',
      config: {
        multiSelect: true,
        search: true,
      },
    },
  },
];

export function load(parentSelector: string) {
  const grid = document.createElement('revo-grid');
  const { isDark } = currentTheme();

  grid.className = 'rounded-lg overflow-hidden cell-border';
  grid.style.minHeight = '300px';
  grid.range = true;
  grid.theme = isDark() ? 'darkMaterial' : 'material';
  grid.columnTypes = {
    dropdown: ColumnDropdown,
  };
  grid.columns = columns;
  grid.hideAttribution = true;

  document.querySelector(parentSelector)?.appendChild(grid);

  grid.source = [
    { task: 'Design filters', status: 'Open', tags: ['Design', 'Frontend'] },
    { task: 'Wire API payload', status: 'In Review', tags: ['Backend'] },
    { task: 'Regression pass', status: 'Complete', tags: ['QA', 'Frontend'] },
  ];

  return () => grid.remove();
}
Vuevue
<template>
  <RevoGrid
    class="rounded-lg overflow-hidden cell-border"
    range
    :columns="columns"
    :source="source"
    :column-types="columnTypes"
    :theme="isDark ? 'darkMaterial' : 'material'"
    hide-attribution
    style="min-height: 300px"
  />
</template>

<script setup lang="ts">
import { ref } from 'vue';
import RevoGrid from '@revolist/vue3-datagrid';
import type { ColumnRegular } from '@revolist/revogrid';
import { ColumnDropdown } from '@revolist/revogrid-pro';
import { currentThemeVue } from '../composables/useRandomData';

const { isDark } = currentThemeVue();

const source = ref([
  { task: 'Design filters', status: 'Open', tags: ['Design', 'Frontend'] },
  { task: 'Wire API payload', status: 'In Review', tags: ['Backend'] },
  { task: 'Regression pass', status: 'Complete', tags: ['QA', 'Frontend'] },
]);

const columnTypes = {
  dropdown: ColumnDropdown,
};

const columns: ColumnRegular[] = [
  { prop: 'task', name: 'Task', size: 220 },
  {
    prop: 'status',
    name: 'Status',
    size: 180,
    columnType: 'dropdown',
    dropdown: {
      syncCellTemplate: true,
      source: [
        { value: 'Open', label: 'Open' },
        { value: 'In Review', label: 'In Review' },
        { value: 'Complete', label: 'Complete' },
      ],
      placeholder: 'Select status',
      config: {
        search: true,
      },
    },
  },
  {
    prop: 'tags',
    name: 'Tags',
    size: 260,
    columnType: 'dropdown',
    dropdown: {
      syncCellTemplate: true,
      source: [
        { value: 'Design', label: 'Design' },
        { value: 'Frontend', label: 'Frontend' },
        { value: 'Backend', label: 'Backend' },
        { value: 'QA', label: 'QA' },
      ],
      placeholder: 'Select tags',
      config: {
        multiSelect: true,
        search: true,
      },
    },
  },
];
</script>
Reacttsx
import { useMemo } from 'react';
import { RevoGrid } from '@revolist/react-datagrid';
import type { ColumnRegular } from '@revolist/revogrid';
import { ColumnDropdown } from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';

function EditorDropdown() {
  const { isDark } = currentTheme();

  const source = useMemo(
    () => [
      { task: 'Design filters', status: 'Open', tags: ['Design', 'Frontend'] },
      { task: 'Wire API payload', status: 'In Review', tags: ['Backend'] },
      { task: 'Regression pass', status: 'Complete', tags: ['QA', 'Frontend'] },
    ],
    [],
  );

  const columnTypes = useMemo(
    () => ({
      dropdown: ColumnDropdown,
    }),
    [],
  );

  const columns: ColumnRegular[] = useMemo(
    () => [
      { prop: 'task', name: 'Task', size: 220 },
      {
        prop: 'status',
        name: 'Status',
        size: 180,
        columnType: 'dropdown',
        dropdown: {
          syncCellTemplate: true,
          source: [
            { value: 'Open', label: 'Open' },
            { value: 'In Review', label: 'In Review' },
            { value: 'Complete', label: 'Complete' },
          ],
          placeholder: 'Select status',
          config: {
            search: true,
          },
        },
      },
      {
        prop: 'tags',
        name: 'Tags',
        size: 260,
        columnType: 'dropdown',
        dropdown: {
          syncCellTemplate: true,
          source: [
            { value: 'Design', label: 'Design' },
            { value: 'Frontend', label: 'Frontend' },
            { value: 'Backend', label: 'Backend' },
            { value: 'QA', label: 'QA' },
          ],
          placeholder: 'Select tags',
          config: {
            multiSelect: true,
            search: true,
          },
        },
      },
    ],
    [],
  );

  return (
    <RevoGrid
      className="rounded-lg overflow-hidden cell-border"
      range
      source={source}
      columns={columns}
      columnTypes={columnTypes}
      theme={isDark() ? 'darkMaterial' : 'material'}
      hideAttribution
      style={{ minHeight: '300px' }}
    />
  );
}

export default EditorDropdown;
Angularts
import { Component, ViewEncapsulation } from '@angular/core';
import { RevoGrid } from '@revolist/angular-datagrid';
import { ColumnDropdown } from '@revolist/revogrid-pro';
import { currentTheme } from '../composables/useRandomData';

@Component({
  selector: 'editor-dropdown-grid',
  standalone: true,
  imports: [RevoGrid],
  template: `
    <revo-grid
      class="rounded-lg overflow-hidden cell-border"
      [range]="true"
      [source]="source"
      [columns]="columns"
      [columnTypes]="columnTypes"
      [theme]="theme"
      [hideAttribution]="true"
      style="min-height: 300px;"
    ></revo-grid>
  `,
  encapsulation: ViewEncapsulation.None,
})
export class EditorDropdownGridComponent {
  theme = currentTheme().isDark() ? 'darkMaterial' : 'material';

  source = [
    { task: 'Design filters', status: 'Open', tags: ['Design', 'Frontend'] },
    { task: 'Wire API payload', status: 'In Review', tags: ['Backend'] },
    { task: 'Regression pass', status: 'Complete', tags: ['QA', 'Frontend'] },
  ];

  columnTypes = {
    dropdown: ColumnDropdown,
  };

  columns = [
    { prop: 'task', name: 'Task', size: 220 },
    {
      prop: 'status',
      name: 'Status',
      size: 180,
      columnType: 'dropdown',
      dropdown: {
        syncCellTemplate: true,
        source: [
          { value: 'Open', label: 'Open' },
          { value: 'In Review', label: 'In Review' },
          { value: 'Complete', label: 'Complete' },
        ],
        placeholder: 'Select status',
        config: {
          search: true,
        },
      },
    },
    {
      prop: 'tags',
      name: 'Tags',
      size: 260,
      columnType: 'dropdown',
      dropdown: {
        syncCellTemplate: true,
        source: [
          { value: 'Design', label: 'Design' },
          { value: 'Frontend', label: 'Frontend' },
          { value: 'Backend', label: 'Backend' },
          { value: 'QA', label: 'QA' },
        ],
        placeholder: 'Select tags',
        config: {
          multiSelect: true,
          search: true,
        },
      },
    },
  ];
}

Features:

  • Supports single-select cells with scalar values
  • Supports multi-select cells with array values through config.multiSelect: true
  • Can enable search with config.search: true
  • Saves changes through RevoGrid cell edit events, so the grid data model stays in sync
  • Supports custom option and selected-value templates through the dropdown render callbacks
  • Can mirror the column cell template in options and selected values with dropdown.syncCellTemplate
  • Can disable the dropdown per cell and disable individual options

Set dropdown.disabled: true to keep the selected value visible while blocking the dropdown. A disabled dropdown never opens through a click, double-click, Enter, F2, typing, or config.autoOpen. It cannot change a selection or remove a multiselect chip.

import type { ColumnRegular } from '@revolist/revogrid';
import { ColumnDropdown } from '@revolist/revogrid-pro';
const grid = document.querySelector<HTMLRevoGridElement>('revo-grid')!;
grid.columnTypes = { dropdown: ColumnDropdown };
const columns: ColumnRegular[] = [{
prop: 'status',
name: 'Status',
columnType: 'dropdown',
dropdown: {
disabled: true,
source: [
{ value: 'active', label: 'Active' },
{ value: 'archived', label: 'Archived' },
],
},
}];
grid.columns = columns;
grid.source = [{ status: 'active' }];

Use a synchronous callback returning a boolean. It receives the current ColumnDataSchemaModel: model, value, column, prop, rowIndex, colIndex, type, colType, and data. The value is the stored cell value, even when cellParser supplies a display label. Keep this callback free of side effects; do not change grid state while rendering. Promise-returning callbacks are unsupported.

import type { ColumnRegular } from '@revolist/revogrid';
import { ColumnDropdown } from '@revolist/revogrid-pro';
const grid = document.querySelector<HTMLRevoGridElement>('revo-grid')!;
grid.columnTypes = { dropdown: ColumnDropdown };
const columns: ColumnRegular[] = [{
prop: 'status',
name: 'Status',
columnType: 'dropdown',
dropdown: {
disabled: ({ model }) => model.locked === true,
source: [
{ value: 'active', label: 'Active' },
{ value: 'archived', label: 'Archived' },
],
},
}];
grid.columns = columns;
grid.source = [
{ status: 'active', locked: false },
{ status: 'archived', locked: true },
];

The callback is resolved separately for each cell and reevaluated on grid updates. Update rows through the grid source/data APIs so the rendered state refreshes. Disabling an open dropdown closes it without saving a new value. Re-enabling restores interaction and does not automatically reopen the popup. The Formula Bar dropdown also respects the resolved disabled state.

Set disabled: true on an option to keep it visible while preventing selection. Other items remain available. Disabled options have disabled styling and aria-disabled="true"; initial focus and Arrow Up/Down navigation skip them. Click, Enter, and Space cannot select or toggle them. Search, sorting, and custom option renderers preserve these restrictions. A custom renderer should also make the disabled state visually clear.

import type { ColumnRegular } from '@revolist/revogrid';
import { ColumnDropdown } from '@revolist/revogrid-pro';
const grid = document.querySelector<HTMLRevoGridElement>('revo-grid')!;
grid.columnTypes = { dropdown: ColumnDropdown };
const statusOptions = [
{ value: 'active', label: 'Active' },
{ value: 'paused', label: 'Paused' },
{ value: 'archived', label: 'Archived', disabled: true },
];
const columns: ColumnRegular[] = [
{
prop: 'status',
name: 'Status',
columnType: 'dropdown',
dropdown: { source: statusOptions },
},
{
prop: 'tags',
name: 'Tags',
columnType: 'dropdown',
dropdown: {
source: statusOptions,
config: { multiSelect: true, search: true, sortDirection: 'asc' },
},
},
];
grid.columns = columns;
grid.source = [{ status: 'archived', tags: ['archived', 'active'] }];

An already-selected disabled value remains displayed and stored. For single-select cells, an enabled option can replace that value. For multiselect cells, disabled selected items cannot be toggled or removed, and their remove controls are omitted; enabled selected items can still change. The dropdown never silently removes stored values when options become disabled.

If all visible items are disabled, an enabled dropdown can still open to show them, but has no active option and cannot select an item. This also applies when a search matches only disabled items. Option disabling defaults to false and does not disable the whole dropdown.

SettingEffect
dropdown.disabledBlocks dropdown opening, selection, and chip removal; does not make the entire grid cell readonly.
column.readonlyBlocks dropdown interaction and Core cell edits such as paste and Delete; accepts a boolean or a cell-context callback.
grid.readonlyBlocks dropdown interaction and editing throughout the grid.
dropdown.source[i].disabledBlocks interaction with that option while keeping enabled options available.

Grid and column readonly rules always win, even when dropdown.disabled returns false. Dropdown columns are editable by default. Readonly cells preserve their passive cell rendering without an interactive dropdown trigger. Disabled dropdown triggers expose aria-disabled="true" and are removed from tab order.

See the Editor Dropdown API for the column contract and the Dropdown API for standalone properties and option types. Standalone DropdownProps.disabled accepts a boolean; cell callbacks belong to DropdownColumnProps.disabled.

Set dropdown.syncCellTemplate to true to reuse the column cellTemplate for both option rows and selected values. Object option fields are merged into the synthetic template model, so the same renderer can use metadata such as a status tone, avatar URL, or icon. Explicit renderOption and renderValue callbacks remain authoritative.

import type { ColumnRegular } from '@revolist/revogrid';
import { ColumnDropdown } from '@revolist/revogrid-pro';
const columns: ColumnRegular[] = [{
prop: 'status',
columnType: 'dropdown',
cellTemplate: (h, { model, value }) => h('span', {
class: `status-badge status-badge--${model.tone}`,
}, model.label ?? value),
dropdown: {
syncCellTemplate: true,
source: [
{ value: 'active', label: 'Active', tone: 'green' },
{ value: 'paused', label: 'Paused', tone: 'amber' },
],
},
}];
const grid = document.querySelector<HTMLRevoGridElement>('revo-grid')!;
grid.columnTypes = { dropdown: ColumnDropdown };
grid.columns = columns;