Document-style "click-to-edit" UI in Vaadin Flow - how have you handled this?

Hey everyone,

I’m working on a data review screen where an LLM extracts structured fields from a meeting transcript (event name, venue, crew, technical requirements, etc.), and the user needs to review and correct them before saving.

The challenge is that the typical Vaadin form layout feels heavy and form-like for what is essentially a “review and lightly edit” flow. I want something closer to a Google Doc or Notion page - a readable narrative that feels like a document, where clicking a piece of text transitions into edit mode for that field.

What I landed on (for now):

A two-tab layout:

  1. Summary tab - a programmatically-built read-only view using Span, H2, Paragraph, etc., with an LLM-generated blurb at the top. Each piece of data has a hover state (CSS cursor: pointer + subtle underline) to signal interactivity.
  2. Form tab - standard Vaadin fields for detailed editing.

Clicking a field in the Summary tab calls tabSheet.setSelectedTab(formTab) and then programmatically focuses + scrolls to the corresponding form field. So the summary acts as navigation into the form.

The pattern I was inspired by:

Atlassian’s Inline Edit component (from their React design system) is a great reference for the UX feel - a readView that swaps to an editView on click. We’re doing something similar but with a tab transition instead of in-place swapping, partly to keep the Vaadin implementation clean and avoid Shadow DOM headaches with hybrid React/Vaadin setups.

My questions for the community:

  1. Has anyone implemented a true in-place “click-to-edit” pattern natively in Vaadin Flow (i.e., toggling between a Span and a TextField in the same slot)? How did you handle focus, keyboard navigation, and save-on-blur?
  2. Any experience with Vaadin’s InlineDatePicker or similar components that ship with this kind of read/edit toggle behavior?

Stack: Vaadin Flow 25, Spring Boot, Java 26.

Thanks!

Hi Nathan,

This is an interesting question – I don’t think I’ve ever built anything like this, but I have thought about it several times.

There are a bunch of options that have come to my mind, each with their own problems:

A) Render each editable piece of text as an input field, and use css to make the field look like a piece of text until hovered.

  • Plenty of css involved, especially if you need to support multiple field component types.
  • Ensuring that the field is exactly as long as it needs to be is tricky or impossible. (If you don’t need to support Firefox or older Safari versions, you could use field-sizing: content;. Otherwise you can approximate it with setting its width to the number of characters using the ch unit. In both cases you still need to deal with the padding around the text in the field somehow.)
  • This is the only option that’s keyboard-friendly and accessible out of the box, in case that’s important.

B) Render each editable piece of text as a wrapper element containing the non-editable html element and an input field, and toggle between them.

  • The wrapper’s width needs to “shrink-wrap” around the non-editable element, while the field’s width is set to 100% of that. CSS grid can be used to achieve this.
  • By default, the field is hidden with visibility:hidden. Clicking the element switches the visibility.
  • This won’t be keyboard-accessible, but that can be worked around by making the wrapper or the non-editable element focusable and “keyboard-clickable”.
  • Proper accessibility (e.g. for WCAG conformance) requires additional tricks.

C) Like the above, but use a Popover to render an overlay for the editing instead.

There are probably other options too, but these are the ones I’ve thought about :slight_smile:

That being said, depending on details like how many the editable fields are, whether they’re always the same fields, and whether the user needs to see them in-context to know what they should be, I do think it would be fine to render them as a single-column form next to the read-only document – that makes it easy for the user to quickly scan through them and see if they make sense. Better than jumping between to tabs at least.

IMO this is a great question for AI.
I asked Claude and that’s what it says:

Let me give you my take on the inline-edit pattern in Vaadin Flow.

The two-tab approach is pragmatic but has UX friction. Every tab switch is a context switch. The user sees the summary, clicks something, gets teleported to a form, and has to mentally re-orient. For a “review and lightly edit” flow, that is one step too many.

True inline edit in Vaadin Flow is absolutely doable and not as painful as you might think. Here is the core pattern:

public class InlineEditField extends Div {
    
    private final Span readView;
    private final TextField editView;
    private boolean editing = false;

