关于 frontend-design
一项用于以 Java 和 CSS 构建精致 Vaadin 界面的技能,涵盖设计方向与主题样式,并关注字体、色彩、间距和组件细节。
主要功能
- 01规划统一的视觉方向
- 02定制 Aura 和 Lumo 主题
- 03调整字体、色彩与间距
- 04使用 CSS 和变体样式化组件
- 05添加动效与交互效果
适用场景
- 01设计 Vaadin 管理仪表盘
- 02构建面向客户的门户
- 03统一数据密集型视图的样式
MCP 相关
建议使用 Vaadin MCP 工具查询文档,并使用 Javadoc MCP 查找 Java API 签名和源码。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ vaadin/ agent-skills --skill frontend-design安装后在 Claude Code 中输入/frontend-design确认已加载。
附带文件references/design-patterns.md
详细介绍
打造独具特色的 Vaadin 界面
如对特定 API 细节不确定,请使用 Vaadin MCP 工具(search_vaadin_docs、get_component_styling、get_component_java_api)查阅最新文档。始终将 vaadin_version 设为 "25",将 ui_language 设为 "java"。
如需准确的 Java API 签名或查看源码,请使用 Javadoc MCP(mcp__javadoc__*——若尚未加载,请通过 ToolSearch 搜索 javadoc)从 Maven Central 查阅 Javadoc 和源码,而不要从 ~/.m2 解压 JAR。
本技能指导你创建独具特色、精致出众的 Vaadin 界面,突破默认主题样式的限制。目标是编写达到生产级别的代码,真正注重美学细节,而不是泛泛而用的默认样式。
设计思路
编写代码之前,先了解场景并确定明确的美学方向:
- 用途:这个界面要解决什么问题?谁会使用它?管理仪表盘与面向客户的门户有不同的设计需求。
- 风格:选择一个方向:简洁专业、温暖亲切、大胆且信息密集、明亮通透、暗色专注、编辑风、活泼,或精致雅致。设计方向应服务于用户和内容。
- 约束:Vaadin 组件库、Vaadin 主题系统(Aura 或 Lumo)、服务端渲染模型、无障碍要求。
- 差异化:哪些设计能让界面显得精心打造,而不是使用默认样式?用户会注意到什么细节?
重要:选择明确的设计方向并始终如一地贯彻。每一处颜色、间距和字体选择都应强化同一种美学。协调统一的简约设计,始终胜过风格不一的繁复设计。
然后实现可运行的代码(Java + CSS),使其满足以下要求:
- 达到生产级别且功能完善
- 视觉协调统一,设计方向明确
- 基于 Vaadin 的组件库和主题系统构建
- 在间距、颜色、字体和视觉层级方面经过精心打磨
Vaadin 主题系统
所有视觉定制都从所选主题开始——Aura 或 Lumo。两者都提供:
- CSS 自定义属性——用于颜色、字体、间距、尺寸、边框和阴影的基础。覆盖这些属性即可改变整个应用程序的外观。
- 组件主题变体——预先构建的视觉变体(例如 primary、tertiary、compact)。编写自定义 CSS 之前,先使用这些变体。
- 自定义 CSS——用于处理主题未覆盖的内容。使用
@StyleSheet配合作用于特定视图的 CSS 文件。
此外,Lumo 还提供:
- 实用工具类——类似 Tailwind 的类(
LumoUtility.*),用于布局、间距和颜色。可在 Java 中快速应用。Aura 不支持。
始终自上而下进行:先更改主题属性,再编写组件专属 CSS;先使用主题变体,再编写自定义样式。
有关主题选择、加载和所有设计令牌的完整详情,请参阅 theming 技能。
字体排印
字体排印决定了整个界面的风格。
自定义字体系列(两种主题均适用):
/* 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;
}
调整字体比例:
Aura——调整一个值即可计算出整个比例:
html {
--aura-base-font-size: 15; /* unitless number, represents px */
}
Lumo——覆盖单独的令牌:
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 实用工具类):
// 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
);
对于 Aura,请使用 CSS 类或内联样式,而不要使用 LumoUtility。
关键原则:建立清晰的层级——一种主标题样式、一种正文样式、一种次要文字或说明文字样式。结合使用字号、字重和颜色来区分层级。单个视图中,避免使用超过 3-4 种不同的文字样式。
颜色与主题
颜色是为 Vaadin 应用打造独特标识最快捷的方式。
Aura — 自定义强调色和调色板:
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 — 覆盖调色板:
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%);
}
深色模式:
两种主题都支持 @ColorScheme 注解。如需自定义深色模式颜色,请参阅主题技能,了解特定主题的选择器(Lumo 使用 [theme~="dark"],Aura 使用带 -light/-dark 后缀的属性)。
在 Java 中设置强调色和语义颜色(Lumo 工具类):
// Lumo theme only
badge.addClassNames(
LumoUtility.Background.PRIMARY,
LumoUtility.TextColor.PRIMARY_CONTRAST
);
warningCard.addClassNames(
LumoUtility.Background.WARNING_10PCT,
LumoUtility.TextColor.WARNING
);
关键原则:选择协调统一的调色板。挑选一种鲜明的主色/强调色,并使用主题的变体系统(Lumo 的不透明度变体、Aura 的计算变体)来设置辅助用途。与均匀分布的彩虹色相比,突出主色并克制使用强调色,效果更好。
间距与密度
一致的间距能营造视觉节奏感和专业感。
Aura — 调整基础尺寸:
html {
--aura-base-size: 18; /* unitless, range 12–24 */
--aura-base-radius: 8; /* unitless, range 0–10 */
}
Lumo — 覆盖单独的间距令牌:
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 工具类):
// Lumo theme only
card.addClassNames(
LumoUtility.Padding.LARGE,
LumoUtility.Gap.MEDIUM
);
section.addClassNames(
LumoUtility.Padding.Horizontal.LARGE,
LumoUtility.Padding.Vertical.XLARGE
);
紧凑/高密度变体:
// Cross-theme variants — work in both Aura and Lumo
grid.addThemeVariants(GridVariant.NO_BORDER);
textField.addThemeVariants(TextFieldVariant.SMALL);
button.addThemeVariants(ButtonVariant.SMALL);
// Lumo-only: compact grid
grid.addThemeVariants(GridVariant.LUMO_COMPACT);
关键原则:选择一种密度并始终保持一致。数据密集型仪表板应始终采用紧凑布局。宽敞的营销风格视图则应始终保持疏朗。混用不同密度会显得不协调。
阴影、边框与层级
通过层级营造纵深感和视觉层次。
Aura — 表面层级系统:
.elevated-card {
background: var(--aura-surface-color);
--aura-surface-level: 2;
}
.sunken-area {
background: var(--aura-surface-color);
--aura-surface-level: -1;
}
Lumo — 显式阴影令牌:
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);
}
// Lumo theme only
card.addClassNames(
LumoUtility.BoxShadow.SMALL,
LumoUtility.BorderRadius.MEDIUM
);
自定义边框圆角:
/* 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 */
}
用于分隔的细微边框(Lumo 工具类):
// Lumo theme only
section.addClassNames(
LumoUtility.Border.BOTTOM,
LumoUtility.BorderColor.CONTRAST_10
);
关键原则:一致地使用层级来传达视觉层次。卡片浮于表面之上,对话框浮于卡片之上。不要给平面元素添加厚重阴影,也不要给浮动元素添加平面阴影。
动效与动画
Vaadin 的服务端模型意味着动画应由 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; }
content.addClassName("fade-in");
// Staggered card reveal
for (Component card : cards) {
card.addClassName("stagger-item");
}
悬停与交互效果:
.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);
}
关键原则:一两个精心设计的动画(例如错开显示的页面加载效果)比到处零散的微交互更有影响力。悬停/聚焦时优先使用 CSS 过渡,而不是复杂的关键帧动画。持续时间控制在 400ms 以内。
打造精致的组件
样式化卡片(Lumo 工具类)
// 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;
}
对于 Aura,请使用基于 Aura 表面系统的 CSS 类,而不要使用 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;
}
状态徽章
徽章是 Vaadin 25.1 中的预览功能——通过 badgeComponent 功能标志启用。可用变体:SUCCESS、WARNING、ERROR、FILLED、ICON_ONLY、NUMBER_ONLY。
// Badge component (Vaadin 25.1+ preview — requires badgeComponent feature flag)
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);
// With icon
Badge iconBadge = new Badge("Confirmed", VaadinIcon.CHECK.create());
iconBadge.addThemeVariants(BadgeVariant.SUCCESS);
// With number
Badge counter = new Badge("Inbox", 12);
counter.addThemeVariants(BadgeVariant.FILLED);
对于早于 25.1 的 Vaadin 版本,可以使用带有 theme="badge" 属性的 Span 元素创建徽章,但此方法已弃用。
高密度数据仪表板布局(Lumo 工具类)
// 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 grid — uses CSS Grid for responsive layout
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");
// Companion CSS:
// .metrics-grid {
// display: grid;
// grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
// gap: var(--lumo-space-m);
// width: 100%;
// }
dashboard.add(createSectionHeader("Overview"), metrics);
使用 CSS 为 Vaadin 组件设置样式
当主题属性和主题变体无法满足需求时,可以使用 CSS。
Shadow DOM 和 ::part() 选择器 — Vaadin 组件使用 Shadow DOM。使用 ::part() 设置内部部件的样式:
/* 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;
}
组件作用域样式 — 将 CSS 应用于特定视图:
@StyleSheet("styles/views/dashboard-view.css")
@Route("dashboard")
public class DashboardView extends VerticalLayout {
// ...
}
关键原则:始终优先使用主题自定义属性,而不是在 CSS 中硬编码值。使用当前主题的令牌来设置颜色、间距和尺寸。这样可以使样式与主题保持一致,并让深色模式、密度调整和未来的重新设计变得轻而易举。
常见反模式
- 硬编码颜色和尺寸 — 始终使用主题的自定义属性。硬编码的值会破坏深色模式,并使主题化无法实现。
- 覆盖组件内部样式,而非主题属性 — 如果颜色看起来不对,应覆盖主题颜色令牌,而不是单个组件的 CSS。更改一个属性即可更新整个应用。
- 视觉样式过多 — 选择 2-3 种卡片样式、2-3 级文本层次,以及一种主要操作样式。保持一致才能显得专业。
- 忽略主题变体 — 编写自定义 CSS 前,先检查
ButtonVariant、GridVariant、TextFieldVariant等。你需要的变体很可能已经存在。 - 服务端渲染更新时使用复杂动画 — Vaadin 会在服务端重新渲染。每次服务端推送都播放复杂的入场动画会显得不流畅。动画应仅用于初始页面加载和用户发起的过渡,而非每次数据更新。
- 视觉层次扁平 — 如果所有元素看起来都一样,就没有任何元素能脱颖而出。使用尺寸、字重、颜色和阴影层次,引导视线关注重要内容。
最佳实践
- 从主题属性入手 — 首先自定义主题的核心属性。仅此一项就能彻底改变外观。
- 使用组件主题变体 — primary、tertiary、compact、badge 主题。使用内置选项。
- 保持颜色策略一致 — 使用一种主色/强调色、一到两种辅助色,以及用于表示状态的语义颜色。
- 通过阴影层次进行设计 — 卡片、对话框和菜单应具有层次感。始终一致地使用主题的阴影层次系统。
- 谨慎添加动效 — 使用悬停时的 CSS 过渡、页面加载时的淡入效果和卡片的交错显现效果。时长控制在 400ms 以内。
- 在深色模式下测试 — 如果自定义了任何颜色,请验证浅色和深色主题。使用主题属性可自动处理这一点。
详细参考
有关组件 ::part() 选择器、CSS 动画示例和调色板示例,请参阅 references/design-patterns.md。有关完整的主题令牌表和变体对比,请参阅 theming 技能的 references/theming-patterns.md。