name: reusable-components description: > 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. version: 0.2.0
Structuring Views into Components in Vaadin 25
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".
When to Extract Components
Not every view needs decomposition. Extract when you see these signals:
- View exceeds ~200 lines — hard to navigate and reason about
- Cohesive groups — a cluster of components that form a logical unit (e.g., a filter bar, a detail panel)
- Repeated patterns — the same group of components appears in multiple views
- Isolated state — a section manages its own state independently from the rest of the view
Before and After
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.
The Extraction Workflow
- Identify cohesive groups — look for clusters of fields, buttons, or layouts that serve one purpose.
- Define the boundary — what does the component own (internal state, layout) vs. what does it need from outside (data, configuration)?
- Choose the right base — use
Composite<T>by default. Extend an existing component only when you want its full API. UseAbstractField<C, V>when the component represents an editable value for Binder. See the next section for details. - Extract and wire up — move the component's fields and layout code into its own class. Pass required data through constructors or setters.
- Connect parent and child — use method calls for parent→child communication, custom events for child→parent. See "Wiring Communication" below.
Choosing the Right Base: Composite vs. Extend
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.
Composite (recommended default)
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.
Extend an existing component
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.
Defining the Component's API
Principles
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 internalSpanandAvatar.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 events
Use typed events — define custom
ComponentEventsubclasses instead of generic callbacks. This integrates with Vaadin's event system and supports@DomEventfor client-side events.
Constructor Design
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());
}
}
Wiring Communication Between Components
When a view is split into parent and child components, they need to communicate. Use the right pattern for the direction.
Parent → Child: method calls
The parent holds a reference to the child and calls its public methods directly:
// In the parent view
detailPanel.setOrder(selectedOrder);
filterBar.reset();
Child → Parent: custom events
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;
}
}
}
Wrapper with Slots
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);
}
}
Lifecycle Considerations
- onAttach() — called when the component is added to the UI. Use for initialization that requires the component to be in the DOM (e.g., accessing session data, subscribing to event buses).
- onDetach() — called before removal from the UI. Use for cleanup (e.g., unsubscribing from event buses, releasing resources).
- Always clean up in
onDetach()what you set up inonAttach()to prevent memory leaks.
Best Practices
- Start with the view, extract when needed — don't pre-optimize. Build the view first, then extract when signals appear (see "When to Extract Components").
- Prefer Composite over direct extension — it gives you API control and prevents leaking internals.
- Keep components focused — a component should do one thing well. If it has too many responsibilities, split it.
- Use typed events for child→parent communication —
addXxxListener()returningRegistrationis the Vaadin way. - Don't expose internal components — return data from getters, not the underlying Spans and Divs.
- Test extracted components in isolation — reusable components should be testable with Vaadin's UI unit testing framework without needing a full application context.