frontend-design

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Creating Distinctive Vaadin Interfaces

创建特色Vaadin界面

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.
如果不确定特定API的细节,可使用Vaadin MCP工具(
search_vaadin_docs
get_component_styling
get_component_java_api
)查询最新文档。始终将
vaadin_version
设为
"25"
ui_language
设为
"java"
本技能指导开发者创建超出默认主题样式的独特、精致Vaadin界面,目标是产出真正兼顾美学细节的生产级代码,而非通用默认效果。

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
编写代码前,先理解业务上下文,确定清晰的美学方向:
  • 用途:这个界面要解决什么问题?面向哪些用户?管理后台的设计需求和面向客户的门户页面完全不同。
  • 风格:选定方向:简洁专业、温暖亲民、 bold 高数据密度、轻盈通透、深色专注、编辑风、活泼风、精致风。方向选择要服务于用户和内容本身。
  • 限制条件:Vaadin组件库、Vaadin主题系统(Aura或Lumo)、服务端渲染模式、无障碍要求。
  • 差异化:什么设计能让这个界面看起来是精心打造而非默认生成?用户会注意到什么细节?
重要提示:选定清晰的方向后要统一执行,每个颜色、间距、排版选择都要服务于同一个美学风格。统一的简约设计永远好过杂乱的复杂设计。
随后编写可运行的代码(Java + CSS),满足以下要求:
  • 生产级可用、功能完整
  • 视觉统一,具备清晰的美学风格
  • 基于Vaadin组件库和主题系统构建
  • 在间距、颜色、排版和视觉层级上做精细化打磨

The Vaadin Theme System

Vaadin主题系统

All visual customization starts with your chosen theme — Aura or Lumo. Both provide:
  1. CSS custom properties — the foundation for colors, typography, spacing, sizing, borders, shadows. Override these to change the entire application's look.
  2. Component theme variants — pre-built visual variations (e.g., primary, tertiary, compact). Use these before writing custom CSS.
  3. Custom CSS — for anything the theme doesn't cover. Use
    @StyleSheet
    with view-scoped CSS files.
Lumo additionally provides:
  1. 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.
所有视觉自定义都从你选定的主题开始——AuraLumo。两者都提供以下能力:
  1. CSS自定义属性——颜色、排版、间距、尺寸、边框、阴影的基础配置,覆盖这些属性即可修改整个应用的外观。
  2. 组件主题变体——预置的视觉样式变体(比如primary、tertiary、compact),编写自定义CSS前优先使用这些变体。
  3. 自定义CSS——用于主题覆盖不到的场景,配合
    @StyleSheet
    使用视图级CSS文件。
Lumo额外提供:
  1. 工具类——类似Tailwind的类(
    LumoUtility.*
    ),可用于布局、间距、颜色设置,在Java中可以快速调用。Aura不支持该能力。
始终遵循自上而下的定制顺序:先修改主题属性,再编写组件专属CSS;先使用主题变体,再编写自定义样式。
关于主题选择、加载和所有设计token的完整说明,可参考主题定制技能。

Typography

排版

Typography sets the tone of the entire interface.
Customizing the font family (works with both themes):
css
/* In styles.css */
@font-face {
    font-family: 'Your Font';
    src: url('./fonts/your-font.woff2') format('woff2');
    font-display: swap;
}

html {
    /* Aura */
    --aura-font-family: 'Your Font', sans-serif;
    /* Lumo */
    --lumo-font-family: 'Your Font', sans-serif;
}
Tuning the type scale:
Aura — adjust one value and the entire scale is computed:
css
html {
    --aura-base-font-size: 15;  /* unitless number, represents px */
}
Lumo — override individual tokens:
css
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):
java
// Lumo theme only — requires Lumo.UTILITY_STYLESHEET
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.
排版决定了整个界面的风格基调。
自定义字体族(两个主题都支持):
css
/* 在styles.css中 */
@font-face {
    font-family: 'Your Font';
    src: url('./fonts/your-font.woff2') format('woff2');
    font-display: swap;
}

