Skip to content

Object menu

2 min read · Updated 27 Oct 2026

This page belongs to Data App developer guide. What each item does — the data dialog, the exports and the image capture — is in Data dialogs and exports.

Build a shared object menu early, before objects acquire separate event handlers. Give each object the menu actions it supports, including viewing and exporting data where available.

Use this order and omit unsupported actions.

Item Offered on Notes
Copy cell value A table, when the pointer is over a cell Copy the displayed text.
Show as table Objects whose query your app owns See Show an object’s data.
Export data as CSV Same
Export data as Excel Same Offer Excel alongside CSV.
Export image of this card / chart / table Every object A PNG of that one object as drawn.
Export image of the region Every object inside a named region For example, all the figure cards as one picture.

No query insights item in a Data App. A Workbook’s menu offers the SQL and execution insights for an object. A Data App does not receive them (see What a Data App can reach), so omit this action.

Objects in the header — filters, for example — live outside the board, so a listener scoped to the board gives them no menu. Listen on the document and scope by attribute:

document.addEventListener('contextmenu', (event) => {
if (!(event.target instanceof Element)) return;
const host = event.target.closest<HTMLElement>('[data-object-id]');
if (!host) return;
event.preventDefault();
const spec = specsById.get(host.dataset.objectId!);
const cell = event.target.closest('[role="gridcell"]');
openMenu(event.clientX, event.clientY, buildItems(spec, host, cell));
});

Mark every object’s host with its id, including hand-built figure cards, so one listener serves them all. Tables render role="gridcell" cells, which is how the menu finds the cell under the pointer; see Row structure.

Test event.target instanceof Element rather than relying on optional chaining: ?. guards a nullish target, not a target such as window that has no closest method.

Offer image export for the object itself and, when it belongs to a named group, the whole group. Declare groups in the markup:

<section class="charts" data-group="Charts">…</section>
<div class="kpi-row" data-group="KPI cards">…</div>
<section class="detail" data-group="Detail panel">…</section>

Walk up with host.closest('[data-group]').

Name the scope in each menu label, for example “Export image of this card” and “Export image of KPI cards”.

A modal <dialog>, opened with showModal(), gives you focus handling, Escape to close and a backdrop. A click on the backdrop does not close it by default: add closedby="any" where the browser supports it, or close it yourself when a click lands outside the menu’s box. Position it at the pointer and flip it when it would leave the viewport.

  • Read CSS rules that fail silently before styling it: an unqualified display on a <dialog> leaves closed menus laid out over the page, catching clicks.
  • For an unavailable item with an explanatory tooltip, use aria-disabled="true" so it can remain focusable. The item’s handler must check this state and ignore activation.
  • Close the menu before running the item. An item that opens a dialog must not race the menu’s own close.
  • Style it from the chrome tokens, like the rest of the header’s popovers; see Keep the chrome consistent across themes. A minimum width around 216px matches Astrato’s own menus.