design-audit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Design Audit

设计审核

The final checkpoint. Loaded by
/genjutsu:paint
at the end of the pipeline. Scans the codebase for motion gaps, a11y violations, perf issues, and inconsistencies.

最终检查点。在流程末尾通过
/genjutsu:paint
加载。 扫描代码库,检查动效缺失、无障碍访问(a11y)违规、性能问题以及设计不一致情况。

Motion Gap Analysis

动效缺失分析

Run these greps against the project to detect missing animations.
运行以下grep命令检测项目中缺失的动画。

Conditional renders without AnimatePresence

未使用AnimatePresence的条件渲染

bash
grep -rn '{.*&&\s*<\|{.*?\s*:\s*<\|{.*ternary.*<' --include='*.tsx' --include='*.jsx' src/ | grep -v 'AnimatePresence'
Look for:
{show && <Component />}
or ternary renders without a wrapping
<AnimatePresence>
. Every conditional mount/unmount needs exit animation support.
bash
grep -rn '{.*&&\s*<\|{.*?\s*:\s*<\|{.*ternary.*<' --include='*.tsx' --include='*.jsx' src/ | grep -v 'AnimatePresence'
查找类似
{show && <Component />}
或未包裹
<AnimatePresence>
的三元渲染。所有条件挂载/卸载都需要支持退出动画。

Hover states without transition

未添加过渡效果的悬停状态

bash
grep -rn ':hover' --include='*.css' --include='*.scss' --include='*.module.css' src/ | grep -vE 'transition|animation'
Every
:hover
rule must have a corresponding
transition
on the base selector. Instant state flips feel broken.
bash
grep -rn ':hover' --include='*.css' --include='*.scss' --include='*.module.css' src/ | grep -vE 'transition|animation'
每个
:hover
规则必须在基础选择器上对应添加
transition
。状态瞬间切换会让用户体验不佳。

Dynamic lists without stagger

未添加 stagger 效果的动态列表

bash
grep -rn '\.map(' --include='*.tsx' --include='*.jsx' src/ | grep -vE 'stagger|delay.*index|variants|transition.*delay'
Lists rendered via
.map()
should stagger their entrance. Simultaneous pop-in looks cheap.
bash
grep -rn '\.map(' --include='*.tsx' --include='*.jsx' src/ | grep -vE 'stagger|delay.*index|variants|transition.*delay'
通过
.map()
渲染的列表应该添加入场 stagger 效果。所有元素同时弹出会显得廉价。

Style changes without transition

未添加过渡效果的样式变更

bash
grep -rn 'style={{' --include='*.tsx' --include='*.jsx' src/ | grep -vE 'transition|transform|opacity'
Inline style changes (e.g., dynamic background, color) need a CSS transition or motion wrapper.
bash
grep -rn 'style={{' --include='*.tsx' --include='*.jsx' src/ | grep -vE 'transition|transform|opacity'
内联样式变更(例如动态背景、颜色)需要添加CSS过渡效果或动效包装器。

Entries without corresponding exits

仅设置入场动画未设置对应退场动画

bash
grep -rn 'initial=' --include='*.tsx' --include='*.jsx' src/ | grep -v 'exit='
Every Framer Motion
initial
+
animate
should have an
exit
prop when inside
AnimatePresence
.

bash
grep -rn 'initial=' --include='*.tsx' --include='*.jsx' src/ | grep -v 'exit='
AnimatePresence
内部,每个Framer Motion的
initial
+
animate
都应该对应设置
exit
属性。

Accessibility Audit

无障碍访问审核

Reduced motion - MANDATORY

减少动效 - 强制要求

A project with animation MUST have at least one global handler matching its stack. Run all 3 greps:
bash
undefined
包含动画的项目必须至少有一个匹配其技术栈的全局处理器。运行以下3条grep命令:
bash
undefined

Web

Web

grep -rn 'prefers-reduced-motion' --include='.css' --include='.scss' --include='.ts' --include='.tsx' --include='.js' --include='.jsx' src/ 2>/dev/null
grep -rn 'prefers-reduced-motion' --include='.css' --include='.scss' --include='.ts' --include='.tsx' --include='.js' --include='.jsx' src/ 2>/dev/null

