Skip to content
JavaScript
import { useState, useEffect, useCallback } from 'react';
import { HotTable, HotColumn, EditorComponent } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
/* start:skip-in-preview */
export const data = [
{ feature: 'Dark Mode', category: 'UI', priority: 'High', feedback: '👍', votes: 124, status: 'Planned' },
{ feature: 'Bulk Edit', category: 'Core', priority: 'High', feedback: '👍', votes: 98, status: 'In Progress' },
{ feature: 'AI Suggestions', category: 'Beta', priority: 'Medium', feedback: '🤷', votes: 45, status: 'Research' },
{ feature: 'Offline Mode', category: 'Infra', priority: 'Low', feedback: '👎', votes: 12, status: 'Backlog' },
];
/* end:skip-in-preview */
export const FeedbackEditor = () => {
const [config, setConfig] = useState(['👍', '👎', '🤷']);
const [shortcuts, setShortcuts] = useState([]);
const onPrepare = (_row, _column, _prop, _TD, _originalValue, cellProperties) => {
setConfig(cellProperties.config);
};
useEffect(() => {
setShortcuts([
{
keys: [['ArrowRight'], ['Tab']],
callback: ({ value, setValue }, _event) => {
setValue(getNextValue(value));
return false;
},
},
{
keys: [['ArrowLeft']],
callback: ({ value, setValue }, _event) => {
setValue(getPrevValue(value));
},
},
]);
}, [config]);
const getNextValue = useCallback(
(value) => {
const index = config.indexOf(value);
return index === config.length - 1 ? config[0] : config[index + 1];
},
[config]
);
const getPrevValue = useCallback(
(value) => {
const index = config.indexOf(value);
return index === 0 ? config[config.length - 1] : config[index - 1];
},
[config]
);
return (
<EditorComponent onPrepare={onPrepare} shortcuts={shortcuts}>
{({ value, setValue, finishEditing }) => (
<>
<div className="feedback-editor">
{config.map((item, _index, _array) => (
<button
className={value === item ? 'active' : ''}
key={item}
onClick={() => {
setValue(item);
finishEditing();
}}
style={{
width: `${100 / _array.length}%`,
}}
>
{item}
</button>
))}
</div>
</>
)}
</EditorComponent>
);
};
const ExampleComponent = () => {
return (
<HotTable
autoRowSize={true}
rowHeaders={true}
autoWrapRow={true}
licenseKey="non-commercial-and-evaluation"
height="auto"
width="100%"
data={data}
colHeaders={['Feature', 'Category', 'Priority', 'Feedback', 'Votes', 'Status']}
headerClassName="htLeft"
>
<HotColumn data="feature" type="text" width={200} />
<HotColumn data="category" type="text" width={90} />
<HotColumn data="priority" type="text" width={100} />
<HotColumn data="feedback" type="text" width={100} editor={FeedbackEditor} config={['👍', '👎', '🤷']} />
<HotColumn data="votes" type="numeric" width={60} />
<HotColumn data="status" type="text" width={120} />
</HotTable>
);
};
export default ExampleComponent;
TypeScript
import { useState, useEffect, ComponentProps, useCallback } from 'react';
import { HotTable, HotColumn, EditorComponent } from '@handsontable/react-wrapper';
import { registerAllModules } from 'handsontable/registry';
// register Handsontable's modules
registerAllModules();
/* start:skip-in-preview */
export const data = [
{ feature: 'Dark Mode', category: 'UI', priority: 'High', feedback: '👍', votes: 124, status: 'Planned' },
{ feature: 'Bulk Edit', category: 'Core', priority: 'High', feedback: '👍', votes: 98, status: 'In Progress' },
{ feature: 'AI Suggestions', category: 'Beta', priority: 'Medium', feedback: '🤷', votes: 45, status: 'Research' },
{ feature: 'Offline Mode', category: 'Infra', priority: 'Low', feedback: '👎', votes: 12, status: 'Backlog' },
];
/* end:skip-in-preview */
type EditorComponentProps = ComponentProps<typeof EditorComponent<string>>;
export const FeedbackEditor = () => {
const [config, setConfig] = useState<string[]>(['👍', '👎', '🤷']);
const [shortcuts, setShortcuts] = useState<EditorComponentProps['shortcuts']>([]);
const onPrepare: EditorComponentProps['onPrepare'] = (_row, _column, _prop, _TD, _originalValue, cellProperties) => {
setConfig(cellProperties.config as string[]);
};
useEffect(() => {
setShortcuts([
{
keys: [['ArrowRight'], ['Tab']],
callback: ({value, setValue}, _event) => {
setValue(getNextValue(value));
return false;
}
},
{
keys: [['ArrowLeft']],
callback: ({value, setValue}, _event) => {
setValue(getPrevValue(value));
}
}
])
}, [config]);
const getNextValue = useCallback((value: string) => {
const index = config.indexOf(value);
return index === config.length - 1 ? config[0] : config[index + 1];
}, [config]);
const getPrevValue = useCallback((value: string) => {
const index = config.indexOf(value);
return index === 0 ? config[config.length - 1] : config[index - 1];
}, [config]);
return (
<EditorComponent<string> onPrepare={onPrepare} shortcuts={shortcuts}>
{({ value, setValue, finishEditing }) => (
<>
<div className="feedback-editor">
{config.map((item, _index, _array) => (
<button
className={value === item ? 'active' : ''}
key={item}
onClick={() => {
setValue(item);
finishEditing();
}}
style={{
width: 100 / _array.length + '%'
}}
>
{item}
</button>
))}
</div>
</>
)}
</EditorComponent>
);
};
const ExampleComponent = () => {
return (
<HotTable
autoRowSize={true}
rowHeaders={true}
autoWrapRow={true}
licenseKey="non-commercial-and-evaluation"
height="auto"
width="100%"
data={data}
colHeaders={['Feature', 'Category', 'Priority', 'Feedback', 'Votes', 'Status']}
headerClassName="htLeft"
>
<HotColumn data="feature" type="text" width={200} />
<HotColumn data="category" type="text" width={90} />
<HotColumn data="priority" type="text" width={100} />
<HotColumn data="feedback" type="text" width={100} editor={FeedbackEditor} config={['👍', '👎', '🤷']} />
<HotColumn data="votes" type="numeric" width={60} />
<HotColumn data="status" type="text" width={120} />
</HotTable>
);
};
export default ExampleComponent;
CSS
.feedback-editor {
display: flex;
gap: var(--ht-gap, 4px);
width: 100%;
height: 100%;
box-sizing: border-box !important;
padding: var(--ht-cell-vertical-padding, 4px) var(--ht-cell-horizontal-padding, 8px);
background-color: var(--ht-cell-editor-background-color, #ffffff);
box-shadow: inset 0 0 0 var(--ht-cell-editor-border-width, 2px)
var(--ht-cell-editor-border-color, #1a42e8),
0 0 var(--ht-cell-editor-shadow-blur-radius, 0) 0
var(--ht-cell-editor-shadow-color, transparent);
border: none;
border-radius: 0;
}
.feedback-editor button {
background: var(--ht-background-color, #ffffff);
color: var(--ht-foreground-color, #000000);
border: 1px solid var(--ht-border-color, #e0e0e0);
border-radius: var(--ht-border-radius, 4px);
padding: 0;
margin: 0;
height: 100%;
width: 33%;
font-size: var(--ht-font-size, 14px);
text-align: center;
cursor: pointer;
}
.feedback-editor button:hover {
background: var(--ht-border-color, #e0e0e0);
}
.feedback-editor button.active,
.feedback-editor button.active:hover {
background: var(--ht-accent-color, #1a42e8);
color: #ffffff;
border-color: var(--ht-accent-color, #1a42e8);
}

Overview

This guide shows how to create a feedback editor cell using emoji buttons (👍, 👎, 🤷) with React’s EditorComponent. The example uses a feature-roadmap table with a Feedback column; the editor supports quick selection, keyboard navigation (Arrow keys, Tab), and per-column configuration via external CSS.

Difficulty: Beginner Time: ~15 minutes Libraries: None

What You’ll Build

A cell that:

  • Displays emoji feedback buttons (👍, 👎, 🤷) when editing
  • Shows the selected emoji when viewing
  • Supports keyboard navigation (Arrow Left/Right and Tab to cycle options)
  • Provides click-to-select and closes the editor on choice
  • Uses React’s EditorComponent with render prop and external CSS (e.g. feedback-editor class)
  • Reads per-column options from cellProperties.config in onPrepare

Prerequisites

Terminal window
npm install @handsontable/react-wrapper

What you need:

  • React 16.8+ (hooks support)
  • @handsontable/react-wrapper package
  • Basic React knowledge (hooks, JSX)
  1. Import Dependencies

    import { useState, useEffect, useCallback, ComponentProps } from 'react';
    import { HotTable, HotColumn, EditorComponent } from '@handsontable/react-wrapper';
    import { registerAllModules } from 'handsontable/registry';
    registerAllModules();

    What we’re importing:

    • EditorComponent - React component for creating custom editors
    • HotTable and HotColumn - React wrapper components
    • React hooks for state management
    • Handsontable styles
  2. Create the Editor Component

    Create a React component that uses EditorComponent with the render prop pattern.

    type EditorComponentProps = ComponentProps<typeof EditorComponent<string>>;
    const FeedbackEditor = () => {
    const [config, setConfig] = useState<string[]>(['👍', '👎', '🤷']);
    return (
    <EditorComponent<string>>
    {({ value, setValue, finishEditing }) => (
    <div className="editor">
    {config.map((item) => (
    <button
    key={item}
    className={`button ${value === item ? 'active' : ''}`}
    onClick={() => {
    setValue(item);
    finishEditing();
    }}
    >
    {item}
    </button>
    ))}
    </div>
    )}
    </EditorComponent>
    );
    };

    What’s happening:

    1. EditorComponent wraps your editor UI
    2. The children prop is a function that receives editor state
    3. value - Current editor value
    4. setValue - Function to update the value
    5. finishEditing - Function to save and close the editor
    6. Render buttons for each option in the config
    7. Highlight the active button based on current value

    Key concepts:

    • Render prop pattern: EditorComponent uses a function as children
    • State management: value and setValue are provided by EditorComponent
    • React components: Use standard React patterns (JSX, className, onClick)
  3. Add Styling

    Style the editor container and buttons using CSS or inline styles.

    const FeedbackEditor = () => {
    const [config, setConfig] = useState<string[]>(['👍', '👎', '🤷']);
    return (
    <EditorComponent<string>>
    {({ value, setValue, finishEditing }) => (
    <>
    <style>{`
    .editor {
    box-sizing: border-box;
    display: flex;
    gap: 3px;
    padding: 3px;
    background: rgb(238, 238, 238);
    border: 1px solid rgb(204, 204, 204);
    border-radius: 4px;
    height: 100%;
    width: 100%;
    }
    .button.active {
    background: #007bff;
    color: white;
    }
    .button:hover {
    background: #f0f0f0;
    }
    .button {
    background: #fff;
    color: black;
    border: none;
    padding: 0;
    margin: 0;
    height: 100%;
    width: 100%;
    font-size: 16px;
    font-weight: bold;
    text-align: center;
    cursor: pointer;
    }
    `}</style>
    <div className="editor">
    {config.map((item, _index, _array) => (
    <button
    key={item}
    className={`button ${value === item ? 'active' : ''}`}
    onClick={() => {
    setValue(item);
    finishEditing();
    }}
    style={{
    width: `${100 / _array.length}%`
    }}
    >
    {item}
    </button>
    ))}
    </div>
    </>
    )}
    </EditorComponent>
    );
    };

    What’s happening:

    • Container uses flexbox for horizontal button layout
    • Buttons dynamically size based on config length
    • Active button has blue background
    • Hover effects for better UX

    Key styling:

    • display: flex - Horizontal button layout
    • gap: 3px - Space between buttons
    • width: ${100 / _array.length}% - Dynamic button width
    • .active class - Highlights selected button
  4. Read Config from Cell Properties

    Use onPrepare to read per-column configuration.

    const FeedbackEditor = () => {
    const [config, setConfig] = useState<string[]>(['👍', '👎', '🤷']);
    const onPrepare: EditorComponentProps['onPrepare'] = (
    _row,
    _column,
    _prop,
    _TD,
    _originalValue,
    cellProperties
    ) => {
    // Read config from column definition
    if (cellProperties.config) {
    setConfig(cellProperties.config as string[]);
    }
    };
    return (
    <EditorComponent<string> onPrepare={onPrepare}>
    {({ value, setValue, finishEditing }) => (
    // ... editor UI
    )}
    </EditorComponent>
    );
    };

    What’s happening:

    • onPrepare is called before the editor opens
    • cellProperties contains column-specific configuration
    • Read config from cellProperties.config
    • Update state to reflect column-specific options

    Why this matters:

    • Different columns can have different options
    • One editor component, multiple configurations
    • Dynamic options based on column settings
  5. Add Keyboard Shortcuts

    Add keyboard navigation using the shortcuts prop.

    const FeedbackEditor = () => {
    const [config, setConfig] = useState<string[]>(['👍', '👎', '🤷']);
    const [shortcuts, setShortcuts] = useState<EditorComponentProps['shortcuts']>([]);
    const getNextValue = useCallback((value: string) => {
    const index = config.indexOf(value);
    return index === config.length - 1 ? config[0] : config[index + 1];
    }, [config]);
    const getPrevValue = useCallback((value: string) => {
    const index = config.indexOf(value);
    return index === 0 ? config[config.length - 1] : config[index - 1];
    }, [config]);
    useEffect(() => {
    setShortcuts([
    {
    keys: [['ArrowRight'], ['Tab']],
    callback: ({ value, setValue }, _event) => {
    setValue(getNextValue(value));
    return false; // Prevent default Tab behavior
    }
    },
    {
    keys: [['ArrowLeft']],
    callback: ({ value, setValue }, _event) => {
    setValue(getPrevValue(value));
    }
    }
    ]);
    }, [config, getNextValue, getPrevValue]);
    return (
    <EditorComponent<string> shortcuts={shortcuts}>
    {({ value, setValue, finishEditing }) => (
    // ... editor UI
    )}
    </EditorComponent>
    );
    };

    What’s happening:

    • ArrowRight/Tab: Move to next option (wraps to first if at end)
    • ArrowLeft: Move to previous option (wraps to last if at start)
    • callback receives { value, setValue, finishEditing } as first parameter
    • Return false to prevent default behavior (e.g., Tab moving to next cell)

    Keyboard navigation benefits:

    • Fast selection without mouse
    • Accessible for keyboard-only users
    • Intuitive left/right navigation
    • Tab cycles through options instead of moving cells
  6. Complete Editor Component

    Put it all together:

    type EditorComponentProps = ComponentProps<typeof EditorComponent<string>>;
    const FeedbackEditor = () => {
    const [config, setConfig] = useState<string[]>(['👍', '👎', '🤷']);
    const [shortcuts, setShortcuts] = useState<EditorComponentProps['shortcuts']>([]);
    const onPrepare: EditorComponentProps['onPrepare'] = (
    _row,
    _column,
    _prop,
    _TD,
    _originalValue,
    cellProperties
    ) => {
    if (cellProperties.config) {
    setConfig(cellProperties.config as string[]);
    }
    };
    const getNextValue = useCallback((value: string) => {
    const index = config.indexOf(value);
    return index === config.length - 1 ? config[0] : config[index + 1];
    }, [config]);
    const getPrevValue = useCallback((value: string) => {
    const index = config.indexOf(value);
    return index === 0 ? config[config.length - 1] : config[index - 1];
    }, [config]);
    useEffect(() => {
    setShortcuts([
    {
    keys: [['ArrowRight'], ['Tab']],
    callback: ({ value, setValue }, _event) => {
    setValue(getNextValue(value));
    return false;
    }
    },
    {
    keys: [['ArrowLeft']],
    callback: ({ value, setValue }, _event) => {
    setValue(getPrevValue(value));
    }
    }
    ]);
    }, [config, getNextValue, getPrevValue]);
    return (
    <EditorComponent<string> onPrepare={onPrepare} shortcuts={shortcuts}>
    {({ value, setValue, finishEditing }) => (
    <>
    <style>{`
    .editor {
    box-sizing: border-box;
    display: flex;
    gap: 3px;
    padding: 3px;
    background: rgb(238, 238, 238);
    border: 1px solid rgb(204, 204, 204);
    border-radius: 4px;
    height: 100%;
    width: 100%;
    }
    .button.active:hover,
    .button.active {
    background: #007bff;
    color: white;
    }
    .button:hover {
    background: #f0f0f0;
    }
    .button {
    background: #fff;
    color: black;
    border: none;
    padding: 0;
    margin: 0;
    height: 100%;
    width: 100%;
    font-size: 16px;
    font-weight: bold;
    text-align: center;
    cursor: pointer;
    }
    `}</style>
    <div className="editor">
    {config.map((item, _index, _array) => (
    <button
    key={item}
    className={`button ${value === item ? 'active' : ''}`}
    onClick={() => {
    setValue(item);
    finishEditing();
    }}
    style={{
    width: `${100 / _array.length}%`
    }}
    >
    {item}
    </button>
    ))}
    </div>
    </>
    )}
    </EditorComponent>
    );
    };

    What’s happening:

    • State management: config and shortcuts managed with React hooks
    • onPrepare: Reads column-specific config
    • shortcuts: Keyboard navigation handlers
    • Render prop: Renders buttons based on config
    • Styling: CSS-in-JS for editor appearance
  7. Use in Handsontable

    Use the editor component in your HotTable:

    const ExampleComponent = () => {
    return (
    <HotTable
    autoRowSize={true}
    rowHeaders={true}
    autoWrapRow={true}
    licenseKey="non-commercial-and-evaluation"
    height="auto"
    data={data}
    colHeaders={true}
    >
    <HotColumn
    width={250}
    editor={FeedbackEditor}
    config={['👍', '👎', '🤷']}
    data="feedback"
    title="Feedback"
    />
    <HotColumn
    width={250}
    editor={FeedbackEditor}
    config={['1', '2', '3', '4', '5']}
    data="stars"
    title="Rating (1-5)"
    />
    </HotTable>
    );
    };

    What’s happening:

    • editor={FeedbackEditor} - Assigns the editor component to the column
    • config={['👍', '👎', '🤷']} - Column-specific options
    • Same editor component, different configurations per column

    Key features:

    • Reusable editor component
    • Per-column configuration
    • Type-safe with TypeScript

How It Works - Complete Flow

  1. Initial Render: Cell displays the emoji value (👍, 👎, or 🤷)
  2. User Double-Clicks or Enter: Editor opens, onPrepare reads column config
  3. Editor Opens: EditorComponent positions container over cell
  4. Button Display: All options visible, current value highlighted
  5. User Interaction:
    • Click a button → setValue(item) and finishEditing() called
    • Press ArrowLeft/Right → Shortcut callback updates value
    • Press Tab → Cycles through options (prevents default cell navigation)
  6. Visual Feedback: Selected button highlighted in blue
  7. User Confirms: Press Enter, click button, or click away
  8. Save: Value saved to cell
  9. Editor Closes: Cell shows selected emoji

Enhancements

  1. Custom Renderer with Styling

    Add a custom renderer to style the emoji display:

    import { rendererFactory } from 'handsontable/renderers';
    const cellDefinition = {
    renderer: rendererFactory(({ td, value }) => {
    td.innerHTML = `
    <div style="text-align: center; font-size: 1.5em; padding: 4px;">
    ${value || '🤷'}
    </div>
    `;
    })
    };
    // Use in HotColumn
    <HotColumn
    editor={FeedbackEditor}
    renderer={cellDefinition.renderer}
    config={['👍', '👎', '🤷']}
    data="feedback"
    />

    What’s happening:

    • Center-aligns the emoji
    • Increases font size for better visibility
    • Adds padding for spacing
  2. More Feedback Options

    Add more emoji options:

    <HotColumn
    editor={FeedbackEditor}
    config={['👍', '👎', '🤷', '❤️', '🔥', '⭐']}
    data="feedback"
    />

    The editor automatically adjusts button widths based on config length.

  3. Custom Button Styling

    Enhanced button appearance with CSS:

    <style>{`
    .button {
    padding: 8px;
    border: 2px solid #ddd;
    background: white;
    color: #333;
    border-radius: 4px;
    cursor: pointer;
    font-size: 1.2em;
    transition: all 0.2s;
    }
    .button.active {
    border-color: #007bff;
    background: #007bff;
    color: white;
    }
    .button:hover {
    transform: scale(1.05);
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
    }
    `}</style>
  4. Dynamic Config from Cell Properties

    The onPrepare hook already handles this! Just pass different configs:

    <HotColumn
    editor={FeedbackEditor}
    config={['👍', '👎', '❤️', '🔥']}
    data="feedback"
    />
  5. Tooltip on Hover

    Add tooltips to buttons:

    {config.map((item) => {
    const tooltips: Record<string, string> = {
    '👍': 'Positive feedback',
    '👎': 'Negative feedback',
    '🤷': 'Neutral feedback'
    };
    return (
    <button
    key={item}
    className={`button ${value === item ? 'active' : ''}`}
    onClick={() => {
    setValue(item);
    finishEditing();
    }}
    title={tooltips[item] || ''}
    >
    {item}
    </button>
    );
    })}
  6. Text Labels Instead of Emojis

    Use text buttons for clarity:

    <HotColumn
    editor={FeedbackEditor}
    config={['Positive', 'Negative', 'Neutral']}
    data="feedback"
    />

    The editor works with any string values, not just emojis.

  7. Using External CSS File

    Move styles to a separate CSS file:

    feedback-editor.css
    .editor {
    box-sizing: border-box;
    display: flex;
    gap: 3px;
    padding: 3px;
    background: rgb(238, 238, 238);
    border: 1px solid rgb(204, 204, 204);
    border-radius: 4px;
    height: 100%;
    width: 100%;
    }
    .button.active {
    background: #007bff;
    color: white;
    }
    .button:hover {
    background: #f0f0f0;
    }
    .button {
    background: #fff;
    color: black;
    border: none;
    padding: 0;
    margin: 0;
    height: 100%;
    width: 100%;
    font-size: 16px;
    font-weight: bold;
    text-align: center;
    cursor: pointer;
    }
    import './feedback-editor.css';
    const FeedbackEditor = () => {
    // ... component code without <style> tag
    };

Accessibility

React buttons are inherently accessible, but you can enhance them:

{config.map((item, index) => (
<button
key={item}
className={`button ${value === item ? 'active' : ''}`}
onClick={() => {
setValue(item);
finishEditing();
}}
aria-label={`${item} feedback option`}
aria-pressed={value === item}
tabIndex={value === item ? 0 : -1}
>
{item}
</button>
))}

Keyboard navigation:

  • Tab: Navigate to editor (focuses active button)
  • Arrow Left/Right: Cycle through options (via shortcuts)
  • Enter: Select current option and finish editing
  • Escape: Cancel editing
  • Click: Direct selection

ARIA attributes:

  • aria-label: Describes each button
  • aria-pressed: Indicates selected state
  • tabIndex: Controls keyboard focus order

Performance Considerations

Why This Is Fast

  1. React Virtual DOM: Efficient updates only when value changes
  2. No External Libraries: Zero overhead beyond React
  3. Efficient Re-renders: Only re-renders when config or value changes
  4. Native Events: Browser-optimized click handlers

React Hooks Optimization

The useCallback and useEffect hooks ensure shortcuts are only recreated when config changes:

const getNextValue = useCallback((value: string) => {
const index = config.indexOf(value);
return index === config.length - 1 ? config[0] : config[index + 1];
}, [config]); // Only recreate if config changes
useEffect(() => {
setShortcuts([...]);
}, [config, getNextValue, getPrevValue]); // Only update when dependencies change

TypeScript Support

EditorComponent is fully typed. You can specify the value type:

<EditorComponent<string>>
{({ value, setValue, finishEditing }) => {
// TypeScript knows value is string | undefined
// TypeScript knows setValue accepts string
return (
// ... editor UI
);
}}
</EditorComponent>

For number-based feedback:

<EditorComponent<number>>
{({ value, setValue, finishEditing }) => {
// TypeScript knows value is number | undefined
return (
// ... editor UI
);
}}
</EditorComponent>

Best Practices

  1. Use onPrepare for per-cell configuration - Access cellProperties to read custom options
  2. Handle keyboard events properly - Use shortcuts for navigation
  3. Call finishEditing() appropriately - When user confirms changes (Enter, blur, button click)
  4. Keep render prop function simple - Extract complex logic into separate components or hooks
  5. Use useCallback for helper functions - Prevents unnecessary re-renders
  6. Update shortcuts in useEffect - Ensures shortcuts match current config

Congratulations! You’ve created a simple feedback editor with emoji buttons using React’s EditorComponent, perfect for quick feedback selection in your data grid!