mcpField
F
frontend-design

frontend-design

github.com/vaadin设计与 UI790 次安装1 天前更新
代码仓库

关于 frontend-design

一项用于以 Java 和 CSS 构建精致 Vaadin 界面的技能,涵盖设计方向与主题样式,并关注字体、色彩、间距和组件细节。

主要功能

  1. 01规划统一的视觉方向
  2. 02定制 Aura 和 Lumo 主题
  3. 03调整字体、色彩与间距
  4. 04使用 CSS 和变体样式化组件
  5. 05添加动效与交互效果

适用场景

  1. 01设计 Vaadin 管理仪表盘
  2. 02构建面向客户的门户
  3. 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。两者都提供:

  1. CSS 自定义属性——用于颜色、字体、间距、尺寸、边框和阴影的基础。覆盖这些属性即可改变整个应用程序的外观。
  2. 组件主题变体——预先构建的视觉变体(例如 primary、tertiary、compact)。编写自定义 CSS 之前,先使用这些变体。
  3. 自定义 CSS——用于处理主题未覆盖的内容。使用 @StyleSheet 配合作用于特定视图的 CSS 文件。

此外,Lumo 还提供:

  1. 实用工具类——类似 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 中硬编码值。使用当前主题的令牌来设置颜色、间距和尺寸。这样可以使样式与主题保持一致,并让深色模式、密度调整和未来的重新设计变得轻而易举。

常见反模式

  1. 硬编码颜色和尺寸 — 始终使用主题的自定义属性。硬编码的值会破坏深色模式,并使主题化无法实现。
  2. 覆盖组件内部样式,而非主题属性 — 如果颜色看起来不对,应覆盖主题颜色令牌,而不是单个组件的 CSS。更改一个属性即可更新整个应用。
  3. 视觉样式过多 — 选择 2-3 种卡片样式、2-3 级文本层次,以及一种主要操作样式。保持一致才能显得专业。
  4. 忽略主题变体 — 编写自定义 CSS 前,先检查 ButtonVariant、GridVariant、TextFieldVariant 等。你需要的变体很可能已经存在。
  5. 服务端渲染更新时使用复杂动画 — Vaadin 会在服务端重新渲染。每次服务端推送都播放复杂的入场动画会显得不流畅。动画应仅用于初始页面加载和用户发起的过渡,而非每次数据更新。
  6. 视觉层次扁平 — 如果所有元素看起来都一样,就没有任何元素能脱颖而出。使用尺寸、字重、颜色和阴影层次,引导视线关注重要内容。

最佳实践

  1. 从主题属性入手 — 首先自定义主题的核心属性。仅此一项就能彻底改变外观。
  2. 使用组件主题变体 — primary、tertiary、compact、badge 主题。使用内置选项。
  3. 保持颜色策略一致 — 使用一种主色/强调色、一到两种辅助色,以及用于表示状态的语义颜色。
  4. 通过阴影层次进行设计 — 卡片、对话框和菜单应具有层次感。始终一致地使用主题的阴影层次系统。
  5. 谨慎添加动效 — 使用悬停时的 CSS 过渡、页面加载时的淡入效果和卡片的交错显现效果。时长控制在 400ms 以内。
  6. 在深色模式下测试 — 如果自定义了任何颜色,请验证浅色和深色主题。使用主题属性可自动处理这一点。

详细参考

有关组件 ::part() 选择器、CSS 动画示例和调色板示例,请参阅 references/design-patterns.md。有关完整的主题令牌表和变体对比,请参阅 theming 技能的 references/theming-patterns.md。

评论

支持 Markdown