SwiftUI / UIKit

SwiftUI / UIKit

grep -rn 'accessibilityReduceMotion|isReduceMotionEnabled|reduceMotionStatusDidChangeNotification' --include='*.swift' . 2>/dev/null
grep -rn 'accessibilityReduceMotion|isReduceMotionEnabled|reduceMotionStatusDidChangeNotification' --include='*.swift' . 2>/dev/null

Compose

Compose

grep -rn 'LocalAccessibilityManager|isReduceTransitions|TRANSITION_ANIMATION_SCALE|ANIMATOR_DURATION_SCALE|areAnimatorsEnabled' --include='*.kt' . 2>/dev/null

**Zero results across all 3 in an animated project = critical violation.** At least one handler must exist somewhere. Cross-reference `motion-principles/SKILL.md` for canonical implementations per stack.
grep -rn 'LocalAccessibilityManager|isReduceTransitions|TRANSITION_ANIMATION_SCALE|ANIMATOR_DURATION_SCALE|areAnimatorsEnabled' --include='*.kt' . 2>/dev/null

**如果包含动画的项目在所有3条命令中都无结果 = 严重违规。** 必须在某个位置存在至少一个处理器。参考`motion-principles/SKILL.md`获取各技术栈的标准实现方案。

Contrast ratio 4.5:1

对比度4.5:1

  • Use browser DevTools (Inspect > color swatch > contrast ratio)
  • Run
    npx pa11y <url>
    or Lighthouse accessibility audit
  • Check animated text at mid-transition -- fading text must remain readable at every opacity above 0.4
  • 使用浏览器开发者工具(检查 > 颜色样本 > 对比度)
  • 运行
    npx pa11y <url>
    或Lighthouse无障碍访问审核
  • 检查过渡中的动画文本 -- 淡入淡出的文本在不透明度高于0.4的任何时刻都必须保持可读

Focus visible on all interactives

所有交互元素可见焦点

bash
grep -rn 'outline:\s*none\|outline:\s*0' --include='*.css' --include='*.scss' --include='*.module.css' src/
Any
outline: none
MUST be paired with a custom
:focus-visible
style. Removing focus rings without replacement is a WCAG failure.
bash
grep -rn 'outline:\s*none\|outline:\s*0' --include='*.css' --include='*.scss' --include='*.module.css' src/
任何
outline: none
必须搭配自定义
:focus-visible
样式。移除焦点环而不替换属于WCAG违规。

Semantic HTML -- no clickable divs

语义化HTML -- 禁止可点击div

bash
grep -rn 'onClick' --include='*.tsx' --include='*.jsx' src/ | grep -E '<div|<span' | grep -v 'role='
Every
<div onClick>
or
<span onClick>
must either be a
<button>
, an
<a>
, or have
role="button"
+
tabIndex
+
onKeyDown
.
bash
grep -rn 'onClick' --include='*.tsx' --include='*.jsx' src/ | grep -E '<div|<span' | grep -v 'role='
每个
<div onClick>
<span onClick>
必须替换为
<button>
<a>
,或者添加
role="button"
+
tabIndex
+
onKeyDown

ARIA on decorative animations

装饰性动画的ARIA设置

bash
grep -rn '<motion\.\|<animated\.\|<Lottie\|<Canvas' --include='*.tsx' --include='*.jsx' src/ | grep -v 'aria-hidden'
Purely decorative animations (background particles, ambient motion, Lottie illustrations) must have
aria-hidden="true"
to avoid polluting screen readers.

bash
grep -rn '<motion\.\|<animated\.\|<Lottie\|<Canvas' --include='*.tsx' --include='*.jsx' src/ | grep -v 'aria-hidden'
纯装饰性动画(背景粒子、环境动效、Lottie插画)必须添加
aria-hidden="true"
,避免干扰屏幕阅读器。

Performance Audit

性能审核

Layout thrashing -- animating layout properties

布局抖动 -- 动画布局属性