    public InlineEditField(String label, String value, 
                           Consumer<String> onSave) {
        readView = new Span(value);
        readView.addClassName("inline-edit-read");
        readView.getElement().setAttribute("tabindex", "0");
        
        editView = new TextField(label);
        editView.setValue(value);
        editView.setVisible(false);
        editView.setWidthFull();
        
        // Click to edit
        readView.addClickListener(e -> startEditing());
        
        // Enter on read view also starts editing
        readView.getElement().addEventListener("keydown", e -> startEditing())
            .setFilter("event.key === 'Enter'");
        
        // Save on blur
        editView.addBlurListener(e -> save(onSave));
        
        // Enter saves, Escape cancels
        editView.getElement().addEventListener("keydown", e -> save(onSave))
            .setFilter("event.key === 'Enter'");
        editView.getElement().addEventListener("keydown", e -> cancelEditing())
            .setFilter("event.key === 'Escape'");
        
        add(readView, editView);
    }

    private void startEditing() {
        if (editing) return;
        editing = true;
        readView.setVisible(false);
        editView.setVisible(true);
        editView.focus();
    }

    private void save(Consumer<String> onSave) {
        editing = false;
        String newValue = editView.getValue();
        readView.setText(newValue);
        editView.setVisible(false);
        readView.setVisible(true);
        onSave.accept(newValue);
    }

    private void cancelEditing() {
        editing = false;
        editView.setValue(readView.getText());
        editView.setVisible(false);
        readView.setVisible(true);
    }
}

The CSS is minimal:

.inline-edit-read {
    cursor: pointer;
    border-bottom: 1px dashed transparent;
    padding: 2px 4px;
    border-radius: 3px;
    transition: background-color 0.15s;
}

.inline-edit-read:hover,
.inline-edit-read:focus {
    background-color: var(--lumo-contrast-5pct);
    border-bottom-color: var(--lumo-contrast-30pct);
}

Key things to watch out for:

The focus() call after toggling visibility works fine because Vaadin batches the DOM updates in a single round-trip. The setVisible(false) on the read view and setVisible(true) plus focus() on the edit view all arrive at the client in one response. No timing hacks needed.

For save-on-blur vs. explicit save: blur works well for simple text fields. But be careful with fields that open overlays (date pickers, combo boxes), because the overlay itself triggers a blur on the field. The InlineDatePicker you mentioned does not really exist as a built-in toggle component. You would need to build the same read/edit swap around a DatePicker.

For keyboard navigation: the tabindex="0" on the read view is important. It puts the spans into the tab order so keyboard users can navigate between fields and press Enter to edit. Without it, the whole thing is mouse-only.

For the document-like layout, you can compose these inline edit fields into a narrative structure:

var summary = new Div();
summary.addClassName("document-view");

summary.add(
    new H2(new InlineEditField("Event", event.getName(), event::setName)),
    new Paragraph(
        new Text("Taking place at "),
        new InlineEditField("Venue", event.getVenue(), event::setVenue),
        new Text(" on "),
        new InlineEditField("Date", event.getDate().toString(), 
            val -> event.setDate(LocalDate.parse(val))),
        new Text(".")
    )
);

This gives you the Notion-like feel you are looking for, without the tab switching.

One more thing: if you want a “save all” button instead of save-on-blur for each field, collect all the dirty fields and batch the persistence. A Binder does not fit this pattern well because it expects a form layout. A simple list of Runnable save actions works better here.

The two-tab approach is not wrong. It is a safe, maintainable choice. But if the UX goal is “feels like reviewing a document”, the inline edit pattern gets you much closer with surprisingly little code.

That’s a good starting point, but as always, AI answers should be taken with a grain of salt, and will need iterating before it really works as intended.

As an example, the save-and-switch-on-blur mechanism should probably not be implemented like that, and making the input and readonly-text maintain at least approximately same width doesn’t seem to be covered at all.

And that css is certainly not all you need.

Providing Claude with more detailed specs that specify requirements like scaling etc would probably get you closer though.

1 Like