html {
    /* Aura */
    --aura-font-family: 'Your Font', sans-serif;
    /* Lumo */
    --lumo-font-family: 'Your Font', sans-serif;
}
调整字体缩放比例:
Aura——修改一个值即可自动计算整套字体比例:
css
html {
    --aura-base-font-size: 15;  /* 无单位数字,单位为px */
}
Lumo——单独覆盖各个token:
css
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;
}
在Java中创建排版层级(Lumo工具类):
java
// 仅Lumo主题支持——需要引入Lumo.UTILITY_STYLESHEET
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
);
如果使用Aura,请用CSS类或行内样式替代
LumoUtility
核心原则:建立清晰的层级——一套主标题样式、一套正文样式、一套辅助/说明文字样式。组合使用字体大小、字重和颜色区分层级,单个视图中不要使用超过3-4种不同的文本样式。

Color and Theming

颜色与主题

Color is the fastest way to give a Vaadin app a distinctive identity.
Aura — customizing the accent and palette:
css
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:
css
html {
    /* Primary color — used for buttons, links, focus rings */
    --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;

    /* Surface colors — backgrounds, cards, dialogs */
    --lumo-base-color: hsl(220, 20%, 98%);

    /* Error, success, warning */
    --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):
java
// Lumo theme only
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.
颜色是最快让Vaadin应用具备独特辨识度的方式。
Aura——自定义主色和调色板:
css
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——覆盖颜色调色板:
css
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%);
}
深色模式:
两个主题都支持
@ColorScheme
注解。如果要自定义深色模式颜色,可参考主题定制技能中的主题专属选择器说明(Lumo使用
[theme~="dark"]
,Aura使用带
-light
/
-dark
后缀的属性)。
在Java中使用主色和语义色(Lumo工具类):
java
// 仅Lumo主题支持
badge.addClassNames(
    LumoUtility.Background.PRIMARY,
    LumoUtility.TextColor.PRIMARY_CONTRAST
);

warningCard.addClassNames(
    LumoUtility.Background.WARNING_10PCT,
    LumoUtility.TextColor.WARNING
);
核心原则:使用统一的调色板,选定一个高辨识度的主色/强调色,次要场景使用主题的变体系统(Lumo的透明度变体、Aura的自动计算变体)。主色突出、辅助色克制的效果远好过颜色杂乱的彩虹设计。

Spacing and Density

间距与密度

Consistent spacing creates visual rhythm and professionalism.
Aura — adjust the base size:
css
html {
    --aura-base-size: 18;     /* unitless, range 12–24 */
    --aura-base-radius: 8;    /* unitless, range 0–10 */
}
Lumo — override individual spacing tokens:
css
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):
java
// Lumo theme only
card.addClassNames(
    LumoUtility.Padding.LARGE,
    LumoUtility.Gap.MEDIUM
);

section.addClassNames(
    LumoUtility.Padding.Horizontal.LARGE,
    LumoUtility.Padding.Vertical.XLARGE
);
Compact/dense variants:
java
// Lumo theme — compact variants
grid.addThemeVariants(GridVariant.LUMO_COMPACT, GridVariant.LUMO_NO_BORDER);
textField.addThemeVariants(TextFieldVariant.LUMO_SMALL);
button.addThemeVariants(ButtonVariant.LUMO_SMALL);

// Aura theme — compact variants
grid.addThemeVariants(GridVariant.AURA_COMPACT, GridVariant.AURA_NO_BORDER);
// Note: Aura does not have SMALL variants for TextField/Button
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.
统一的间距能营造视觉韵律感和专业感。
Aura——调整基础尺寸:
css
html {
    --aura-base-size: 18;     /* 无单位,取值范围12–24 */
    --aura-base-radius: 8;    /* 无单位,取值范围0–10 */
}
Lumo——覆盖单独的间距token:
css
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;
}
在Java中设置间距(Lumo工具类):
java
// 仅Lumo主题支持
card.addClassNames(
    LumoUtility.Padding.LARGE,
    LumoUtility.Gap.MEDIUM
);

section.addClassNames(
    LumoUtility.Padding.Horizontal.LARGE,
    LumoUtility.Padding.Vertical.XLARGE
);
紧凑/高密度变体:
java
// Lumo主题——紧凑变体
grid.addThemeVariants(GridVariant.LUMO_COMPACT, GridVariant.LUMO_NO_BORDER);
textField.addThemeVariants(TextFieldVariant.LUMO_SMALL);
button.addThemeVariants(ButtonVariant.LUMO_SMALL);

