From vaadin-claude
Structures Vaadin 25 Flow views by extracting cohesive groups into reusable Composite components. Use when views exceed ~200 lines or have repeated UI patterns.
npx claudepluginhub vaadin/claude-plugin --plugin vaadin-claudeThis skill uses the workspace's default tool permissions.
Use the Vaadin MCP tools (`search_vaadin_docs`, `get_component_java_api`) to look up the latest documentation whenever uncertain about a specific API detail. Always set `vaadin_version` to `"25"` and `ui_language` to `"java"`.
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
Use the Vaadin MCP tools (search_vaadin_docs, get_component_java_api) to look up the latest documentation whenever uncertain about a specific API detail. Always set vaadin_version to "25" and ui_language to "java".
Not every view needs decomposition. Extract when you see these signals:
A monolithic OrderView with filters, a grid, and a detail panel all in one class:
// BEFORE: everything in one 300-line view
public class OrderView extends Composite<VerticalLayout> {
// 15 filter fields, grid setup, detail panel, all interleaved...
}
// AFTER: decomposed into focused components
public class OrderView extends Composite<VerticalLayout> {
private final OrderFilterBar filterBar = new OrderFilterBar();
private final Grid<Order> grid = new Grid<>(Order.class);
private final OrderDetailPanel detailPanel = new OrderDetailPanel();
public OrderView(OrderService service) {
filterBar.addFilterChangeListener(e -> refreshGrid(service, e.getFilter()));
grid.asSingleSelect().addValueChangeListener(e -> detailPanel.setOrder(e.getValue()));
getContent().add(filterBar, grid, detailPanel);
}
}
Each extracted component owns its own layout and state; the view wires them together.
Composite<T> by default. Extend an existing component only when you want its full API. Use AbstractField<C, V> when the component represents an editable value for Binder. See the next section for details.When extracting a view section into its own component, you need to choose a base class. Lead with Composite<T> — it's the right choice in most cases.
Use when you want to hide the root component's API and expose only what you explicitly define. Composite<T> wraps a root component and makes getContent() protected, so users can only interact through your public methods.
public class UserCard extends Composite<HorizontalLayout> {
private final Avatar avatar = new Avatar();
private final Span name = new Span();
private final Span role = new Span();
public UserCard() {
VerticalLayout info = new VerticalLayout(name, role);
info.setSpacing(false);
info.setPadding(false);
getContent().setAlignItems(FlexComponent.Alignment.CENTER);
getContent().add(avatar, info);
}
public void setUser(String userName, String userRole, String imageUrl) {
name.setText(userName);
role.setText(userRole);
avatar.setImage(imageUrl);
avatar.setName(userName);
}
}
Good for: compound components, encapsulated UI blocks, extracted view sections.
Use when you want to add to an existing component's API. The parent class's full public API remains accessible.
public class PrimaryButton extends Button {
public PrimaryButton(String text) {
super(text);
addThemeVariants(ButtonVariant.LUMO_PRIMARY); // LUMO_PRIMARY for Lumo, AURA_PRIMARY for Aura
}
}
Good for: pre-configured variants, adding convenience methods, specializing behavior.
Risk: every public method on the parent is part of your API. Users can call anything on Button, which may break your component's invariants.
Prefer Composite for new components. It produces cleaner APIs and prevents accidental misuse. See the reference file for the full decision matrix.
Expose intent, not implementation — public methods should describe what the component does, not how it's built. setUser(name, role) is better than exposing the internal Span and Avatar.
Make invalid states unrepresentable — if your component requires both a title and an icon, take them as constructor parameters rather than offering separate setters that can be called in any order.
Follow Vaadin conventions — users expect familiar patterns:
setValue() / getValue() for components with a valuesetLabel() for field labelssetEnabled() / setReadOnly() for interaction statesaddXxxListener() for eventsUse typed events for reusable components — define custom ComponentEvent subclasses for components intended for reuse. For one-off components, lightweight callbacks (Runnable, Consumer<T>) are appropriate. Typed events integrate with Vaadin's event system and support @DomEvent for client-side events.
Provide a no-arg constructor for compatibility with Vaadin's declarative systems, then offer convenience constructors for common usage:
public class StatusBadge extends Composite<Span> {
public StatusBadge() {
// default state
}
public StatusBadge(Status status) {
setStatus(status);
}
public void setStatus(Status status) {
getContent().setText(status.getLabel());
getContent().getElement().getThemeList().clear();
getContent().getElement().getThemeList().add("badge " + status.getTheme());
}
}
When a view is split into parent and child components, they need to communicate. Use the right pattern for the direction.
Choosing a pattern:
You can mix patterns in the same view.
The parent holds a reference to the child and calls its public methods directly:
// In the parent view
detailPanel.setOrder(selectedOrder);
filterBar.reset();
Children should not know about their parent. Fire a typed event and let the parent listen:
public class OrderFilterBar extends Composite<HorizontalLayout> {
public Registration addFilterChangeListener(
ComponentEventListener<FilterChangeEvent> listener) {
return addListener(FilterChangeEvent.class, listener);
}
private void onFilterChanged() {
fireEvent(new FilterChangeEvent(this, false, buildFilter()));
}
public static class FilterChangeEvent extends ComponentEvent<OrderFilterBar> {
private final OrderFilter filter;
public FilterChangeEvent(OrderFilterBar source, boolean fromClient,
OrderFilter filter) {
super(source, fromClient);
this.filter = filter;
}
public OrderFilter getFilter() {
return filter;
}
}
}
When multiple child components depend on the same piece of state, signals reduce boilerplate. Instead of calling setters on each child, define a ValueSignal and let children bind to it:
public class EmployeeDetail extends Composite<VerticalLayout> {
private final Span nameLabel = new Span();
private final Span emailLabel = new Span();
public EmployeeDetail(ValueSignal<Employee> employee) {
nameLabel.bindText(employee.map(Employee::getName));
emailLabel.bindText(employee.map(Employee::getEmail));
getContent().add(nameLabel, emailLabel);
}
}
Use setters when updates are triggered by specific events and the flow is easy to follow. Use signals when multiple components share the same state and you want to avoid manual coordination. See the signals skill for details.
For one-off components where defining a full event class is overkill, accept a Runnable or Consumer<T>:
public class DetailFooter extends Composite<Div> {
public DetailFooter(Runnable onEdit, Runnable onDelete) {
Button editButton = new Button("Edit", e -> onEdit.run());
Button deleteButton = new Button("Delete", e -> onDelete.run());
getContent().add(editButton, deleteButton);
}
}
Use events for reusable components (type-safe, Registration-based). Use callbacks for one-off components where simplicity matters.
A common pattern for layout-style components with named areas:
public class PageHeader extends Composite<HorizontalLayout> {
private final Div titleSlot = new Div();
private final Div actionsSlot = new Div();
public PageHeader() {
getContent().setWidthFull();
getContent().setAlignItems(FlexComponent.Alignment.CENTER);
getContent().setJustifyContentMode(FlexComponent.JustifyContentMode.BETWEEN);
getContent().add(titleSlot, actionsSlot);
}
public void setTitle(String title) {
titleSlot.removeAll();
titleSlot.add(new H2(title));
}
public void setActions(Component... actions) {
actionsSlot.removeAll();
HorizontalLayout actionBar = new HorizontalLayout(actions);
actionsSlot.add(actionBar);
}
}
onDetach() what you set up in onAttach() to prevent memory leaks.Vaadin provides mixin interfaces that add standard functionality to your component. Implement only what your component needs:
HasSize — setWidth(), setHeight(), and related sizing methodsHasComponents — add(), remove(), and child component managementHasText — setText() and getText()HasEnabled — setEnabled() for enabling and disablingHasTheme — theme variant supportpublic class StatusBadge extends Composite<HorizontalLayout>
implements HasSize {
// Now callers can use setWidth(), setHeight(), etc.
}
addXxxListener() returning Registration is the Vaadin way.