Skip to main content

ActionFrames

An ActionFrame keeps a visible frame on one cell and mounts application-owned form content in a persistent popover — for inline workflows like comments, approvals, or quick edits that outlive a hover.

Loading interactive example…
React
function CommentFrame({ value, rowId, colDef, api, close }: ActionFrameComponentParams) {
const [draft, setDraft] = useState(String(value ?? ""));
return (
<form onSubmit={(event) => {
event.preventDefault();
api.setCellValue({ rowId, colId: colDef.colId }, draft);
close();
}}>
<textarea value={draft} onChange={(e) => setDraft(e.target.value)} />
<button type="submit">Save</button>
</form>
);
}

const columns: ReactColDef[] = [{
key: "comment",
label: "Comment",
actionFrameTrigger: "click",
actionFrameComponent: CommentFrame,
actionFrameIndicator: "comment",
actionFrameOptions: { placement: "right", offset: 10 },
}];

Click-triggered comment form

const commentForm = (params: ActionFrameComponentParams) => {
const form = document.createElement("form");
const input = document.createElement("textarea");
const save = document.createElement("button");

input.value = String(params.value ?? "");
save.textContent = "Save";
form.append(input, save);
form.addEventListener("submit", (event) => {
event.preventDefault();
params.api.setCellValue(
{ rowId: params.rowId, colId: params.colDef.colId },
input.value,
);
params.close();
});
return form;
};

const column = {
key: "comment",
label: "Comment",
actionFrameTrigger: "click",
actionFrameComponent: commentForm,
actionFrameIndicator: "comment",
actionFrameOptions: {
placement: "right",
offset: 10,
escapeRootClip: true,
},
} satisfies ColDef;

actionFrameIndicator may also be true or a predicate. Use defaultColDef.actionFrameComponent to share a component across columns. The React and Angular bindings accept framework components in the same actionFrameComponent slot — the live demo above mounts a React form.

Theming and escapeRootClip

escapeRootClip: true mounts the popover in document.body so it is not clipped by the grid's own overflow near the grid edge. The cost is theming: a theme's values are applied as inline --pte-* custom properties on the grid root, and a body-mounted popover is outside that subtree, so the actionFramePopover* theme params do not apply to itactionFramePopoverWidth, actionFramePopoverBackgroundColor, actionFramePopoverRadius, actionFramePopoverShadow, and friends are all ignored, and the popover renders with the stylesheet defaults (300px wide, light surface).

Either keep the popover inside the root:

actionFrameOptions: { placement: "right", offset: 10 }, // escapeRootClip off

or set the variables at document scope, which applies to every grid on the page:

:root {
--pte-action-frame-popover-width: 420px;
}

Tooltips behave the same way under tooltipOptions.escapeRootClip.

Programmatic control

const cell = { rowId: "task-4", colId: "comment" };

api.openActionFrame(cell);
console.log(api.getActionFrameCell());
api.closeActionFrame();

actionFrameTrigger: "none" keeps API and Shift+F2 access while disabling the click trigger. Only one frame can be open; starting an edit closes it. Frames hide while their cell is scrolled out and return when it becomes visible again.