// Aura主题——紧凑变体
grid.addThemeVariants(GridVariant.AURA_COMPACT, GridVariant.AURA_NO_BORDER);
// 注意:Aura没有针对TextField/Button的SMALL变体
核心原则:选定密度后保持统一,高数据密度的后台要全程使用紧凑模式,宽松的营销类页面要全程使用通透间距,混合密度会看起来很不专业。

Shadows, Borders, and Elevation

阴影、边框与层级

Create depth and visual hierarchy through elevation.
Aura — surface level system:
css
.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:
css
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);
}
java
// Lumo theme only
card.addClassNames(
    LumoUtility.BoxShadow.SMALL,
    LumoUtility.BorderRadius.MEDIUM
);
Border radius customization:
css
/* Lumo */
html {
    --lumo-border-radius-s: 4px;
    --lumo-border-radius-m: 8px;
    --lumo-border-radius-l: 16px;
}

/* Aura — adjust the base radius instead */
html {
    --aura-base-radius: 8;  /* computes all radius values */
}
Subtle borders for separation (Lumo utility classes):
java
// Lumo theme only
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.
通过层级设计营造深度感和视觉优先级。
Aura——表层等级系统:
css
.elevated-card {
    background: var(--aura-surface-color);
    --aura-surface-level: 2;
}

.sunken-area {
    background: var(--aura-surface-color);
    --aura-surface-level: -1;
}
Lumo——显式阴影token:
css
html {
    --lumo-box-shadow-xs: 0 1px 2px 0 rgba(0,0,0,0.05);
    --lumo-box-shadow-s: 0 2px 4px -1 rgba(0,0,0,0.1);
    --lumo-box-shadow-m: 0 4px 8px -2 rgba(0,0,0,0.1);
    --lumo-box-shadow-l: 0 8px 16px -4 rgba(0,0,0,0.15);
    --lumo-box-shadow-xl: 0 16px 32px -8 rgba(0,0,0,0.2);
}
java
// 仅Lumo主题支持
card.addClassNames(
    LumoUtility.BoxShadow.SMALL,
    LumoUtility.BorderRadius.MEDIUM
);
圆角自定义:
css
/* Lumo */
html {
    --lumo-border-radius-s: 4px;
    --lumo-border-radius-m: 8px;
    --lumo-border-radius-l: 16px;
}

/* Aura——直接调整基础圆角即可 */
html {
    --aura-base-radius: 8;  /* 自动计算所有圆角值 */
}
用细边框做内容分隔(Lumo工具类):
java
// 仅Lumo主题支持
section.addClassNames(
    LumoUtility.Border.BOTTOM,
    LumoUtility.BorderColor.CONTRAST_10
);
核心原则:统一使用层级传达优先级,卡片悬浮在背景之上,弹窗悬浮在卡片之上。不要给扁平元素加重阴影,也不要给悬浮元素加扁平阴影。

Motion and Animation

动效与动画

Vaadin's server-side model means animations should be CSS-driven. Keep them subtle and purposeful.
View transitions with CSS:
css
/* Fade-in for view content */
.fade-in {
    animation: fadeIn 0.3s ease-out;
}

@keyframes fadeIn {
    from {
        opacity: 0;
        transform: translateY(8px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}

/* Staggered reveal for list items */
.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; }
java
content.addClassName("fade-in");

// Staggered card reveal
for (Component card : cards) {
    card.addClassName("stagger-item");
}
Hover and interaction effects:
css
.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);  /* or use theme token */
    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.
Vaadin的服务端渲染模式意味着动画应该由CSS驱动,保持微妙、有用的原则即可。
用CSS做视图过渡:
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; }
java
content.addClassName("fade-in");

// 卡片错落展示
for (Component card : cards) {
    card.addClassName("stagger-item");
}
悬停与交互效果:
css
.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);  /* 也可以使用主题token */
    transform: translateY(-2px);
}
核心原则:一两个精心设计的动画(比如页面加载错落展示)比随处乱放的微交互效果更好,优先使用hover/focus的CSS过渡,少用复杂的关键帧动画,动画时长控制在400ms以内。

