Context
Vaadin 25, Flow. I have a MasterDetailLayout where clicking a row in a Grid opens a detail panel via setDetail(). I want focus to move into the detail content so that:
- Pressing Escape closes the detail (via a
Shortcuts.addShortcutListener(..., Key.ESCAPE).listenOn(wrapper)) - For form-based details, the first text field is ready to type in immediately
What I tried
I added an addAttachListener on the detail component that calls focus() when the component is attached:
For a read-only detail (non-focusable VerticalLayout wrapper):
wrapper.getElement().setAttribute("tabindex", "0");
Shortcuts.addShortcutListener(wrapper, onClose::run, Key.ESCAPE).listenOn(wrapper);
wrapper.addAttachListener(_ -> wrapper.getElement().callJsFunction("focus"));
For form-based details (VerticalLayout subclass with a TextField name as the first field):
Shortcuts.addShortcutListener(this, () -> fireEvent(new CancelEvent(this, false)), Key.ESCAPE)
.listenOn(this);
addAttachListener(_ -> name.focus());
I also tried deferring with executeJs("setTimeout(() => this.focus(), 0)") instead of calling focus() / callJsFunction("focus") directly.
The problem
Focus is not reliably moved to the detail content. I can tell because after clicking a grid row and then pressing the arrow keys, the cursor is still navigating cells in the Grid — focus stayed in the grid.
My hypothesis: the Grid’s native click handler re-focuses the clicked cell after our server-round-trip completes and the addAttachListener fires. The setTimeout(..., 0) approach didn’t help either, suggesting the grid focus restoration happens later than a single tick.
Questions
- Is there a recommended pattern for moving focus out of a
GridintoMasterDetailLayoutdetail content after a row selection opens the detail? - Is there a Grid API to suppress its default focus management when a row is selected (e.g. an “activate row” action that opens the detail)?
- Should I be listening to a different event on the Grid — for example
addItemClickListenerinstead ofasSingleSelect().addValueChangeListener()— and is there any difference in focus behavior? - Is there a
MasterDetailLayoutAPI or event I should use to set focus after the detail is shown?
Minimal reproduction
// Grid selection listener that opens detail
grid.asSingleSelect().addValueChangeListener(event -> {
if (event.getValue() != null) {
detailOpener.openDetail(buildDetail(event.getValue(), detailOpener::closeDetail));
}
});
// Detail builder
static VerticalLayout buildDetail(MyItem item, Runnable onClose) {
// ... build content ...
VerticalLayout wrapper = new VerticalLayout(content);
wrapper.setSizeFull();
wrapper.getElement().setAttribute("tabindex", "0");
Shortcuts.addShortcutListener(wrapper, onClose::run, Key.ESCAPE).listenOn(wrapper);
wrapper.addAttachListener(_ -> wrapper.getElement().callJsFunction("focus"));
return wrapper;
}
Any guidance appreciated. Thanks!