flutter-duskmoon-design
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFlutter DuskMoon UI — Design Principles & Usage Rules
Flutter DuskMoon UI — 设计原则与使用规则
Architecture Overview
架构概述
Package Dependency Graph (import direction →)
包依赖关系图(导入方向 →)
duskmoon-dev/design (YAML → codegen)
→ duskmoon_theme
├ DmDesignTokens (generated const data)
├ DmTheme (InheritedWidget)
├ DmPlatformStyle { material, cupertino, fluent }
└ toMaterial() / toCupertino() / toFluent()
duskmoon_theme
→ duskmoon_widgets
├ DuskmoonApp (root shell)
├ DmAdaptiveWidget (base class)
└ Dm* widgets (Button, TextField, Switch, etc.)
duskmoon_widgets
→ duskmoon_settings
→ duskmoon_feedback
→ duskmoon_ui (umbrella re-export)Rule: Never import downstream. must never import from . must never import from .
duskmoon_themeduskmoon_widgetsduskmoon_widgetsduskmoon_settingsduskmoon-dev/design (YAML → codegen)
→ duskmoon_theme
├ DmDesignTokens (generated const data)
├ DmTheme (InheritedWidget)
├ DmPlatformStyle { material, cupertino, fluent }
└ toMaterial() / toCupertino() / toFluent()
duskmoon_theme
→ duskmoon_widgets
├ DuskmoonApp (root shell)
├ DmAdaptiveWidget (base class)
└ Dm* widgets (Button, TextField, Switch, etc.)
duskmoon_widgets
→ duskmoon_settings
→ duskmoon_feedback
→ duskmoon_ui (umbrella re-export)规则:禁止导入下游包。 绝不能从 导入内容。 绝不能从 导入内容。
duskmoon_themeduskmoon_widgetsduskmoon_widgetsduskmoon_settingsWidget Tree (Runtime)
Widget树(运行时)
DuskmoonApp(tokens: .sunshine, darkTokens: .moonlight, platformStyle: .cupertino)
└→ DmTheme (InheritedWidget — .of(context) available everywhere below)
└→ CupertinoApp(theme: tokens.toCupertino())
└→ user's widget tree
└→ DmButton(label: "Save") // dispatches to CupertinoButtonDuskmoonApp(tokens: .sunshine, darkTokens: .moonlight, platformStyle: .cupertino)
└→ DmTheme (InheritedWidget — .of(context) 可在下方所有节点调用)
└→ CupertinoApp(theme: tokens.toCupertino())
└→ 用户的Widget树
└→ DmButton(label: "Save") // 调度到CupertinoButtonRule 1: Theme Setup
规则1:主题配置
Correct App Root
正确的应用根节点
dart
// ✅ ALWAYS — use DuskmoonApp as the root widget
void main() {
runApp(
DuskmoonApp(
tokens: DmDesignTokens.sunshine,
darkTokens: DmDesignTokens.moonlight,
themeMode: ThemeMode.system,
platformStyle: DmPlatformStyle.material,
home: const MyHomePage(),
),
);
}dart
// ✅ 必须使用 DuskmoonApp 作为根Widget
void main() {
runApp(
DuskmoonApp(
tokens: DmDesignTokens.sunshine,
darkTokens: DmDesignTokens.moonlight,
themeMode: ThemeMode.system,
platformStyle: DmPlatformStyle.material,
home: const MyHomePage(),
),
);
}❌ NEVER — bypass DuskmoonApp
❌ 禁止绕过 DuskmoonApp
dart
// ❌ NEVER wrap MaterialApp/CupertinoApp directly
void main() {
runApp(MaterialApp(
theme: DmDesignTokens.sunshine.toMaterial(), // wrong — skips DmTheme
home: MyHomePage(),
));
}Why: injects above the platform app. Without it, returns null and all Dm* widgets fail to resolve tokens.
DuskmoonAppDmThemeDmTheme.of(context)dart
// ❌ 禁止直接包裹 MaterialApp/CupertinoApp
void main() {
runApp(MaterialApp(
theme: DmDesignTokens.sunshine.toMaterial(), // 错误 — 跳过了DmTheme
home: MyHomePage(),
));
}原因: 会在平台应用上方注入 。如果没有它, 会返回null,所有Dm* Widget都无法解析令牌。
DuskmoonAppDmThemeDmTheme.of(context)Rule 2: Accessing Design Tokens
规则2:访问设计令牌
Always use DmTheme.of(context)
DmTheme.of(context)始终使用 DmTheme.of(context)
DmTheme.of(context)dart
// ✅ Read tokens from the widget tree
Widget build(BuildContext context) {
final tokens = DmTheme.of(context).tokens;
return Container(
color: tokens.primaryContainer,
child: Text('Hello', style: TextStyle(color: tokens.onPrimaryContainer)),
);
}dart
// ✅ 从Widget树中读取令牌
Widget build(BuildContext context) {
final tokens = DmTheme.of(context).tokens;
return Container(
color: tokens.primaryContainer,
child: Text('Hello', style: TextStyle(color: tokens.onPrimaryContainer)),
);
}❌ NEVER reference generated constants directly in widget builds
❌ 禁止在Widget构建中直接引用生成的常量
dart
// ❌ This ignores dark mode, theme overrides, and subtree overrides
Widget build(BuildContext context) {
return Container(color: DmDesignTokens.sunshine.primaryContainer);
}Why: The resolved tokens depend on , platform brightness, and possible ancestors. Only returns the correct resolved set.
ThemeModeDmThemeOverrideDmTheme.of(context)dart
// ❌ 这种写法忽略了深色模式、主题覆盖和子树覆盖
Widget build(BuildContext context) {
return Container(color: DmDesignTokens.sunshine.primaryContainer);
}原因: 解析后的令牌取决于 、平台亮度以及可能存在的 父节点。只有 能返回正确的解析结果。
ThemeModeDmThemeOverrideDmTheme.of(context)Exception — static adapter methods
例外情况 — 静态适配器方法
DuskmoonAppThemeDataDmThemedart
// Inside DuskmoonApp.build() — acceptable
MaterialApp(theme: DmTheme.staticToMaterial(resolvedTokens));DuskmoonAppDmThemeThemeDatadart
// 在 DuskmoonApp.build() 内部 — 允许使用
MaterialApp(theme: DmTheme.staticToMaterial(resolvedTokens));Rule 3: Color System
规则3:颜色系统
Token Structure (61 color tokens per theme)
令牌结构(每个主题包含61个颜色令牌)
| Group | Tokens | Usage |
|---|---|---|
| Primary | | Main brand actions, primary CTAs |
| Secondary | | Supporting actions, alternative CTAs |
| Tertiary | | Accent highlights, badges, special UI |
| Error | | Error states, destructive actions |
| Surface | | Backgrounds, cards, elevation |
| Outline | | Borders, dividers |
| Inverse | | Snackbars, contrast overlays |
| Scrim/Shadow | | Modal overlays, elevation shadows |
| Semantic | | Status indicators |
| 分组 | 令牌 | 用途 |
|---|---|---|
| Primary | | 主品牌操作、主要CTA按钮 |
| Secondary | | 辅助操作、备选CTA按钮 |
| Tertiary | | 强调高亮、徽章、特殊UI元素 |
| Error | | 错误状态、破坏性操作 |
| Surface | | 背景、卡片、层级 |
| Outline | | 边框、分隔线 |
| Inverse | | Snackbar、对比遮罩 |
| Scrim/Shadow | | 模态遮罩、层级阴影 |
| Semantic | | 状态指示器 |
Color Format: OKLCH
颜色格式:OKLCH
All colors are defined in OKLCH in the source CSS. The Dart codegen converts to objects via inline OKLCH→sRGB math (zero external deps).
Color所有颜色在源CSS中以OKLCH格式定义。Dart代码生成器通过内置的OKLCH→sRGB转换逻辑将其转换为对象(无外部依赖)。
Color❌ NEVER hardcode color values
❌ 禁止硬编码颜色值
dart
// ❌ Hardcoded hex
Container(color: Color(0xFF60A5FA))
// ❌ Hardcoded Material color
Container(color: Colors.blue)
// ✅ Use design tokens
Container(color: DmTheme.of(context).tokens.primary)dart
// ❌ 硬编码十六进制颜色
Container(color: Color(0xFF60A5FA))
// ❌ 硬编码Material颜色
Container(color: Colors.blue)
// ✅ 使用设计令牌
Container(color: DmTheme.of(context).tokens.primary)Semantic color pairing rule
语义颜色配对规则
Every background token has a corresponding foreground token. Always pair them:
dart
// ✅ Correct pairing
Container(
color: tokens.primaryContainer,
child: Text('Label', style: TextStyle(color: tokens.onPrimaryContainer)),
)
// ❌ Mismatched — onSurface on primaryContainer may fail contrast
Container(
color: tokens.primaryContainer,
child: Text('Label', style: TextStyle(color: tokens.onSurface)),
)| Background | Foreground |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
每个背景令牌都有对应的前景令牌。 必须配对使用:
dart
// ✅ 正确配对
Container(
color: tokens.primaryContainer,
child: Text('Label', style: TextStyle(color: tokens.onPrimaryContainer)),
)
// ❌ 错误配对 — 在primaryContainer上使用onSurface可能导致对比度不足
Container(
color: tokens.primaryContainer,
child: Text('Label', style: TextStyle(color: tokens.onSurface)),
)| 背景令牌 | 前景令牌 |
|---|---|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
Surface elevation hierarchy
Surface层级结构
Use surface container tokens for visual depth, not opacity or shadows alone:
surfaceContainerLowest → bottom layer (behind everything)
surfaceContainerLow → low-elevation cards
surfaceContainer → standard cards/containers
surfaceContainerHigh → elevated cards, menus
surfaceContainerHighest → dialogs, tooltips, top layer使用surface容器令牌实现视觉深度,不要仅依赖透明度或阴影:
surfaceContainerLowest → 最底层(在所有元素之后)
surfaceContainerLow → 低层级卡片
surfaceContainer → 标准卡片/容器
surfaceContainerHigh → 高层级卡片、菜单
surfaceContainerHighest → 对话框、工具提示、最顶层Rule 4: Available Themes
规则4:可用主题
5 themes defined in , codegen'd to Dart:
duskmoon-dev/design| Theme | Mode | Primary Character |
|---|---|---|
| light | Warm amber/gold |
| dark | Cool blue/lavender |
| dark | Deep blue/teal |
| light | Natural green/earth |
| light | Warm orange/rose |
Access via , , etc.
DmDesignTokens.sunshineDmDesignTokens.moonlight在中定义了5个主题,通过代码生成转换为Dart代码:
duskmoon-dev/design| 主题 | 模式 | 主色调特征 |
|---|---|---|
| 浅色 | 暖琥珀色/金色 |
| 深色 | 冷蓝色/淡紫色 |
| 深色 | 深蓝色/蓝绿色 |
| 浅色 | 自然绿色/大地色 |
| 浅色 | 暖橙色/玫瑰色 |
通过、等方式访问。
DmDesignTokens.sunshineDmDesignTokens.moonlightRule 5: Platform Adaptive Widgets
规则5:平台自适应Widget
Resolution Stack (highest priority first)
优先级解析栈(优先级从高到低)
L1: Per-widget `platformOverride` parameter
L2: Nearest DmPlatformOverride ancestor (subtree override)
L3: DmTheme.of(context).platformStyle (from DuskmoonApp)
L4: defaultTargetPlatform (auto-detect)L1: 单个Widget的`platformOverride`参数
L2: 最近的DmPlatformOverride父节点(子树覆盖)
L3: DmTheme.of(context).platformStyle(来自DuskmoonApp)
L4: defaultTargetPlatform(自动检测)Writing an adaptive widget
编写自适应Widget
All adaptive widgets extend :
DmAdaptiveWidgetdart
class DmButton extends DmAdaptiveWidget {
const DmButton({super.key, required this.label, super.platformOverride});
final String label;
Widget buildMaterial(BuildContext context, DmDesignTokens tokens) {
return FilledButton(onPressed: () {}, child: Text(label));
}
Widget buildCupertino(BuildContext context, DmDesignTokens tokens) {
return CupertinoButton.filled(onPressed: () {}, child: Text(label));
}
// buildFluent defaults to buildMaterial unless overridden
}所有自适应Widget都继承自:
DmAdaptiveWidgetdart
class DmButton extends DmAdaptiveWidget {
const DmButton({super.key, required this.label, super.platformOverride});
final String label;
Widget buildMaterial(BuildContext context, DmDesignTokens tokens) {
return FilledButton(onPressed: () {}, child: Text(label));
}
Widget buildCupertino(BuildContext context, DmDesignTokens tokens) {
return CupertinoButton.filled(onPressed: () {}, child: Text(label));
}
// buildFluent默认继承buildMaterial,除非重写
}❌ NEVER check platform manually
❌ 禁止手动判断平台
dart
// ❌ Manual platform switching
if (Platform.isIOS) {
return CupertinoButton(...);
} else {
return ElevatedButton(...);
}
// ✅ Use DmAdaptiveWidget dispatch or DmPlatformStyle resolution
class MyWidget extends DmAdaptiveWidget { ... }dart
// ❌ 手动切换平台
if (Platform.isIOS) {
return CupertinoButton(...);
} else {
return ElevatedButton(...);
}
// ✅ 使用DmAdaptiveWidget调度或DmPlatformStyle解析
class MyWidget extends DmAdaptiveWidget { ... }File structure for adaptive widgets
自适应Widget的文件结构
dm_button/
├── dm_button.dart # Public API, extends DmAdaptiveWidget
├── dm_button_material.dart # buildMaterial implementation
├── dm_button_cupertino.dart # buildCupertino implementation
└── dm_button_fluent.dart # buildFluent (optional, falls through to material)dm_button/
├── dm_button.dart # 公开API,继承自DmAdaptiveWidget
├── dm_button_material.dart # buildMaterial实现
├── dm_button_cupertino.dart # buildCupertino实现
└── dm_button_fluent.dart # buildFluent(可选,默认回退到material)Rule 6: Shared Design Enums
规则6:共享设计枚举
All Dm* widgets share these semantic enums. Use them consistently — never invent ad-hoc parameters.
dart
enum DmColorRole { primary, secondary, tertiary, error, neutral }
enum DmSize { xs, sm, md, lg, xl }
enum DmButtonVariant { filled, outlined, ghost, tonal }
enum DmInputVariant { outlined, filled, underlined }所有Dm* Widget共享以下语义枚举。请统一使用,禁止自定义临时参数。
dart
enum DmColorRole { primary, secondary, tertiary, error, neutral }
enum DmSize { xs, sm, md, lg, xl }
enum DmButtonVariant { filled, outlined, ghost, tonal }
enum DmInputVariant { outlined, filled, underlined }Color resolution from DmColorRole
通过DmColorRole解析颜色
Every widget that takes resolves tokens identically:
DmColorRole| DmColorRole | Background | Foreground | Container | On Container |
|---|---|---|---|---|
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
所有接收的Widget解析令牌的逻辑一致:
DmColorRole| DmColorRole | 背景 | 前景 | 容器 | 容器前景 |
|---|---|---|---|---|
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
Size scale
尺寸缩放
| DmSize | Horizontal padding | Vertical padding | Font scale |
|---|---|---|---|
| 8 | 4 | 0.75rem (12) |
| 12 | 6 | 0.875rem (14) |
| 16 | 8 | 0.875rem (14) |
| 24 | 12 | 1rem (16) |
| 32 | 16 | 1.125rem (18) |
| DmSize | 水平内边距 | 垂直内边距 | 字体缩放 |
|---|---|---|---|
| 8 | 4 | 0.75rem (12) |
| 12 | 6 | 0.875rem (14) |
| 16 | 8 | 0.875rem (14) |
| 24 | 12 | 1rem (16) |
| 32 | 16 | 1.125rem (18) |
Rule 7: Component Design — Actions
规则7:组件设计 — 操作类
DmButton
DmButton
Default: , ,
variant: filledcolor: primarysize: md| Variant | Background | Foreground | Border | Use case |
|---|---|---|---|---|
| role color | onRole | none | Primary CTAs, main actions |
| transparent | role color | role color | Secondary actions, cancel |
| transparent | role color | none | Tertiary/inline actions, links |
| roleContainer | onRoleContainer | none | Soft emphasis, toggles |
Color role assignment convention:
| Action type | Color role | Example |
|---|---|---|
| Main CTA, save, submit, confirm | | "Save Changes" |
| Alternative action, secondary flow | | "Export", "Share" |
| Accent action, special highlight | | "Watch Demo", "Premium" |
| Destructive, delete, remove | | "Delete Account" |
| Neutral, dismiss, low emphasis | | "Cancel", "Skip" |
dart
// ✅ Typical action group
Row(children: [
DmButton(variant: .ghost, color: .neutral, child: Text('Cancel')),
DmButton(variant: .outlined, color: .secondary, child: Text('Save Draft')),
DmButton(variant: .filled, color: .primary, child: Text('Publish')),
])默认值: , ,
variant: filledcolor: primarysize: md| 变体 | 背景 | 前景 | 边框 | 使用场景 |
|---|---|---|---|---|
| 角色颜色 | onRole | 无 | 主要CTA、核心操作 |
| 透明 | 角色颜色 | 角色颜色 | 次要操作、取消按钮 |
| 透明 | 角色颜色 | 无 | 三级/内联操作、链接 |
| roleContainer | onRoleContainer | 无 | 柔和强调、切换按钮 |
颜色角色分配约定:
| 操作类型 | 颜色角色 | 示例 |
|---|---|---|
| 主要CTA、保存、提交、确认 | | "保存更改" |
| 备选操作、次要流程 | | "导出"、"分享" |
| 强调操作、特殊高亮 | | "观看演示"、"高级版" |
| 破坏性操作、删除、移除 | | "删除账户" |
| 中性操作、关闭、低强调 | | "取消"、"跳过" |
dart
// ✅ 典型操作组
Row(children: [
DmButton(variant: .ghost, color: .neutral, child: Text('Cancel')),
DmButton(variant: .outlined, color: .secondary, child: Text('Save Draft')),
DmButton(variant: .filled, color: .primary, child: Text('Publish')),
])DmIconButton
DmIconButton
Same color/size system as DmButton. Must always have .
semanticLabeldart
DmIconButton(
icon: Icons.delete,
color: DmColorRole.error,
semanticLabel: 'Delete item',
onPressed: () {},
)与DmButton使用相同的颜色/尺寸系统。必须始终设置。
semanticLabeldart
DmIconButton(
icon: Icons.delete,
color: DmColorRole.error,
semanticLabel: 'Delete item',
onPressed: () {},
)DmFab (Floating Action Button)
DmFab(浮动操作按钮)
- Default color: — the single most important action on the screen
primary - Surface: background,
primaryContainericononPrimaryContainer - Rule: Maximum one FAB per screen. If you need multiple actions, use .
DmActionList
dart
DmFab(
onPressed: () {},
icon: Icons.add,
// FAB always uses primaryContainer/onPrimaryContainer — no color param
)- 默认颜色: — 屏幕上最重要的单个操作
primary - 表面: 背景,
primaryContainer图标onPrimaryContainer - 规则: 每个屏幕最多一个FAB。如果需要多个操作,请使用。
DmActionList
dart
DmFab(
onPressed: () {},
icon: Icons.add,
// FAB始终使用primaryContainer/onPrimaryContainer — 无颜色参数
)DmActionList
DmActionList
Adapts rendering to available space:
| Breakpoint | Rendering |
|---|---|
| Small (< 600) | Popup menu (overflow) |
| Medium (600–1200) | Icon buttons in row |
| Large (> 1200) | Text buttons with icons |
dart
DmActionList(
actions: [
DmAction(icon: Icons.edit, label: 'Edit', onPressed: ...),
DmAction(icon: Icons.share, label: 'Share', onPressed: ...),
DmAction(icon: Icons.delete, label: 'Delete', color: DmColorRole.error, onPressed: ...),
],
)根据可用空间自适应渲染:
| 断点 | 渲染方式 |
|---|---|
| 小屏(< 600) | 弹出菜单(溢出) |
| 中屏(600–1200) | 行内图标按钮 |
| 大屏(> 1200) | 带图标的文本按钮 |
dart
DmActionList(
actions: [
DmAction(icon: Icons.edit, label: 'Edit', onPressed: ...),
DmAction(icon: Icons.share, label: 'Share', onPressed: ...),
DmAction(icon: Icons.delete, label: 'Delete', color: DmColorRole.error, onPressed: ...),
],
)Rule 8: Component Design — Navigation
规则8:组件设计 — 导航类
DmAppBar
DmAppBar
Default token mapping:
| Element | Token | Rationale |
|---|---|---|
| Background | | Brand presence, top-level identity |
| Title text | | Contrast on primary |
| Icon buttons | | Consistent with primary surface |
| Bottom border | none (primary fills) | Clean branded bar |
Scrolled/elevated state: Background transitions to , text to .
primaryContaineronPrimaryContainerdart
DmAppBar(
title: Text('Settings'),
leading: DmIconButton(icon: Icons.arrow_back, semanticLabel: 'Back'),
actions: [
DmIconButton(icon: Icons.search, semanticLabel: 'Search'),
DmIconButton(icon: Icons.more_vert, semanticLabel: 'More options'),
],
)Neutral variant: For screens where the app bar should not compete with content (e.g., content-heavy reading views), pass to fall back to /.
color: DmColorRole.neutralsurfaceonSurface默认令牌映射:
| 元素 | 令牌 | 设计理由 |
|---|---|---|
| 背景 | | 品牌展示、顶层身份标识 |
| 标题文本 | | 在primary背景上保证对比度 |
| 图标按钮 | | 与primary表面保持一致 |
| 底部边框 | 无(primary填充整个栏) | 简洁的品牌栏 |
滚动/悬浮状态: 背景过渡为,文本过渡为。
primaryContaineronPrimaryContainerdart
DmAppBar(
title: Text('Settings'),
leading: DmIconButton(icon: Icons.arrow_back, semanticLabel: 'Back'),
actions: [
DmIconButton(icon: Icons.search, semanticLabel: 'Search'),
DmIconButton(icon: Icons.more_vert, semanticLabel: 'More options'),
],
)中性变体: 对于AppBar不应与内容竞争的屏幕(如内容密集的阅读视图),传递回退到/。
color: DmColorRole.neutralsurfaceonSurfaceDmBottomNav
DmBottomNav
| Element | Token |
|---|---|
| Background | |
| Selected icon/label | |
| Unselected icon/label | |
| Selected indicator | |
| Top border | none (primary fills) |
Rule: 3–5 destinations maximum. Labels always visible (not icon-only).
| 元素 | 令牌 |
|---|---|
| 背景 | |
| 选中的图标/标签 | |
| 未选中的图标/标签 | |
| 选中指示器 | |
| 顶部边框 | 无(primary填充整个栏) |
规则: 最多3–5个目标页面。标签始终可见(不支持仅图标模式)。
DmTabBar
DmTabBar
| Element | Token |
|---|---|
| Background | |
| Selected tab | |
| Unselected tab | |
| Indicator | |
| 元素 | 令牌 |
|---|---|
| 背景 | |
| 选中标签 | |
| 未选中标签 | |
| 指示器 | |
DmDrawer
DmDrawer
| Element | Token |
|---|---|
| Background | |
| Header area | |
| Selected item bg | |
| Selected item text | |
| Unselected text | |
| Dividers | |
| Scrim (overlay behind drawer) | |
Side menus and drawers use the secondary color family to visually distinguish navigation chrome from the primary-branded top bar.
| 元素 | 令牌 |
|---|---|
| 背景 | |
| 头部区域 | |
| 选中项背景 | |
| 选中项文本 | |
| 未选中文本 | |
| 分隔线 | |
| 遮罩(Drawer后方的覆盖层) | |
侧边菜单和Drawer使用secondary色系,在视觉上将导航控件与primary品牌的顶部栏区分开。
DmBreadcrumbs
DmBreadcrumbs
| Element | Token |
|---|---|
| Active (current) | |
| Ancestors (links) | |
| Separator | |
| 元素 | 令牌 |
|---|---|
| 激活项(当前页面) | |
| 祖先项(链接) | |
| 分隔符 | |
Rule 9: Component Design — Layout & Cards
规则9:组件设计 — 布局与卡片
DmCard
DmCard
Elevation hierarchy via surface tokens:
| Card style | Background token | Use case |
|---|---|---|
| Flat | | Inline content, no separation |
| Outlined | | List items, settings rows |
| Elevated | | Standard cards |
| Filled | | Emphasized/grouped content |
Interior layout convention:
┌─────────────────────────────────┐
│ [optional media/image] │
├─────────────────────────────────┤
│ Title (onSurface) │
│ Subtitle (onSurfaceVariant)│
│ │
│ Body text (onSurface) │
│ │
│ ┌─────────────────────────────┐ │
│ │ Actions: ghost/outlined btns│ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘dart
DmCard(
style: DmCardStyle.elevated,
child: Column(children: [
Image(...),
Padding(
padding: EdgeInsets.all(16),
child: Column(children: [
Text('Title', style: TextStyle(color: tokens.onSurface)),
Text('Subtitle', style: TextStyle(color: tokens.onSurfaceVariant)),
Row(children: [
DmButton(variant: .ghost, child: Text('Cancel')),
DmButton(variant: .filled, child: Text('Confirm')),
]),
]),
),
]),
)❌ NEVER put a primary card background with text for regular content cards. Primary/secondary/tertiary containers are for interactive highlights (selected state, feature callout), not default card backgrounds.
filledonPrimary通过surface令牌实现层级结构:
| 卡片样式 | 背景令牌 | 使用场景 |
|---|---|---|
| Flat | | 内联内容,无需分隔 |
| Outlined | | 列表项、设置行 |
| Elevated | | 标准卡片 |
| Filled | | 强调/分组内容 |
内部布局约定:
┌─────────────────────────────────┐
│ [可选媒体/图片] │
├─────────────────────────────────┤
│ 标题 (onSurface) │
│ 副标题 (onSurfaceVariant)│
│ │
│ 正文文本 (onSurface) │
│ │
│ ┌─────────────────────────────┐ │
│ │ 操作区:ghost/outlined按钮 │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────┘dart
DmCard(
style: DmCardStyle.elevated,
child: Column(children: [
Image(...),
Padding(
padding: EdgeInsets.all(16),
child: Column(children: [
Text('Title', style: TextStyle(color: tokens.onSurface)),
Text('Subtitle', style: TextStyle(color: tokens.onSurfaceVariant)),
Row(children: [
DmButton(variant: .ghost, child: Text('Cancel')),
DmButton(variant: .filled, child: Text('Confirm')),
]),
]),
),
]),
)❌ 禁止 为常规内容卡片使用的primary卡片背景搭配文本。Primary/secondary/tertiary容器仅用于交互高亮(选中状态、功能标注),而非默认卡片背景。
filledonPrimaryDmDivider
DmDivider
| Variant | Token | Use case |
|---|---|---|
| Default | | Section separation |
| Strong | | Major section breaks |
| 变体 | 令牌 | 使用场景 |
|---|---|---|
| 默认 | | 区块分隔 |
| 加粗 | | 主要区块分隔 |
DmScaffold
DmScaffold
Responsive layout dispatch:
| Breakpoint | Navigation style |
|---|---|
| Compact (< 600) | |
| Medium (600–1200) | |
| Expanded (> 1200) | |
Page body background: . Rail/side nav background: .
surfacesecondary响应式布局调度:
| 断点 | 导航样式 |
|---|---|
| 紧凑屏(< 600) | |
| 中屏(600–1200) | |
| 扩展屏(> 1200) | |
页面主体背景:。侧边导航背景:。
surfacesecondaryRule 10: Component Design — Data Display (Bricks)
规则10:组件设计 — 数据展示(基础块)
DmBadge
DmBadge
Small status/count indicator. Takes .
DmColorRole| Variant | Background | Foreground | Use case |
|---|---|---|---|
| Filled | role color | onRole | Notification count, status dot |
| Tonal | roleContainer | onRoleContainer | Soft label, category tag |
Default: (notification convention),
color: errorsize: smdart
DmBadge(count: 3) // red notification dot
DmBadge(label: 'New', color: .tertiary, variant: .tonal) // soft accent tag
DmBadge(label: 'Draft', color: .neutral, variant: .tonal) // muted status小型状态/计数指示器。接收参数。
DmColorRole| 变体 | 背景 | 前景 | 使用场景 |
|---|---|---|---|
| Filled | 角色颜色 | onRole | 通知计数、状态点 |
| Tonal | roleContainer | onRoleContainer | 柔和标签、分类标记 |
默认值: (通知约定),
color: errorsize: smdart
DmBadge(count: 3) // 红色通知点
DmBadge(label: 'New', color: .tertiary, variant: .tonal) // 柔和强调标签
DmBadge(label: 'Draft', color: .neutral, variant: .tonal) // 低强调状态DmChip
DmChip
Selectable/filterable labels. Takes .
DmColorRole| State | Background | Foreground | Border |
|---|---|---|---|
| Unselected | | | |
| Selected | | | none |
| Disabled | | | |
Default selection color: — secondary containers are for selection states.
secondary可选/可过滤的标签。接收参数。
DmColorRole| 状态 | 背景 | 前景 | 边框 |
|---|---|---|---|
| 未选中 | | | |
| 选中 | | | 无 |
| 禁用 | | | |
默认选中颜色: — secondary容器用于选中状态。
secondaryDmAvatar
DmAvatar
| Variant | Background | Foreground |
|---|---|---|
| With image | — | — |
| Initials (default) | | |
| Initials (group variety) | Cycle through | Matching |
Sizes follow enum. Default: (40dp diameter).
DmSizemd| 变体 | 背景 | 前景 |
|---|---|---|
| 带图片 | — | — |
| 首字母(默认) | | |
| 首字母(群组变体) | 循环使用 | 对应的onContainer |
尺寸遵循枚举。默认值:(40dp直径)。
DmSizemdDmStat (Data Brick)
DmStat(数据块)
Statistics display block:
┌───────────────┐
│ 1,234 │ ← value: onSurface, large/bold
│ Active Users │ ← label: onSurfaceVariant, small
│ ▲ 12.5% │ ← trend: success or error token
└───────────────┘| Element | Token |
|---|---|
| Value | |
| Label | |
| Positive trend | |
| Negative trend | |
| Card background | |
统计信息展示块:
┌───────────────┐
│ 1,234 │ ← 数值:onSurface,大字号/加粗
│ Active Users │ ← 标签:onSurfaceVariant,小字号
│ ▲ 12.5% │ ← 趋势:success或error令牌
└───────────────┘| 元素 | 令牌 |
|---|---|
| 数值 | |
| 标签 | |
| 正向趋势 | |
| 负向趋势 | |
| 卡片背景 | |
DmTable / Data Grid
DmTable / 数据网格
| Element | Token |
|---|---|
| Header row bg | |
| Header text | |
| Body row bg (even) | |
| Body row bg (odd) | |
| Body text | |
| Row hover | |
| Selected row | |
| Border/grid lines | |
| Sort indicator | |
| 元素 | 令牌 |
|---|---|
| 表头行背景 | |
| 表头文本 | |
| 表体行背景(偶数) | |
| 表体行背景(奇数) | |
| 表体文本 | |
| 行悬停 | |
| 选中行 | |
| 边框/网格线 | |
| 排序指示器 | |
Rule 11: Component Design — Feedback
规则11:组件设计 — 反馈类
DmAlert
DmAlert
| Semantic | Background | Foreground | Icon color |
|---|---|---|---|
| Info | | | |
| Success | | | |
| Warning | | | |
| Error | | | |
Convention: Alerts use semantic container tokens with full-width layout. For inline indicators, use .
DmBadge| 语义类型 | 背景 | 前景 | 图标颜色 |
|---|---|---|---|
| 信息 | | | |
| 成功 | | | |
| 警告 | | | |
| 错误 | | | |
约定: Alert使用语义容器令牌和全宽布局。对于内联指示器,请使用。
DmBadgeDmDialog
DmDialog
| Element | Token |
|---|---|
| Scrim (backdrop) | |
| Dialog surface | |
| Title | |
| Body | |
| Confirm button | |
| Cancel button | |
| Destructive confirm | |
| 元素 | 令牌 |
|---|---|
| 遮罩(背景) | |
| 对话框表面 | |
| 标题 | |
| 正文 | |
| 确认按钮 | |
| 取消按钮 | |
| 破坏性确认按钮 | |
DmSnackbar
DmSnackbar
Uses inverse tokens for contrast against current theme:
| Element | Token |
|---|---|
| Background | |
| Text | |
| Action button | |
使用inverse令牌以在当前主题下保证对比度:
| 元素 | 令牌 |
|---|---|
| 背景 | |
| 文本 | |
| 操作按钮 | |
DmProgress
DmProgress
Linear and circular variants. Default color: .
primary| Variant | Track | Indicator |
|---|---|---|
| Default | | |
| With color role | | role color |
线性和圆形变体。默认颜色:。
primary| 变体 | 轨道 | 指示器 |
|---|---|---|
| 默认 | | |
| 带颜色角色 | | 角色颜色 |
DmSkeleton
DmSkeleton
Loading placeholder. Uses with shimmer animation toward .
surfaceContainerHighsurfaceContainerLow加载占位符。使用并向过渡的闪烁动画。
surfaceContainerHighsurfaceContainerLowRule 12: Component Design — Inputs
规则12:组件设计 — 输入类
DmTextField
DmTextField
| Variant | Idle | Focused | Error |
|---|---|---|---|
| | | |
| | | |
| | | |
| Element | Token |
|---|---|
| Input text | |
| Placeholder/hint | |
| Label (floating) | |
| Helper text | |
| Error text | |
| Prefix/suffix icon | |
Default variant:
outlined| 变体 | 空闲状态 | 聚焦状态 | 错误状态 |
|---|---|---|---|
| | | |
| | | |
| | | |
| 元素 | 令牌 |
|---|---|
| 输入文本 | |
| 占位符/提示文本 | |
| 标签(浮动) | |
| 辅助文本 | |
| 错误文本 | |
| 前缀/后缀图标 | |
默认变体:
outlinedDmCheckbox / DmSwitch / DmSlider
DmCheckbox / DmSwitch / DmSlider
| State | Token |
|---|---|
| Unchecked/off | |
| Checked/on | |
| Track (switch off) | |
| Track (switch on) | |
| Thumb | |
| Slider active track | |
| Slider inactive track | |
| Slider thumb | |
| Disabled | All at 38% opacity |
| 状态 | 令牌 |
|---|---|
| 未选中/关闭 | |
| 选中/开启 | |
| 轨道(开关关闭) | |
| 轨道(开关开启) | |
| 滑块 | |
| 滑块激活轨道 | |
| 滑块未激活轨道 | |
| 滑块手柄 | |
| 禁用 | 所有元素38%透明度 |
Rule 13: Visual Design Principles
规则13:视觉设计原则
Hierarchy through token roles, not through ad-hoc colors
通过令牌角色实现层级,而非自定义颜色
Primary → THE action (one per screen section)
Secondary → supporting actions, selection states
Tertiary → accents, highlights, special callouts
Surface → everything else (backgrounds, text, structure)If you need emphasis, promote the token role — don't invent a color.
Primary → 核心操作(每个屏幕区块一个)
Secondary → 辅助操作、选中状态
Tertiary → 强调、高亮、特殊标注
Surface → 其他所有元素(背景、文本、结构)如果需要强调,提升令牌角色 — 不要自定义颜色。
Density and spacing
密度与间距
DuskMoon follows MD3 density: default padding 16dp, compact 12dp, comfortable 24dp. Widget padding follows the scale.
DmSizeDuskMoon遵循MD3密度:默认内边距16dp,紧凑模式12dp,舒适模式24dp。Widget内边距遵循缩放规则。
DmSizeElevation = surface tokens, not shadows
层级 = surface令牌,而非阴影
Use → for visual hierarchy. Shadows ( token) are supplementary, not the primary depth cue.
surfaceContainerLowestsurfaceContainerHighestshadowdart
// ✅ Surface-token elevation
Container(color: tokens.surfaceContainerHigh) // elevated
Container(color: tokens.surface) // base level
// ❌ Shadow-only elevation
Container(
decoration: BoxDecoration(
color: tokens.surface,
boxShadow: [BoxShadow(blurRadius: 8)], // shadow without surface distinction
),
)使用 → 实现视觉层级。阴影(令牌)仅作为补充,而非主要深度提示。
surfaceContainerLowestsurfaceContainerHighestshadowdart
// ✅ 使用surface令牌实现层级
Container(color: tokens.surfaceContainerHigh) // 高层级
Container(color: tokens.surface) // 基础层级
// ❌ 仅使用阴影实现层级
Container(
decoration: BoxDecoration(
color: tokens.surface,
boxShadow: [BoxShadow(blurRadius: 8)], // 仅用阴影,无surface区分
),
)Dark mode is not "invert everything"
深色模式不是“反转一切”
Each theme has its own curated token set. The codegen produces distinct values per theme. Never compute dark colors by inverting or dimming light colors at runtime.
dart
// ❌ Never compute dark variants
final darkBg = Color.lerp(tokens.surface, Colors.black, 0.3);
// ✅ Use the dark theme's own tokens
DuskmoonApp(tokens: .sunshine, darkTokens: .moonlight) // moonlight has its own curated values每个主题都有自己的精心设计的令牌集合。代码生成器会为每个主题生成不同的值。禁止在运行时通过反转或调暗浅色颜色来计算深色颜色。
dart
// ❌ 禁止计算深色变体
final darkBg = Color.lerp(tokens.surface, Colors.black, 0.3);
// ✅ 使用深色主题自身的令牌
DuskmoonApp(tokens: .sunshine, darkTokens: .moonlight) // moonlight有自己的精心设计的值Rule 14: Package Boundaries
规则14:包边界
(Architecture rules — same as above, renumbered for continuity)
(架构规则 — 与前文一致,重新编号以保持连续性)
What goes where
内容划分
| Package | Contains | Does NOT contain |
|---|---|---|
| | Any widgets, any |
| | Token definitions, theme adapters |
| | Widget implementations |
| Settings UI widgets built on adaptive dispatch | Theme internals |
| Feedback/bug-report widgets | Theme internals |
| Umbrella — re-exports all above | No unique code |
| 包 | 包含内容 | 不包含内容 |
|---|---|---|
| | 任何Widget、任何依赖 |
| | 令牌定义、主题适配器 |
| | Widget实现 |
| 基于自适应调度的设置UI Widget | 主题内部逻辑 |
| 反馈/错误报告Widget | 主题内部逻辑 |
| 聚合包 — 重新导出所有上述包 | 无独有代码 |
❌ NEVER add duskmoon_widgets as dependency of duskmoon_theme
❌ 禁止将duskmoon_widgets作为duskmoon_theme的依赖
This creates a circular dependency. If needs to reference a widget concept, use an abstract interface or callback, not a concrete widget import.
duskmoon_theme这会导致循环依赖。如果需要引用Widget相关概念,请使用抽象接口或回调,而非具体的Widget导入。
duskmoon_themeRule 15: Code Engine Integration
规则15:代码引擎集成
duskmoon_code_engineduskmoon_themeduskmoon_themedart
// In duskmoon_theme — NOT in duskmoon_code_engine
extension DmCodeEngineTheme on DmDesignTokens {
CodeEditorTheme toCodeEditorTheme() => CodeEditorTheme(
background: surface,
foreground: onSurface,
// ...
);
}duskmoon_code_engineduskmoon_themeduskmoon_themedart
// 在duskmoon_theme中 — 不在duskmoon_code_engine中
extension DmCodeEngineTheme on DmDesignTokens {
CodeEditorTheme toCodeEditorTheme() => CodeEditorTheme(
background: surface,
foreground: onSurface,
// ...
);
}Rule 16: Codegen Pipeline
规则16:代码生成流程
duskmoon-dev/design YAML
→ Bun/TypeScript emitter
→ CSS (duskmoonui consumption)
→ TypeScript (duskmoonui/duskmoon-elements)
→ Dart (flutter_duskmoon_ui — committed, CI never needs Bun)
→ JSON (documentation/tooling)Generated Dart files are committed to git. CI must never require Bun or Node to build the Flutter packages.
duskmoon-dev/design YAML
→ Bun/TypeScript生成器
→ CSS(供duskmoonui使用)
→ TypeScript(供duskmoonui/duskmoon-elements使用)
→ Dart(flutter_duskmoon_ui — 已提交到Git,CI无需Bun)
→ JSON(文档/工具)生成的Dart文件已提交到Git。 CI构建Flutter包时绝不需要Bun或Node。
❌ NEVER hand-edit generated files
❌ 禁止手动编辑生成的文件
Files in are produced by codegen. Edit the YAML source in and re-run the pipeline.
packages/duskmoon_theme/lib/src/generated/duskmoon-dev/designpackages/duskmoon_theme/lib/src/generated/duskmoon-dev/designRule 17: Accessibility
规则17:无障碍访问
- All color pairings must meet WCAG 2.1 AA contrast (4.5:1 normal text, 3:1 large text)
- Every interactive Dm* widget must support keyboard navigation
- Semantic labels required on all icon-only buttons
- Focus indicators must be visible on all themes
- 所有颜色配对必须符合WCAG 2.1 AA对比度标准(普通文本4.5:1,大文本3:1)
- 所有交互式Dm* Widget必须支持键盘导航
- 所有仅图标按钮必须设置语义标签
- 在所有主题下,焦点指示器必须可见
Rule 18: Testing Patterns
规则18:测试模式
Widget tests must verify all three platforms
Widget测试必须覆盖所有三个平台
dart
for (final style in DmPlatformStyle.values) {
testWidgets('DmButton renders on $style', (tester) async {
await tester.pumpWidget(
DuskmoonApp(
tokens: DmDesignTokens.sunshine,
platformStyle: style,
home: const DmButton(label: 'Test'),
),
);
expect(find.text('Test'), findsOneWidget);
});
}dart
for (final style in DmPlatformStyle.values) {
testWidgets('DmButton renders on $style', (tester) async {
await tester.pumpWidget(
DuskmoonApp(
tokens: DmDesignTokens.sunshine,
platformStyle: style,
home: const DmButton(label: 'Test'),
),
);
expect(find.text('Test'), findsOneWidget);
});
}Theme tests must verify token resolution
主题测试必须验证令牌解析
dart
testWidgets('DmTheme.of resolves correct tokens', (tester) async {
late DmDesignTokens resolved;
await tester.pumpWidget(
DuskmoonApp(
tokens: DmDesignTokens.sunshine,
home: Builder(builder: (context) {
resolved = DmTheme.of(context).tokens;
return const SizedBox();
}),
),
);
expect(resolved.primary, equals(DmDesignTokens.sunshine.primary));
});dart
testWidgets('DmTheme.of resolves correct tokens', (tester) async {
late DmDesignTokens resolved;
await tester.pumpWidget(
DuskmoonApp(
tokens: DmDesignTokens.sunshine,
home: Builder(builder: (context) {
resolved = DmTheme.of(context).tokens;
return const SizedBox();
}),
),
);
expect(resolved.primary, equals(DmDesignTokens.sunshine.primary));
});Quick Reference: Anti-Patterns
速查:反模式
| ❌ Don't | ✅ Do |
|---|---|
| |
| |
| |
| Extend |
Hand-edit | Edit YAML source, re-run codegen |
Import | Keep dependency direction strict |
Put theme adapter in | Extension method in |
| Pair |
AppBar background = | AppBar background = |
Drawer/side menu bg = | Drawer/side menu bg = |
Card default bg = | Card bg = |
| Shadows as primary depth cue | Surface container tokens for elevation hierarchy |
| Use the dark theme's own curated tokens |
| Multiple FABs on one screen | One FAB max; use |
Icon button without | Always provide |
Selection highlight with | Selection states use |
| Inventing colors outside the token system | Promote token role (primary→secondary→tertiary) |
| ❌ 禁止做法 | ✅ 正确做法 |
|---|---|
使用 | 使用 |
在Widget构建中使用 | 使用 |
使用 | 使用 |
使用 | 继承 |
手动编辑 | 编辑YAML源文件,重新运行代码生成 |
从 | 严格保持依赖方向 |
将主题适配器放在 | 在 |
使用 | 配对使用 |
AppBar背景使用 | AppBar背景使用 |
Drawer/侧边菜单背景使用 | Drawer/侧边菜单背景使用 |
卡片默认背景使用 | 卡片背景使用 |
| 仅使用阴影作为主要深度提示 | 使用surface容器令牌实现层级结构 |
使用 | 使用深色主题自身的精心设计的令牌 |
| 一个屏幕上使用多个FAB | 最多一个FAB;使用 |
图标按钮不设置 | 始终为 |
使用 | 使用 |
| 在令牌系统外自定义颜色 | 提升令牌角色(primary→secondary→tertiary) |
Checklist for Code Review
代码审查检查清单
When reviewing Flutter code that uses DuskMoon UI, verify:
Architecture:
- App root is , not
DuskmoonApp/MaterialAppdirectlyCupertinoApp - Package imports flow downstream only (theme → widgets → settings)
- No generated files were hand-edited
- No dependency in
duskmoon_themeduskmoon_code_engine - Adaptive widgets extend , not manual platform checks
DmAdaptiveWidget
Color & Tokens:
- All color values come from , not hardcoded
DmTheme.of(context).tokens - Background/foreground token pairs match (primary↔onPrimary, etc.)
- No or
Colors.*literalsColor(0x...) - Dark mode uses separate theme tokens, no runtime color computation
Component Design:
- Buttons use convention (primary=main CTA, error=destructive, etc.)
DmColorRole - AppBar and BottomNav use /
primarydefaultsonPrimary - Drawer and side menu use /
secondarydefaultsonSecondary - Cards use surface container tokens, not primary/secondary containers for default bg
- Selection states use tokens
secondaryContainer - Surface elevation via container tokens, not shadow-only
- Maximum one FAB per screen section
- for multiple actions, not ad-hoc button rows
DmActionList - Snackbar uses inverse tokens
- Dialog scrim uses token with alpha
scrim
Accessibility:
- Semantic labels on all icon-only buttons
- Focus indicators visible on all themes
- Widget tests cover all three values
DmPlatformStyle - WCAG AA contrast on all bg/fg pairings
审查使用DuskMoon UI的Flutter代码时,请验证:
架构:
- 应用根节点是,而非直接使用
DuskmoonApp/MaterialAppCupertinoApp - 包导入仅向下游流动(theme → widgets → settings)
- 没有手动编辑生成的文件
- 中没有
duskmoon_code_engine依赖duskmoon_theme - 自适应Widget继承自,而非手动判断平台
DmAdaptiveWidget
颜色与令牌:
- 所有颜色值均来自,而非硬编码
DmTheme.of(context).tokens - 背景/前景令牌配对正确(primary↔onPrimary等)
- 没有或
Colors.*字面量Color(0x...) - 深色模式使用独立的主题令牌,无运行时颜色计算
组件设计:
- 按钮遵循约定(primary=主要CTA,error=破坏性操作等)
DmColorRole - AppBar和BottomNav使用/
primary默认值onPrimary - Drawer和侧边菜单使用/
secondary默认值onSecondary - 卡片使用surface容器令牌,默认背景不使用primary/secondary容器
- 选中状态使用令牌
secondaryContainer - 使用容器令牌实现Surface层级,而非仅依赖阴影
- 每个屏幕区块最多一个FAB
- 使用处理多个操作,而非自定义按钮行
DmActionList - Snackbar使用inverse令牌
- Dialog遮罩使用带透明度的令牌
scrim
无障碍访问:
- 所有仅图标按钮都有语义标签
- 在所有主题下焦点指示器可见
- Widget测试覆盖所有三个值
DmPlatformStyle - 所有背景/前景配对符合WCAG AA对比度标准