bash
grep -rn 'transition.*\(width\|height\|top\|left\|right\|bottom\|margin\|padding\)' --include='*.css' --include='*.scss' --include='*.module.css' src/
Animating layout properties triggers reflow every frame. Replace with
transform: translate/scale
and
opacity
.
bash
grep -rn 'transition.*\(width\|height\|top\|left\|right\|bottom\|margin\|padding\)' --include='*.css' --include='*.scss' --include='*.module.css' src/
为布局属性添加动画会触发每帧重排。替换为
transform: translate/scale
opacity

Excessive paint triggers

过度触发绘制

bash
grep -rn 'will-change' --include='*.css' --include='*.scss' --include='*.module.css' src/
will-change
should be rare and scoped. If more than ~5 elements use it permanently, the GPU memory cost outweighs the benefit. Apply it dynamically (add on hover/focus, remove on animation end).
bash
grep -rn 'will-change' --include='*.css' --include='*.scss' --include='*.module.css' src/
will-change
应该少用且作用域明确。如果超过约5个元素永久使用它,GPU内存成本将超过收益。应动态应用(在悬停/聚焦时添加,动画结束时移除)。

Animation library bundle cost

动画库包体积成本

Check actual impact:
bash
npx source-map-explorer dist/**/*.js 2>/dev/null || npx vite-bundle-visualizer 2>/dev/null
Reference sizes (gzipped): framer-motion ~30KB, GSAP ~25KB, popmotion ~5KB, CSS-only = 0KB. If the project only uses fade+slide, a 30KB lib is overkill.
On mobile native, the APK / IPA size matters. A third-party animation library adds typically 500KB-2MB:
LibrarySize impact (uncompressed)
Lottie (iOS)~600KB
Lottie (Android)~900KB
Rive (iOS)~1.5MB
Rive (Android)~2MB
Native Compose / SwiftUI animations0KB (built-in)
If the project only uses fades, slides, and springs, native APIs (Compose
animate*AsState
+ spring, SwiftUI
withAnimation
) are sufficient. Justify a Lottie / Rive dependency only for genuinely complex pre-designed animations (e.g., onboarding illustrations).
检查实际影响:
bash
npx source-map-explorer dist/**/*.js 2>/dev/null || npx vite-bundle-visualizer 2>/dev/null
参考体积(gzip压缩后):framer-motion ~30KB,GSAP ~25KB,popmotion ~5KB,纯CSS = 0KB。如果项目仅使用淡入淡出+滑动效果,30KB的库就过于冗余了。
在移动端原生开发中,APK/IPA体积很重要。第三方动画库通常会增加500KB-2MB体积:
体积影响(未压缩)
Lottie (iOS)~600KB
Lottie (Android)~900KB
Rive (iOS)~1.5MB
Rive (Android)~2MB
原生Compose / SwiftUI动画0KB(内置)
如果项目仅使用淡入淡出、滑动和弹簧效果,原生API(Compose
animate*AsState
+ spring,SwiftUI
withAnimation
)就足够了。只有在处理真正复杂的预设计动画(例如引导页插画)时,才需要引入Lottie/Rive依赖。

requestAnimationFrame vs setTimeout

requestAnimationFrame vs setTimeout

bash
grep -rn 'setTimeout\|setInterval' --include='*.ts' --include='*.tsx' --include='*.js' --include='*.jsx' src/ | grep -iE 'anim\|motion\|scroll\|position\|style\|transform'
Animation loops must use
requestAnimationFrame
.
setTimeout
/
setInterval
causes frame drops and doesn't pause in background tabs.

bash
grep -rn 'setTimeout\|setInterval' --include='*.ts' --include='*.tsx' --include='*.js' --include='*.jsx' src/ | grep -iE 'anim\|motion\|scroll\|position\|style\|transform'
动画循环必须使用
requestAnimationFrame
setTimeout
/
setInterval
会导致丢帧,且在后台标签页中不会暂停。

Consistency Audit

一致性审核

Duration consistency

时长一致性

