From vaadin-development
Guide Claude on structuring Vaadin 25 Flow views into focused, reusable components. This skill should be used when the user asks to "structure a view", "organize view code", "break down a complex view", "extract a component", "split a view into components", "simplify a large view", "create a reusable component", "use Composite", "compose components", or when a view is growing beyond ~200 lines, has multiple logical sections, or contains repeated UI patterns.
npx claudepluginhub marcushellberg/vaadin-development-plugin --plugin vaadin-developmentThis 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"`.
Implements use cases by creating Vaadin views, forms, grids for UI layer and jOOQ queries for data access in Java web apps.
Provides patterns for Vue components including props validation with TypeScript, defaults, emits, slots, and provide/inject. Use when building reusable Vue components.
Guides planning and implementation of Vue 3 projects with TypeScript, Composition API, defineModel bindings, Testing Library user-behavior tests, and MSW API mocking to enforce modern patterns.
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 — define custom ComponentEvent subclasses instead of generic callbacks. This integrates with Vaadin's event system and supports @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.
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;
}
}
}
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.addXxxListener() returning Registration is the Vaadin way.