| name | frontend-design |
| description | Guide Claude on creating visually distinctive, polished Vaadin 25 interfaces that go beyond default theme styling. This skill should be used when the user asks to "make it look good", "improve the design", "style the view", "make it visually appealing", "add polish", "design a UI", "create a beautiful interface", or when building a new view where visual quality matters. Also trigger when the user wants to add animations, visual effects, or build polished component compositions in a Vaadin application.
|
| version | 0.1.0 |
Creating Distinctive Vaadin Interfaces
Use the Vaadin MCP tools (search_vaadin_docs, get_component_styling, 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".
This skill guides creation of distinctive, polished Vaadin interfaces that go beyond default theme styling. The goal is production-grade code with genuine attention to aesthetic detail — not generic defaults.
Design Thinking
Before writing code, understand the context and commit to a clear aesthetic direction:
- Purpose: What problem does this interface solve? Who uses it? An admin dashboard has different design needs than a customer-facing portal.
- Tone: Pick a direction: clean and professional, warm and approachable, bold and data-dense, light and airy, dark and focused, editorial, playful, or refined. The direction should serve the users and the content.
- Constraints: Vaadin component library, Vaadin theme system (Aura or Lumo), server-side rendering model, accessibility requirements.
- Differentiation: What makes this interface feel crafted rather than default? What detail will users notice?
CRITICAL: Choose a clear direction and execute it consistently. Every color, spacing, and typography choice should reinforce the same aesthetic. A cohesive simple design always beats an inconsistent elaborate one.
Then implement working code (Java + CSS) that is:
- Production-grade and functional
- Visually cohesive with a clear aesthetic point of view
- Built on Vaadin's component library and theme system
- Refined in spacing, color, typography, and visual hierarchy
The Vaadin Theme System
All visual customization starts with your chosen theme — Aura or Lumo. Both provide:
- CSS custom properties — the foundation for colors, typography, spacing, sizing, borders, shadows. Override these to change the entire application's look.
- Component theme variants — pre-built visual variations (e.g., primary, tertiary, compact). Use these before writing custom CSS.
- Custom CSS — for anything the theme doesn't cover. Use
@StyleSheet with view-scoped CSS files.
Lumo additionally provides:
- Utility classes — Tailwind-like classes (
LumoUtility.*) for layout, spacing, colors. Fast to apply from Java. Not available with Aura.
Always work top-down: change a theme property before writing component-specific CSS, and use a theme variant before writing custom styles.
For full details on theme selection, loading, and all design tokens, see the theming skill.
Typography
Typography sets the tone of the entire interface.
Customizing the font family (works with both themes):
@font-face {
font-family: 'Your Font';
src: url('./fonts/your-font.woff2') format('woff2');
font-display: swap;
}
html {
--aura-font-family: 'Your Font', sans-serif;
--lumo-font-family: 'Your Font', sans-serif;
}
Tuning the type scale:
Aura — adjust one value and the entire scale is computed:
html {
--aura-base-font-size: 15;
}
Lumo — override individual tokens:
html {
--lumo-font-size-xxxl: 2.5rem;
--lumo-font-size-xxl: 1.75rem;
--lumo-font-size-xl: 1.375rem;
--lumo-font-size-l: 1.125rem;
--lumo-font-size-m: 1rem;
--lumo-font-size-s: 0.875rem;
--lumo-font-size-xs: 0.8125rem;
--lumo-font-size-xxs: 0.75rem;
}
Creating typographic hierarchy from Java (Lumo utility classes):
H2 title = new H2("Dashboard");
title.addClassNames(
LumoUtility.FontSize.XXLARGE,
LumoUtility.FontWeight.BOLD,
LumoUtility.TextColor.HEADER
);
Span subtitle = new Span("Weekly performance overview");
subtitle.addClassNames(
LumoUtility.FontSize.MEDIUM,
LumoUtility.TextColor.SECONDARY
);
For Aura, use CSS classes or inline styles instead of LumoUtility.
Key principle: Establish a clear hierarchy — one dominant heading style, one body style, one secondary/caption style. Use font size, weight, and color together to differentiate levels. Avoid using more than 3-4 distinct text styles in a single view.
Color and Theming
Color is the fastest way to give a Vaadin app a distinctive identity.
Aura — customizing the accent and palette:
html {
--aura-accent-color-light: hsl(220, 80%, 50%);
--aura-accent-color-dark: hsl(220, 85%, 65%);
--aura-background-color-light: hsl(220, 20%, 98%);
--aura-background-color-dark: hsl(220, 20%, 12%);
--aura-blue: hsl(220, 80%, 50%);
--aura-green: hsl(150, 60%, 40%);
--aura-red: hsl(0, 75%, 55%);
}
Lumo — overriding the color palette:
html {
--lumo-primary-color: hsl(220, 80%, 50%);
--lumo-primary-color-50pct: hsla(220, 80%, 50%, 0.5);
--lumo-primary-color-10pct: hsla(220, 80%, 50%, 0.1);
--lumo-primary-text-color: hsl(220, 80%, 45%);
--lumo-primary-contrast-color: #fff;
--lumo-base-color: hsl(220, 20%, 98%);
--lumo-error-color: hsl(0, 75%, 55%);
--lumo-success-color: hsl(150, 60%, 40%);
--lumo-warning-color: hsl(40, 95%, 50%);
}
Dark mode:
Both themes support the @ColorScheme annotation. For custom dark mode colors, see the theming skill for theme-specific selectors ([theme~="dark"] for Lumo, -light/-dark suffixed properties for Aura).
Accent and semantic colors from Java (Lumo utility classes):
badge.addClassNames(
LumoUtility.Background.PRIMARY,
LumoUtility.TextColor.PRIMARY_CONTRAST
);
warningCard.addClassNames(
LumoUtility.Background.WARNING_10PCT,
LumoUtility.TextColor.WARNING
);
Key principle: Commit to a cohesive palette. Pick one strong primary/accent color and use the theme's variant system (Lumo's opacity variants, Aura's computed variants) for secondary uses. A dominant primary with restrained accent colors outperforms an evenly distributed rainbow.
Spacing and Density
Consistent spacing creates visual rhythm and professionalism.
Aura — adjust the base size:
html {
--aura-base-size: 18;
--aura-base-radius: 8;
}
Lumo — override individual spacing tokens:
html {
--lumo-space-xs: 0.25rem;
--lumo-space-s: 0.5rem;
--lumo-space-m: 1rem;
--lumo-space-l: 1.5rem;
--lumo-space-xl: 2.5rem;
}
Applying spacing from Java (Lumo utility classes):
card.addClassNames(
LumoUtility.Padding.LARGE,
LumoUtility.Gap.MEDIUM
);
section.addClassNames(
LumoUtility.Padding.Horizontal.LARGE,
LumoUtility.Padding.Vertical.XLARGE
);
Compact/dense variants:
grid.addThemeVariants(GridVariant.NO_BORDER);
textField.addThemeVariants(TextFieldVariant.SMALL);
button.addThemeVariants(ButtonVariant.SMALL);
grid.addThemeVariants(GridVariant.LUMO_COMPACT);
Key principle: Pick a density and stick with it. Data-dense dashboards should be consistently compact. Spacious marketing-style views should be consistently airy. Mixing densities looks unintentional.
Shadows, Borders, and Elevation
Create depth and visual hierarchy through elevation.
Aura — surface level system:
.elevated-card {
background: var(--aura-surface-color);
--aura-surface-level: 2;
}
.sunken-area {
background: var(--aura-surface-color);
--aura-surface-level: -1;
}
Lumo — explicit shadow tokens:
html {
--lumo-box-shadow-xs: 0 1px 2px 0 rgba(0,0,0,0.05);
--lumo-box-shadow-s: 0 2px 4px -1px rgba(0,0,0,0.1);
--lumo-box-shadow-m: 0 4px 8px -2px rgba(0,0,0,0.1);
--lumo-box-shadow-l: 0 8px 16px -4px rgba(0,0,0,0.15);
--lumo-box-shadow-xl: 0 16px 32px -8px rgba(0,0,0,0.2);
}
card.addClassNames(
LumoUtility.BoxShadow.SMALL,
LumoUtility.BorderRadius.MEDIUM
);
Border radius customization:
html {
--lumo-border-radius-s: 4px;
--lumo-border-radius-m: 8px;
--lumo-border-radius-l: 16px;
}
html {
--aura-base-radius: 8;
}
Subtle borders for separation (Lumo utility classes):
section.addClassNames(
LumoUtility.Border.BOTTOM,
LumoUtility.BorderColor.CONTRAST_10
);
Key principle: Use elevation consistently to communicate hierarchy. Cards float above the surface, dialogs float above cards. Don't put heavy shadows on flat elements or flat shadows on floating elements.
Motion and Animation
Vaadin's server-side model means animations should be CSS-driven. Keep them subtle and purposeful.
View transitions with CSS:
.fade-in {
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.stagger-item {
animation: fadeIn 0.3s ease-out backwards;
}
.stagger-item:nth-child(1) { animation-delay: 0.05s; }
.stagger-item:nth-child(2) { animation-delay: 0.1s; }
.stagger-item:nth-child(3) { animation-delay: 0.15s; }
.stagger-item:nth-child(4) { animation-delay: 0.2s; }
.stagger-item:nth-child(5) { animation-delay: 0.25s; }
content.addClassName("fade-in");
for (Component card : cards) {
card.addClassName("stagger-item");
}
Hover and interaction effects:
.interactive-card {
transition: box-shadow 0.2s ease, transform 0.2s ease;
}
.interactive-card:hover {
box-shadow: 0 4px 8px -2px rgba(0,0,0,0.1);
transform: translateY(-2px);
}
Key principle: One or two well-crafted animations (like a staggered page-load reveal) create more impact than scattered micro-interactions everywhere. Prefer CSS transitions on hover/focus over complex keyframe animations. Keep durations under 400ms.
Building Polished Components
Styled cards (Lumo utility classes)
public static Div createCard(String title, String value, String description) {
Div card = new Div();
card.addClassNames(
LumoUtility.Background.BASE,
LumoUtility.BorderRadius.MEDIUM,
LumoUtility.BoxShadow.SMALL,
LumoUtility.Padding.LARGE,
LumoUtility.Display.FLEX,
LumoUtility.FlexDirection.COLUMN,
LumoUtility.Gap.SMALL
);
Span titleSpan = new Span(title);
titleSpan.addClassNames(
LumoUtility.FontSize.SMALL,
LumoUtility.TextColor.SECONDARY,
LumoUtility.FontWeight.MEDIUM
);
Span valueSpan = new Span(value);
valueSpan.addClassNames(
LumoUtility.FontSize.XXLARGE,
LumoUtility.FontWeight.BOLD,
LumoUtility.TextColor.HEADER
);
Span descSpan = new Span(description);
descSpan.addClassNames(
LumoUtility.FontSize.SMALL,
LumoUtility.TextColor.TERTIARY
);
card.add(titleSpan, valueSpan, descSpan);
return card;
}
For Aura, use CSS classes with Aura's surface system instead of LumoUtility:
.metric-card {
background: var(--aura-surface-color);
--aura-surface-level: 2;
border-radius: var(--vaadin-border-radius-m);
padding: var(--vaadin-padding);
display: flex;
flex-direction: column;
gap: 0.5rem;
}
Status badges
Badge is a preview feature in Vaadin 25.1 — enable it with the badgeComponent feature flag. Available variants: SUCCESS, WARNING, ERROR, FILLED, ICON_ONLY, NUMBER_ONLY.
Badge pending = new Badge("Pending");
Badge confirmed = new Badge("Confirmed");
confirmed.addThemeVariants(BadgeVariant.SUCCESS);
Badge warning = new Badge("Warning");
warning.addThemeVariants(BadgeVariant.WARNING);
Badge denied = new Badge("Denied");
denied.addThemeVariants(BadgeVariant.ERROR);
Badge iconBadge = new Badge("Confirmed", VaadinIcon.CHECK.create());
iconBadge.addThemeVariants(BadgeVariant.SUCCESS);
Badge counter = new Badge("Inbox", 12);
counter.addThemeVariants(BadgeVariant.FILLED);
For Vaadin versions before 25.1, badges can be created with Span elements using the theme="badge" attribute, but this approach is deprecated.
Data-dense dashboard layout (Lumo utility classes)
VerticalLayout dashboard = new VerticalLayout();
dashboard.setPadding(true);
dashboard.setSpacing(false);
dashboard.addClassNames(
LumoUtility.Gap.LARGE,
LumoUtility.Background.CONTRAST_5
);
Div metrics = new Div(
createCard("Revenue", "$48,200", "+12% from last month"),
createCard("Users", "1,420", "+5% from last month"),
createCard("Orders", "384", "+8% from last month")
);
metrics.addClassName("metrics-grid");
dashboard.add(createSectionHeader("Overview"), metrics);
Styling Vaadin Components with CSS
When theme properties and theme variants aren't enough, use CSS.
Shadow DOM and ::part() selectors — Vaadin components use shadow DOM. Style internal parts with ::part():
vaadin-grid::part(header-cell) {
background-color: var(--lumo-contrast-5pct);
font-weight: 600;
}
vaadin-dialog-overlay::part(overlay) {
border-radius: 16px;
}
vaadin-text-field::part(input-field) {
border-radius: 8px;
}
Component-scoped styles — apply CSS to a specific view:
@StyleSheet("styles/views/dashboard-view.css")
@Route("dashboard")
public class DashboardView extends VerticalLayout {
}
Key principle: Always prefer theme custom properties over hardcoded values in CSS. Use your active theme's tokens for colors, spacing, and sizing. This keeps styles consistent with the theme and makes dark mode, density changes, and future redesigns trivial.
Common Anti-Patterns
- Hardcoded colors and sizes — always use your theme's custom properties. Hardcoded values break dark mode and make theming impossible.
- Overriding component internals instead of theme properties — if a color looks wrong, override the theme color token, not the individual component's CSS. One property change should update the entire app.
- Too many visual styles — pick 2-3 card styles, 2-3 text hierarchies, one primary action style. Consistency creates professionalism.
- Ignoring theme variants — check
ButtonVariant, GridVariant, TextFieldVariant, etc. before writing custom CSS. The variant you need probably exists.
- Heavy animations on server-rendered updates — Vaadin rerenders from the server. Complex entrance animations on every server push look janky. Use animations for initial page loads and user-initiated transitions, not for every data update.
- Flat visual hierarchy — if everything looks the same, nothing stands out. Use size, weight, color, and elevation to guide the eye to what matters.
Best Practices
- Start with theme properties — customize your theme's core properties first. This alone can transform the look.
- Use component theme variants — primary, tertiary, compact, badge themes. Use what's built in.
- Maintain a consistent color strategy — one primary/accent, one or two additional colors, semantic colors for status.
- Design with elevation — cards, dialogs, and menus should feel layered. Use your theme's elevation system consistently.
- Add motion sparingly — CSS transitions on hover, a page-load fade-in, staggered card reveals. Keep it under 400ms.
- Test in dark mode — if you customize any colors, verify both light and dark themes. Using theme properties makes this automatic.
Detailed Reference
For component ::part() selectors, CSS animation recipes, and color palette recipes, see references/design-patterns.md. For complete theme token tables and variant comparisons, see the theming skill's references/theming-patterns.md.