Building Polished Components

构建精致组件

Styled cards (Lumo utility classes)

带样式的卡片(Lumo工具类)

java
// Lumo theme only — uses LumoUtility 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
:
css
.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;
}
java
// 仅Lumo主题支持——使用LumoUtility类
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;
}
如果使用Aura,用搭配Aura表层系统的CSS类替代
LumoUtility
css
.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

状态徽章

java
public static Span createBadge(String text, BadgeVariant variant) {
    Span badge = new Span(text);
    badge.getElement().getThemeList().add("badge " + variant.theme);
    return badge;
}

public enum BadgeVariant {
    DEFAULT(""),
    SUCCESS("success"),
    ERROR("error"),
    WARNING("warning"),
    CONTRAST("contrast"),
    PRIMARY("primary"),
    PILL("pill"),
    SMALL("small");

    final String theme;
    BadgeVariant(String theme) { this.theme = theme; }
}
Combine variants:
"badge success small pill"
for a small success pill badge. Badges work with both Aura and Lumo.
java
public static Span createBadge(String text, BadgeVariant variant) {
    Span badge = new Span(text);
    badge.getElement().getThemeList().add("badge " + variant.theme);
    return badge;
}

public enum BadgeVariant {
    DEFAULT(""),
    SUCCESS("success"),
    ERROR("error"),
    WARNING("warning"),
    CONTRAST("contrast"),
    PRIMARY("primary"),
    PILL("pill"),
    SMALL("small");

    final String theme;
    BadgeVariant(String theme) { this.theme = theme; }
}
可以组合变体:用
"badge success small pill"
创建小型成功状态胶囊徽章,徽章在Aura和Lumo中都可用。

Data-dense dashboard layout (Lumo utility classes)

高数据密度仪表盘布局(Lumo工具类)

java
// Lumo theme only — uses LumoUtility classes
VerticalLayout dashboard = new VerticalLayout();
dashboard.setPadding(true);
dashboard.setSpacing(false);
dashboard.addClassNames(
    LumoUtility.Gap.LARGE,
    LumoUtility.Background.CONTRAST_5
);

// Metric cards row
HorizontalLayout metrics = new HorizontalLayout();
metrics.setWidthFull();
metrics.addClassNames(LumoUtility.Gap.MEDIUM);
metrics.add(
    createCard("Revenue", "$48,200", "+12% from last month"),
    createCard("Users", "1,420", "+5% from last month"),
    createCard("Orders", "384", "+8% from last month")
);
metrics.getChildren().forEach(c -> ((HasSize) c).setWidthFull());

dashboard.add(createSectionHeader("Overview"), metrics);
java
// 仅Lumo主题支持——使用LumoUtility类
VerticalLayout dashboard = new VerticalLayout();
dashboard.setPadding(true);
dashboard.setSpacing(false);
dashboard.addClassNames(
    LumoUtility.Gap.LARGE,
    LumoUtility.Background.CONTRAST_5
);

// 数据卡片行
HorizontalLayout metrics = new HorizontalLayout();
metrics.setWidthFull();
metrics.addClassNames(LumoUtility.Gap.MEDIUM);
metrics.add(
    createCard("Revenue", "$48,200", "+12% from last month"),
    createCard("Users", "1,420", "+5% from last month"),
    createCard("Orders", "384", "+8% from last month")
);
metrics.getChildren().forEach(c -> ((HasSize) c).setWidthFull());

dashboard.add(createSectionHeader("Overview"), metrics);

Styling Vaadin Components with CSS

用CSS给Vaadin组件加样式

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()
:
css
/* Style Grid header cells */
vaadin-grid::part(header-cell) {
    background-color: var(--lumo-contrast-5pct);  /* Lumo */
    /* or for Aura: --aura-surface-level: 0; */
    font-weight: 600;
}

/* Style Dialog overlay */
vaadin-dialog-overlay::part(overlay) {
    border-radius: 16px;
}

