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 outside the grid root so it is not clipped by the grid's own overflow near the grid edge — in document.body, or in the grid's own shadow root when the grid is mounted inside one.

The popover is still themed like the grid it belongs to: the theme's inline --pte-* values are mirrored from the grid root onto the popover while it is open, and follow a theme change. actionFramePopoverWidth, actionFramePopoverBackgroundColor, actionFramePopoverRadius, actionFramePopoverShadow and the rest apply either way, as does a dark theme.

const options = {
theme: themeDark.withParams({ actionFramePopoverWidth: "420px" }),
defaultColDef: { actionFrameOptions: { escapeRootClip: true } },
} satisfies GridOptions;

Tooltips under tooltipOptions.escapeRootClip work the same way. See Popups outside the grid root.

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.