bash
grep -rnoE 'duration[:"'\''= ]+[0-9.]+' --include='*.tsx' --include='*.jsx' --include='*.ts' --include='*.css' --include='*.scss' src/ | sort -t: -k3 | uniq -c -f2 | sort -rn
A well-designed project uses 3-5 distinct durations max (e.g., 0.15, 0.25, 0.35, 0.5). If you see 15 different values, extract them into a motion tokens file.
bash
grep -rnoE 'duration[:"'\''= ]+[0-9.]+' --include='*.tsx' --include='*.jsx' --include='*.ts' --include='*.css' --include='*.scss' src/ | sort -t: -k3 | uniq -c -f2 | sort -rn
设计良好的项目最多使用3-5种不同的动画时长(例如0.15、0.25、0.35、0.5)。如果发现15种不同的值,应将它们提取到动效令牌文件中。

Easing inventory

缓动效果盘点

Run all 3 greps to inventory easing values across the codebase:
bash
undefined
运行以下3条grep命令盘点代码库中的缓动值:
bash
undefined

Web (CSS / JS / TSX)

Web (CSS / JS / TSX)

grep -rnoE 'ease[A-Za-z]|cubic-bezier([^)]+)|spring([^)])' --include='.tsx' --include='.jsx' --include='.ts' --include='.css' --include='*.scss' src/ 2>/dev/null
grep -rnoE 'ease[A-Za-z]|cubic-bezier([^)]+)|spring([^)])' --include='.tsx' --include='.jsx' --include='.ts' --include='.css' --include='*.scss' src/ 2>/dev/null

SwiftUI

SwiftUI

grep -rnoE '.spring([^)])|.snappy|.bouncy|.smooth|.linear(|.easeIn|.easeOut|.interpolatingSpring' --include='.swift' . 2>/dev/null
grep -rnoE '.spring([^)])|.snappy|.bouncy|.smooth|.linear(|.easeIn|.easeOut|.interpolatingSpring' --include='.swift' . 2>/dev/null

Compose

Compose

grep -rnoE 'spring([^)])|tween([^)])|FastOutSlowInEasing|LinearOutSlowInEasing|FastOutLinearInEasing|CubicBezierEasing' --include='*.kt' . 2>/dev/null

Same rule across all stacks: 3-5 named easings max in the design system. Random values scattered across files = visual inconsistency. If the codebase has 12 different `cubic-bezier(...)` values or 8 custom `spring(response:dampingFraction:)` configurations, that's a design-system violation, fix it by centralizing into named tokens.
grep -rnoE 'spring([^)])|tween([^)])|FastOutSlowInEasing|LinearOutSlowInEasing|FastOutLinearInEasing|CubicBezierEasing' --include='*.kt' . 2>/dev/null

所有技术栈遵循相同规则:设计系统中最多使用3-5种命名缓动效果。随机值分散在各个文件中会导致视觉不一致。如果代码库中有12种不同的`cubic-bezier(...)`值或8种自定义`spring(response:dampingFraction:)`配置,这属于设计系统违规,应通过集中到命名令牌中来修复。

Symmetric enter/exit

对称的入场/退场动画

Scan for motion components and verify that:
  • Enter duration >= exit duration (never the reverse)
  • Enter uses
    ease-out
    , exit uses
    ease-in
  • Enter has full choreography (translate + opacity + scale), exit is simpler (opacity only or opacity + slight scale)
bash
grep -A5 'exit=' --include='*.tsx' --include='*.jsx' -rn src/
Compare
animate
and
exit
props side by side. Asymmetric timing (fast exit, slow enter) is correct. The reverse is wrong.

扫描动效组件并验证:
  • 入场时长 >= 退场时长(绝不能反过来)
  • 入场使用
    ease-out
    ,退场使用
    ease-in
  • 入场包含完整的编排(位移 + 透明度 + 缩放),退场更简洁(仅透明度或透明度+轻微缩放)
bash
grep -A5 'exit=' --include='*.tsx' --include='*.jsx' -rn src/
对比
animate
exit
属性。非对称时序(快速退场、缓慢入场)是正确的,反过来则错误。

Stack-specific audit

技术栈专属审核

Pick the subsection matching the project stack.
选择与项目技术栈匹配的小节。

Compose (Android / Multiplatform)