/* Style TextField input */
vaadin-text-field::part(input-field) {
    border-radius: 8px;
}
Component-scoped styles — apply CSS to a specific view:
java
@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.
当主题属性和主题变体都满足不了需求时,使用CSS。
Shadow DOM和
::part()
选择器
——Vaadin组件使用shadow DOM,用
::part()
给内部元素加样式:
css
/* 给Grid表头单元格加样式 */
vaadin-grid::part(header-cell) {
    background-color: var(--lumo-contrast-5pct);  /* Lumo */
    /* Aura可以用:--aura-surface-level: 0; */
    font-weight: 600;
}

/* 给Dialog弹窗加样式 */
vaadin-dialog-overlay::part(overlay) {
    border-radius: 16px;
}

/* 给TextField输入框加样式 */
vaadin-text-field::part(input-field) {
    border-radius: 8px;
}
组件范围样式——给特定视图应用CSS:
java
@StyleSheet("styles/views/dashboard-view.css")
@Route("dashboard")
public class DashboardView extends VerticalLayout {
    // ...
}
核心原则:永远优先使用主题自定义属性,不要在CSS里写硬编码值。用当前主题的token定义颜色、间距和尺寸,这样能保证样式和主题统一,也能简化深色模式、密度调整和后续的改版工作。

Common Anti-Patterns

常见反模式

  1. Hardcoded colors and sizes — always use your theme's custom properties. Hardcoded values break dark mode and make theming impossible.
  2. 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.
  3. Too many visual styles — pick 2-3 card styles, 2-3 text hierarchies, one primary action style. Consistency creates professionalism.
  4. Ignoring theme variants — check
    ButtonVariant
    ,
    GridVariant
    ,
    TextFieldVariant
    , etc. before writing custom CSS. The variant you need probably exists.
  5. 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.
  6. Flat visual hierarchy — if everything looks the same, nothing stands out. Use size, weight, color, and elevation to guide the eye to what matters.
  1. 硬编码颜色和尺寸——永远使用主题的自定义属性,硬编码值会破坏深色模式,也会导致后续无法统一换肤。
  2. 不修改主题属性,直接覆盖组件内部样式——如果某个颜色看起来不对,先覆盖主题颜色token,不要修改单个组件的CSS,修改一个属性就能更新整个应用。
  3. 视觉样式过多——选定2-3种卡片样式、2-3种文本层级、一种主操作样式,统一才能营造专业感。
  4. 忽略主题变体——编写自定义CSS前先检查
    ButtonVariant
    GridVariant
    TextFieldVariant
    等预置变体,你需要的变体大概率已经存在。
  5. 在服务端渲染更新时加厚重动画——Vaadin从服务端重新渲染内容,每次服务端推送都加复杂入场动画会很卡顿,只在首次页面加载和用户触发的过渡场景用动画,不要给每个数据更新都加动画。
  6. 视觉层级扁平——如果所有元素看起来都一样,就没有重点了,用尺寸、字重、颜色和层级引导用户关注重要内容。

Best Practices

最佳实践

  1. Start with theme properties — customize your theme's core properties first. This alone can transform the look.
  2. Use component theme variants — primary, tertiary, compact, badge themes. Use what's built in.
  3. Maintain a consistent color strategy — one primary/accent, one or two additional colors, semantic colors for status.
  4. Design with elevation — cards, dialogs, and menus should feel layered. Use your theme's elevation system consistently.
  5. Add motion sparingly — CSS transitions on hover, a page-load fade-in, staggered card reveals. Keep it under 400ms.
  6. Test in dark mode — if you customize any colors, verify both light and dark themes. Using theme properties makes this automatic.
  1. 从主题属性开始调整——先自定义主题的核心属性,仅这一步就能大幅改变外观。
  2. 使用组件主题变体——primary、tertiary、compact、徽章主题等,优先使用预置能力。
  3. 保持统一的颜色策略——一个主色/强调色、1-2个辅助色、语义化状态色。
  4. 用层级做设计——卡片、弹窗、菜单要有分层感,统一使用主题的层级系统。
  5. 少量添加动效——hover过渡、页面加载淡入、错落卡片展示即可,时长控制在400ms以内。
  6. 在深色模式下测试——如果自定义了任何颜色,要同时验证浅色和深色主题,使用主题属性能自动适配。

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
.
关于组件
::part()
选择器、CSS动画方案和调色板方案,可参考
references/design-patterns.md
。完整的主题token表和变体对比可参考主题定制技能的
references/theming-patterns.md