Compose(Android / 跨平台)

  • Run Layout Inspector (Android Studio): inspect recompositions, identify components recomposing on every state change.
  • Run Macrobenchmark (
    androidx.benchmark.macro
    ): measure frame timing on a real device under representative scrolling / animation load. Target: <16.67ms per frame at 60fps, <8.33ms at 120fps.
  • Inspect recomposition counts via
    Modifier.recomposeHighlighter()
    (Compose 1.6+) or Layout Inspector.
  • Generate Baseline Profiles (
    BaselineProfileGenerator
    ) for production builds.
  • Verify
    Modifier.semantics
    is set on custom components (TalkBack support).
  • 运行Layout Inspector(Android Studio):检查重组情况,识别每次状态变更都会重组的组件。
  • 运行Macrobenchmark
    androidx.benchmark.macro
    ):在真实设备上测量代表性滚动/动画负载下的帧时序。目标:60fps时每帧<16.67ms,120fps时每帧<8.33ms。
  • 通过
    Modifier.recomposeHighlighter()
    (Compose 1.6+)或Layout Inspector检查重组次数
  • 为生产构建生成Baseline Profiles
    BaselineProfileGenerator
    )。
  • 验证自定义组件上是否设置了
    Modifier.semantics
    (支持TalkBack)。

SwiftUI (iOS / macOS)

SwiftUI(iOS / macOS)

  • Run Instruments Time Profiler: identify hot paths during animation (target: zero frames over 16.67ms).
  • Run Instruments Hitches Instrument (iOS 14+): detects dropped frames and stalls.
  • Run Instruments GPU Frame Capture for Metal shaders: verify shader compile time, GPU vs CPU bottleneck.
  • Verify
    .accessibilityLabel
    /
    .accessibilityHint
    on every interactive view.
  • Test with Dynamic Type at 200% size (
    Environment Overrides
    in Xcode).
  • Test with Reduce Motion ON.
  • 运行Instruments Time Profiler:识别动画期间的热点路径(目标:无帧超过16.67ms)。
  • 运行Instruments Hitches Instrument(iOS 14+):检测丢帧和卡顿。
  • 为Metal着色器运行Instruments GPU Frame Capture:验证着色器编译时间、GPU与CPU瓶颈。
  • 验证每个交互视图上是否设置了
    .accessibilityLabel
    /
    .accessibilityHint
  • 在200%动态字体大小下测试(Xcode中的
    Environment Overrides
    )。
  • 在“减少动效”开启状态下测试。

Web

Web

  • Existing checklist above (Lighthouse, Chrome DevTools Performance, etc.).

  • 使用上述已有检查清单(Lighthouse、Chrome DevTools性能分析等)。

Output Format

输出格式

Structure findings by severity:
按严重程度整理发现的问题:

Critical (must fix before ship)

严重(发布前必须修复)

  • Missing
    prefers-reduced-motion
    handling
  • Clickable divs without keyboard support
  • outline: none
    without
    :focus-visible
    replacement
  • Animating layout properties (width/height/top/left)
  • 缺失
    prefers-reduced-motion
    处理
  • 可点击div无键盘支持
  • outline: none
    未搭配
    :focus-visible
    替代样式
  • 为布局属性(width/height/top/left)添加动画

Important (fix in current sprint)

重要(当前迭代修复)

  • Conditional renders without
    AnimatePresence
  • Hover states without transition
  • Missing
    aria-hidden
    on decorative animations
  • setTimeout
    used for animation loops
  • Inconsistent durations (>8 unique values)
  • 条件渲染未使用
    AnimatePresence
  • 悬停状态未添加过渡效果
  • 装饰性动画未设置
    aria-hidden
  • 使用
    setTimeout
    实现动画循环
  • 动画时长不一致(超过8种唯一值)

Nice-to-have (backlog)

优化项(放入待办)

  • Lists without stagger animation
  • Inline styles without transition
  • Excessive
    will-change
    usage
  • Asymmetric enter/exit (wrong direction)
  • Animation library oversized for actual usage
  • 列表未添加stagger动画
  • 内联样式未添加过渡效果
  • 过度使用
    will-change
  • 入场/退场动画不对称(方向错误)
  • 动画库体积远超实际需求