[{"data":1,"prerenderedAt":1589},["ShallowReactive",2],{"article-/topics/design/gesture-interaction-design-specification":3,"related-design":373,"content-query-wKf4A3DJla":1315},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"topic":5,"author":11,"tags":12,"image":18,"imageQuery":19,"pexelsPhotoId":20,"pexelsUrl":21,"featured":6,"readingTime":22,"body":23,"_type":367,"_id":368,"_source":369,"_file":370,"_stem":371,"_extension":372},"/topics/design/gesture-interaction-design-specification","design",false,"","手势交互设计规范：从触发模型到反馈节奏的系统设计方法","手势交互的难点不是做出滑动或拖拽，而是让触发逻辑、反馈时机和误触控制保持一致。本文从移动端与复杂界面场景出发，讲清手势交互的设计规范。","2026-04-23","HTMLPAGE 团队",[13,14,15,16,17],"Gesture Design","Interaction Design","UX","Mobile UI","Design System","/images/topics/design/gesture-interaction-design-specification.jpg","mobile app interaction design prototype hand gestures",6941314,"https://www.pexels.com/photo/person-s-hand-playing-music-on-a-cell-phone-6941314/",14,{"type":24,"children":25,"toc":356},"root",[26,34,39,64,69,75,80,85,108,113,118,123,128,146,151,156,161,166,189,194,200,205,210,215,238,244,249,272,277,282,310,315,320,325],{"type":27,"tag":28,"props":29,"children":30},"element","p",{},[31],{"type":32,"value":33},"text","手势交互最容易被误解成“多做一点酷炫动作”。",{"type":27,"tag":28,"props":35,"children":36},{},[37],{"type":32,"value":38},"但真正落到产品里，手势设计的核心问题通常是：",{"type":27,"tag":40,"props":41,"children":42},"ul",{},[43,49,54,59],{"type":27,"tag":44,"props":45,"children":46},"li",{},[47],{"type":32,"value":48},"用户能不能预期这个动作会发生什么",{"type":27,"tag":44,"props":50,"children":51},{},[52],{"type":32,"value":53},"会不会频繁误触",{"type":27,"tag":44,"props":55,"children":56},{},[57],{"type":32,"value":58},"多种手势冲突时谁优先",{"type":27,"tag":44,"props":60,"children":61},{},[62],{"type":32,"value":63},"反馈是否足够及时和清晰",{"type":27,"tag":28,"props":65,"children":66},{},[67],{"type":32,"value":68},"所以手势交互设计规范的重点，不是动作本身，而是让触发、反馈和恢复都足够可预测。",{"type":27,"tag":70,"props":71,"children":73},"h2",{"id":72},"手势设计先解决的是可发现性",[74],{"type":32,"value":72},{"type":27,"tag":28,"props":76,"children":77},{},[78],{"type":32,"value":79},"很多手势设计体验差，不是实现不够流畅，而是用户根本不知道这里能操作。",{"type":27,"tag":28,"props":81,"children":82},{},[83],{"type":32,"value":84},"因此手势能力通常需要至少一种可发现信号：",{"type":27,"tag":40,"props":86,"children":87},{},[88,93,98,103],{"type":27,"tag":44,"props":89,"children":90},{},[91],{"type":32,"value":92},"视觉暗示",{"type":27,"tag":44,"props":94,"children":95},{},[96],{"type":32,"value":97},"首次引导",{"type":27,"tag":44,"props":99,"children":100},{},[101],{"type":32,"value":102},"结构化提示",{"type":27,"tag":44,"props":104,"children":105},{},[106],{"type":32,"value":107},"可替代操作入口",{"type":27,"tag":28,"props":109,"children":110},{},[111],{"type":32,"value":112},"如果一个关键动作只能靠隐藏手势触发，产品可用性通常会明显下降。",{"type":27,"tag":70,"props":114,"children":116},{"id":115},"同一界面里的手势优先级必须明确",[117],{"type":32,"value":115},{"type":27,"tag":28,"props":119,"children":120},{},[121],{"type":32,"value":122},"复杂界面里最容易出问题的，是手势冲突。",{"type":27,"tag":28,"props":124,"children":125},{},[126],{"type":32,"value":127},"比如：",{"type":27,"tag":40,"props":129,"children":130},{},[131,136,141],{"type":27,"tag":44,"props":132,"children":133},{},[134],{"type":32,"value":135},"横向滑动与纵向滚动冲突",{"type":27,"tag":44,"props":137,"children":138},{},[139],{"type":32,"value":140},"拖拽与点击冲突",{"type":27,"tag":44,"props":142,"children":143},{},[144],{"type":32,"value":145},"长按与普通点击冲突",{"type":27,"tag":28,"props":147,"children":148},{},[149],{"type":32,"value":150},"这类问题不能靠“感觉调一调”解决，必须先定义优先级规则和触发阈值。",{"type":27,"tag":70,"props":152,"children":154},{"id":153},"反馈节奏决定用户是否信任手势",[155],{"type":32,"value":153},{"type":27,"tag":28,"props":157,"children":158},{},[159],{"type":32,"value":160},"手势交互一旦缺少反馈，用户会迅速失去信心。",{"type":27,"tag":28,"props":162,"children":163},{},[164],{"type":32,"value":165},"更稳的反馈通常至少包含：",{"type":27,"tag":40,"props":167,"children":168},{},[169,174,179,184],{"type":27,"tag":44,"props":170,"children":171},{},[172],{"type":32,"value":173},"触发前预感知",{"type":27,"tag":44,"props":175,"children":176},{},[177],{"type":32,"value":178},"触发中状态变化",{"type":27,"tag":44,"props":180,"children":181},{},[182],{"type":32,"value":183},"完成后结果确认",{"type":27,"tag":44,"props":185,"children":186},{},[187],{"type":32,"value":188},"失败后的恢复路径",{"type":27,"tag":28,"props":190,"children":191},{},[192],{"type":32,"value":193},"没有这些，手势虽然能用，但不会让人愿意继续用。",{"type":27,"tag":70,"props":195,"children":197},{"id":196},"误触控制比多做手势更重要",[198],{"type":32,"value":199},"误触控制比“多做手势”更重要",{"type":27,"tag":28,"props":201,"children":202},{},[203],{"type":32,"value":204},"很多团队设计手势时，关注点都在功能丰富度。",{"type":27,"tag":28,"props":206,"children":207},{},[208],{"type":32,"value":209},"但实际体验里，误触成本往往比功能数更关键。尤其在移动端和高密度界面中，任何高频误触都会迅速放大挫败感。",{"type":27,"tag":28,"props":211,"children":212},{},[213],{"type":32,"value":214},"因此规范里通常要明确：",{"type":27,"tag":40,"props":216,"children":217},{},[218,223,228,233],{"type":27,"tag":44,"props":219,"children":220},{},[221],{"type":32,"value":222},"触发阈值",{"type":27,"tag":44,"props":224,"children":225},{},[226],{"type":32,"value":227},"取消条件",{"type":27,"tag":44,"props":229,"children":230},{},[231],{"type":32,"value":232},"回弹与撤销",{"type":27,"tag":44,"props":234,"children":235},{},[236],{"type":32,"value":237},"危险动作的二次确认",{"type":27,"tag":70,"props":239,"children":241},{"id":240},"一个常见失败案例手势很丰富但用户几乎不敢用",[242],{"type":32,"value":243},"一个常见失败案例：手势很丰富，但用户几乎不敢用",{"type":27,"tag":28,"props":245,"children":246},{},[247],{"type":32,"value":248},"这种情况通常说明：",{"type":27,"tag":40,"props":250,"children":251},{},[252,257,262,267],{"type":27,"tag":44,"props":253,"children":254},{},[255],{"type":32,"value":256},"可发现性差",{"type":27,"tag":44,"props":258,"children":259},{},[260],{"type":32,"value":261},"误触率高",{"type":27,"tag":44,"props":263,"children":264},{},[265],{"type":32,"value":266},"反馈不稳定",{"type":27,"tag":44,"props":268,"children":269},{},[270],{"type":32,"value":271},"缺少可恢复路径",{"type":27,"tag":28,"props":273,"children":274},{},[275],{"type":32,"value":276},"问题不在于动作不够高级，而在于系统没有建立用户信任。",{"type":27,"tag":70,"props":278,"children":280},{"id":279},"一份可直接复用的检查清单",[281],{"type":32,"value":279},{"type":27,"tag":40,"props":283,"children":284},{},[285,290,295,300,305],{"type":27,"tag":44,"props":286,"children":287},{},[288],{"type":32,"value":289},"关键手势是否有可发现提示和替代入口",{"type":27,"tag":44,"props":291,"children":292},{},[293],{"type":32,"value":294},"同一界面中的手势优先级与阈值是否清晰",{"type":27,"tag":44,"props":296,"children":297},{},[298],{"type":32,"value":299},"触发前、中、后是否都有稳定反馈",{"type":27,"tag":44,"props":301,"children":302},{},[303],{"type":32,"value":304},"是否为误触和危险动作提供了取消与恢复机制",{"type":27,"tag":44,"props":306,"children":307},{},[308],{"type":32,"value":309},"规范是否能跨页面和跨组件稳定复用",{"type":27,"tag":70,"props":311,"children":313},{"id":312},"总结",[314],{"type":32,"value":312},{"type":27,"tag":28,"props":316,"children":317},{},[318],{"type":32,"value":319},"手势交互设计规范的核心，不是支持更多动作，而是让每个动作都可发现、可理解、可恢复。只要先把优先级、反馈和误触控制做好，手势能力才会真正提升体验。",{"type":27,"tag":28,"props":321,"children":322},{},[323],{"type":32,"value":324},"进一步阅读：",{"type":27,"tag":40,"props":326,"children":327},{},[328,338,347],{"type":27,"tag":44,"props":329,"children":330},{},[331],{"type":27,"tag":332,"props":333,"children":335},"a",{"href":334},"/topics/design/complex-interaction-component-guide",[336],{"type":32,"value":337},"复杂交互组件方案",{"type":27,"tag":44,"props":339,"children":340},{},[341],{"type":27,"tag":332,"props":342,"children":344},{"href":343},"/topics/design/modal-overlay-design-specification",[345],{"type":32,"value":346},"模态与弹层设计规范",{"type":27,"tag":44,"props":348,"children":349},{},[350],{"type":27,"tag":332,"props":351,"children":353},{"href":352},"/topics/design/loading-state-design-best-practices",[354],{"type":32,"value":355},"加载状态设计最佳实践",{"title":7,"searchDepth":357,"depth":357,"links":358},3,[359,361,362,363,364,365,366],{"id":72,"depth":360,"text":72},2,{"id":115,"depth":360,"text":115},{"id":153,"depth":360,"text":153},{"id":196,"depth":360,"text":199},{"id":240,"depth":360,"text":243},{"id":279,"depth":360,"text":279},{"id":312,"depth":360,"text":312},"markdown","content:topics:design:gesture-interaction-design-specification.md","content","topics/design/gesture-interaction-design-specification.md","topics/design/gesture-interaction-design-specification","md",[374,734,1036],{"_path":375,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":376,"description":377,"keywords":378,"image":384,"author":11,"date":385,"readingTime":386,"topic":5,"body":387,"_type":367,"_id":731,"_source":369,"_file":732,"_stem":733,"_extension":372},"/topics/design/button-component-design","按钮组件设计详解","学习按钮样式、交互状态、无障碍性和最佳实践",[379,380,381,382,383],"按钮设计","Button Component","交互状态","UI 组件","用户体验","/images/topics/button-design.jpg","2025-12-08",18,{"type":24,"children":388,"toc":713},[389,393,398,403,410,423,429,438,444,453,457,463,474,480,489,495,504,509,518,523,534,539,548,553,566,600,611,654,659],{"type":27,"tag":70,"props":390,"children":391},{"id":376},[392],{"type":32,"value":376},{"type":27,"tag":28,"props":394,"children":395},{},[396],{"type":32,"value":397},"按钮是 UI 中最重要的交互元素。优秀的按钮设计能够指导用户行为。",{"type":27,"tag":70,"props":399,"children":401},{"id":400},"按钮类型",[402],{"type":32,"value":400},{"type":27,"tag":404,"props":405,"children":407},"h3",{"id":406},"primary-button主按钮",[408],{"type":32,"value":409},"Primary Button（主按钮）",{"type":27,"tag":411,"props":412,"children":417},"pre",{"className":413,"code":415,"language":416,"meta":7},[414],"language-css",".btn-primary {\n  background-color: #0066cc;\n  color: #ffffff;\n  padding: 12px 24px;\n  border: none;\n  border-radius: 4px;\n  font-weight: 600;\n  font-size: 16px;\n  cursor: pointer;\n  transition: all 0.2s ease;\n}\n\n.btn-primary:hover {\n  background-color: #0052a3;\n  box-shadow: 0 4px 12px rgba(0, 102, 204, 0.2);\n}\n\n.btn-primary:active {\n  background-color: #003d7a;\n  transform: scale(0.98);\n}\n\n.btn-primary:disabled {\n  background-color: #cccccc;\n  cursor: not-allowed;\n  opacity: 0.6;\n}\n","css",[418],{"type":27,"tag":419,"props":420,"children":421},"code",{"__ignoreMap":7},[422],{"type":32,"value":415},{"type":27,"tag":404,"props":424,"children":426},{"id":425},"secondary-button次按钮",[427],{"type":32,"value":428},"Secondary Button（次按钮）",{"type":27,"tag":411,"props":430,"children":433},{"className":431,"code":432,"language":416,"meta":7},[414],".btn-secondary {\n  background-color: transparent;\n  color: #0066cc;\n  border: 2px solid #0066cc;\n  padding: 10px 22px;\n  border-radius: 4px;\n  font-weight: 600;\n  cursor: pointer;\n  transition: all 0.2s;\n}\n\n.btn-secondary:hover {\n  background-color: rgba(0, 102, 204, 0.1);\n}\n\n.btn-secondary:active {\n  background-color: rgba(0, 102, 204, 0.2);\n}\n",[434],{"type":27,"tag":419,"props":435,"children":436},{"__ignoreMap":7},[437],{"type":32,"value":432},{"type":27,"tag":404,"props":439,"children":441},{"id":440},"danger-button危险按钮",[442],{"type":32,"value":443},"Danger Button（危险按钮）",{"type":27,"tag":411,"props":445,"children":448},{"className":446,"code":447,"language":416,"meta":7},[414],".btn-danger {\n  background-color: #cc0000;\n  color: #ffffff;\n  padding: 12px 24px;\n  border-radius: 4px;\n  cursor: pointer;\n  font-weight: 600;\n}\n\n.btn-danger:hover {\n  background-color: #990000;\n  box-shadow: 0 4px 12px rgba(204, 0, 0, 0.2);\n}\n",[449],{"type":27,"tag":419,"props":450,"children":451},{"__ignoreMap":7},[452],{"type":32,"value":447},{"type":27,"tag":70,"props":454,"children":455},{"id":381},[456],{"type":32,"value":381},{"type":27,"tag":404,"props":458,"children":460},{"id":459},"loading-状态",[461],{"type":32,"value":462},"Loading 状态",{"type":27,"tag":411,"props":464,"children":469},{"className":465,"code":467,"language":468,"meta":7},[466],"language-jsx","import { useState } from 'react';\n\nfunction Button({ children, onClick, loading, ...props }) {\n  const [isLoading, setIsLoading] = useState(false);\n  \n  const handleClick = async () => {\n    setIsLoading(true);\n    try {\n      await onClick();\n    } finally {\n      setIsLoading(false);\n    }\n  };\n  \n  return (\n    \u003Cbutton\n      onClick={handleClick}\n      disabled={isLoading || loading}\n      aria-busy={isLoading || loading}\n      {...props}\n    >\n      {isLoading ? (\n        \u003C>\n          \u003Cspan className=\"spinner\" aria-hidden=\"true\">\u003C/span>\n          {children}\n        \u003C/>\n      ) : (\n        children\n      )}\n    \u003C/button>\n  );\n}\n","jsx",[470],{"type":27,"tag":419,"props":471,"children":472},{"__ignoreMap":7},[473],{"type":32,"value":467},{"type":27,"tag":404,"props":475,"children":477},{"id":476},"disabled-状态",[478],{"type":32,"value":479},"Disabled 状态",{"type":27,"tag":411,"props":481,"children":484},{"className":482,"code":483,"language":416,"meta":7},[414],".btn:disabled {\n  opacity: 0.5;\n  cursor: not-allowed;\n  background-color: #cccccc;\n  color: #999999;\n}\n\n/* 禁用状态下隐藏指针光标 */\n.btn:disabled:hover {\n  box-shadow: none;\n  transform: none;\n}\n",[485],{"type":27,"tag":419,"props":486,"children":487},{"__ignoreMap":7},[488],{"type":32,"value":483},{"type":27,"tag":404,"props":490,"children":492},{"id":491},"focus-状态",[493],{"type":32,"value":494},"Focus 状态",{"type":27,"tag":411,"props":496,"children":499},{"className":497,"code":498,"language":416,"meta":7},[414],".btn:focus {\n  outline: none;\n  box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1),\n              0 0 0 5px #0066cc;\n}\n\n/* 键盘导航焦点 */\n.btn:focus-visible {\n  outline: 2px solid #0066cc;\n  outline-offset: 2px;\n}\n",[500],{"type":27,"tag":419,"props":501,"children":502},{"__ignoreMap":7},[503],{"type":32,"value":498},{"type":27,"tag":70,"props":505,"children":507},{"id":506},"按钮大小",[508],{"type":32,"value":506},{"type":27,"tag":411,"props":510,"children":513},{"className":511,"code":512,"language":416,"meta":7},[414],"/* 小按钮 */\n.btn-sm {\n  padding: 6px 12px;\n  font-size: 12px;\n  min-height: 32px;\n  min-width: 32px;\n}\n\n/* 中等按钮（默认） */\n.btn-md {\n  padding: 12px 24px;\n  font-size: 16px;\n  min-height: 44px;\n  min-width: 44px;\n}\n\n/* 大按钮 */\n.btn-lg {\n  padding: 16px 32px;\n  font-size: 18px;\n  min-height: 56px;\n  min-width: 56px;\n}\n\n/* 全宽按钮 */\n.btn-block {\n  width: 100%;\n  display: block;\n}\n",[514],{"type":27,"tag":419,"props":515,"children":516},{"__ignoreMap":7},[517],{"type":32,"value":512},{"type":27,"tag":70,"props":519,"children":521},{"id":520},"无障碍性",[522],{"type":32,"value":520},{"type":27,"tag":411,"props":524,"children":529},{"className":525,"code":527,"language":528,"meta":7},[526],"language-html","\u003C!-- 语义正确 -->\n\u003Cbutton type=\"submit\" aria-label=\"提交表单\">\n  提交\n\u003C/button>\n\n\u003C!-- 加载状态 -->\n\u003Cbutton aria-busy=\"true\" disabled>\n  \u003Cspan aria-hidden=\"true\" class=\"spinner\">\u003C/span>\n  加载中...\n\u003C/button>\n\n\u003C!-- 图标按钮 -->\n\u003Cbutton aria-label=\"关闭\">\n  \u003Csvg aria-hidden=\"true\" width=\"24\" height=\"24\">\n    \u003Cline x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n    \u003Cline x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n  \u003C/svg>\n\u003C/button>\n\n\u003C!-- 切换按钮 -->\n\u003Cbutton aria-pressed=\"false\" aria-label=\"点赞\">\n  ♥\n\u003C/button>\n","html",[530],{"type":27,"tag":419,"props":531,"children":532},{"__ignoreMap":7},[533],{"type":32,"value":527},{"type":27,"tag":70,"props":535,"children":537},{"id":536},"完整组件示例",[538],{"type":32,"value":536},{"type":27,"tag":411,"props":540,"children":543},{"className":541,"code":542,"language":468,"meta":7},[466],"const Button = React.forwardRef((\n  {\n    children,\n    variant = 'primary',\n    size = 'md',\n    loading = false,\n    disabled = false,\n    icon,\n    className,\n    ...props\n  },\n  ref\n) => {\n  return (\n    \u003Cbutton\n      ref={ref}\n      className={`btn btn-${variant} btn-${size} ${className}`}\n      disabled={disabled || loading}\n      aria-busy={loading}\n      {...props}\n    >\n      {icon && \u003Cspan className=\"btn-icon\" aria-hidden=\"true\">{icon}\u003C/span>}\n      {loading ? (\n        \u003C>\n          \u003Cspan className=\"spinner\" aria-hidden=\"true\">\u003C/span>\n          {children}\n        \u003C/>\n      ) : (\n        children\n      )}\n    \u003C/button>\n  );\n});\n\nButton.displayName = 'Button';\n",[544],{"type":27,"tag":419,"props":545,"children":546},{"__ignoreMap":7},[547],{"type":32,"value":542},{"type":27,"tag":70,"props":549,"children":551},{"id":550},"最佳实践",[552],{"type":32,"value":550},{"type":27,"tag":28,"props":554,"children":555},{},[556,558,564],{"type":32,"value":557},"✅ ",{"type":27,"tag":559,"props":560,"children":561},"strong",{},[562],{"type":32,"value":563},"应该做的事",{"type":32,"value":565},":",{"type":27,"tag":40,"props":567,"children":568},{},[569,574,579,590,595],{"type":27,"tag":44,"props":570,"children":571},{},[572],{"type":32,"value":573},"最小触摸目标 44x44px",{"type":27,"tag":44,"props":575,"children":576},{},[577],{"type":32,"value":578},"清晰的视觉反馈",{"type":27,"tag":44,"props":580,"children":581},{},[582,584],{"type":32,"value":583},"使用语义 HTML ",{"type":27,"tag":419,"props":585,"children":587},{"className":586},[],[588],{"type":32,"value":589},"\u003Cbutton>",{"type":27,"tag":44,"props":591,"children":592},{},[593],{"type":32,"value":594},"提供加载状态反馈",{"type":27,"tag":44,"props":596,"children":597},{},[598],{"type":32,"value":599},"支持键盘导航",{"type":27,"tag":28,"props":601,"children":602},{},[603,605,610],{"type":32,"value":604},"❌ ",{"type":27,"tag":559,"props":606,"children":607},{},[608],{"type":32,"value":609},"不应该做的事",{"type":32,"value":565},{"type":27,"tag":40,"props":612,"children":613},{},[614,627,632,637,642],{"type":27,"tag":44,"props":615,"children":616},{},[617,619,625],{"type":32,"value":618},"使用 ",{"type":27,"tag":419,"props":620,"children":622},{"className":621},[],[623],{"type":32,"value":624},"\u003Cdiv>",{"type":32,"value":626}," 模拟按钮",{"type":27,"tag":44,"props":628,"children":629},{},[630],{"type":32,"value":631},"隐藏焦点指示器",{"type":27,"tag":44,"props":633,"children":634},{},[635],{"type":32,"value":636},"过多的按钮样式",{"type":27,"tag":44,"props":638,"children":639},{},[640],{"type":32,"value":641},"忽视禁用状态",{"type":27,"tag":44,"props":643,"children":644},{},[645,646,652],{"type":32,"value":618},{"type":27,"tag":419,"props":647,"children":649},{"className":648},[],[650],{"type":32,"value":651},"\u003Ca>",{"type":32,"value":653}," 代替按钮",{"type":27,"tag":70,"props":655,"children":657},{"id":656},"测试清单",[658],{"type":32,"value":656},{"type":27,"tag":40,"props":660,"children":663},{"className":661},[662],"contains-task-list",[664,677,686,695,704],{"type":27,"tag":44,"props":665,"children":668},{"className":666},[667],"task-list-item",[669,675],{"type":27,"tag":670,"props":671,"children":674},"input",{"disabled":672,"type":673},true,"checkbox",[],{"type":32,"value":676}," 在各种浏览器中测试",{"type":27,"tag":44,"props":678,"children":680},{"className":679},[667],[681,684],{"type":27,"tag":670,"props":682,"children":683},{"disabled":672,"type":673},[],{"type":32,"value":685}," 验证键盘导航",{"type":27,"tag":44,"props":687,"children":689},{"className":688},[667],[690,693],{"type":27,"tag":670,"props":691,"children":692},{"disabled":672,"type":673},[],{"type":32,"value":694}," 检查色彩对比度",{"type":27,"tag":44,"props":696,"children":698},{"className":697},[667],[699,702],{"type":27,"tag":670,"props":700,"children":701},{"disabled":672,"type":673},[],{"type":32,"value":703}," 测试触摸设备",{"type":27,"tag":44,"props":705,"children":707},{"className":706},[667],[708,711],{"type":27,"tag":670,"props":709,"children":710},{"disabled":672,"type":673},[],{"type":32,"value":712}," 屏幕阅读器兼容性",{"title":7,"searchDepth":357,"depth":357,"links":714},[715,716,721,726,727,728,729,730],{"id":376,"depth":360,"text":376},{"id":400,"depth":360,"text":400,"children":717},[718,719,720],{"id":406,"depth":357,"text":409},{"id":425,"depth":357,"text":428},{"id":440,"depth":357,"text":443},{"id":381,"depth":360,"text":381,"children":722},[723,724,725],{"id":459,"depth":357,"text":462},{"id":476,"depth":357,"text":479},{"id":491,"depth":357,"text":494},{"id":506,"depth":360,"text":506},{"id":520,"depth":360,"text":520},{"id":536,"depth":360,"text":536},{"id":550,"depth":360,"text":550},{"id":656,"depth":360,"text":656},"content:topics:design:button-component-design.md","topics/design/button-component-design.md","topics/design/button-component-design",{"_path":735,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":736,"description":737,"keywords":738,"image":743,"author":11,"date":385,"readingTime":744,"topic":5,"body":745,"_type":367,"_id":1033,"_source":369,"_file":1034,"_stem":1035,"_extension":372},"/topics/design/dark-mode-design","暗黑模式设计完整方案","学习暗黑模式实现、色彩方案、对比度管理和最佳实践",[739,740,741,742,383],"暗黑模式","Dark Mode","色彩系统","CSS 变量","/images/topics/dark-mode-design.jpg",20,{"type":24,"children":746,"toc":1016},[747,751,756,761,767,776,782,791,796,802,811,817,828,834,843,848,857,862,871,876,885,889,898,926,935,963,967],{"type":27,"tag":70,"props":748,"children":749},{"id":736},[750],{"type":32,"value":736},{"type":27,"tag":28,"props":752,"children":753},{},[754],{"type":32,"value":755},"暗黑模式已成为现代应用的标准功能。它能够减少眼睛疲劳、节省电池、改善用户体验。",{"type":27,"tag":70,"props":757,"children":759},{"id":758},"核心色彩系统",[760],{"type":32,"value":758},{"type":27,"tag":404,"props":762,"children":764},{"id":763},"light-mode-配色",[765],{"type":32,"value":766},"Light Mode 配色",{"type":27,"tag":411,"props":768,"children":771},{"className":769,"code":770,"language":416,"meta":7},[414],":root {\n  /* Light Mode */\n  --bg-primary: #ffffff;\n  --bg-secondary: #f5f5f5;\n  --bg-tertiary: #efefef;\n  \n  --text-primary: #1a1a1a;\n  --text-secondary: #666666;\n  --text-tertiary: #999999;\n  \n  --border-color: #e0e0e0;\n  --divider-color: #f0f0f0;\n}\n",[772],{"type":27,"tag":419,"props":773,"children":774},{"__ignoreMap":7},[775],{"type":32,"value":770},{"type":27,"tag":404,"props":777,"children":779},{"id":778},"dark-mode-配色",[780],{"type":32,"value":781},"Dark Mode 配色",{"type":27,"tag":411,"props":783,"children":786},{"className":784,"code":785,"language":416,"meta":7},[414],"@media (prefers-color-scheme: dark) {\n  :root {\n    /* Dark Mode */\n    --bg-primary: #1a1a1a;\n    --bg-secondary: #2d2d2d;\n    --bg-tertiary: #3a3a3a;\n    \n    --text-primary: #ffffff;\n    --text-secondary: #e0e0e0;\n    --text-tertiary: #a0a0a0;\n    \n    --border-color: #404040;\n    --divider-color: #2a2a2a;\n  }\n}\n",[787],{"type":27,"tag":419,"props":788,"children":789},{"__ignoreMap":7},[790],{"type":32,"value":785},{"type":27,"tag":70,"props":792,"children":794},{"id":793},"实现方案",[795],{"type":32,"value":793},{"type":27,"tag":404,"props":797,"children":799},{"id":798},"方案-1prefers-color-scheme",[800],{"type":32,"value":801},"方案 1：prefers-color-scheme",{"type":27,"tag":411,"props":803,"children":806},{"className":804,"code":805,"language":416,"meta":7},[414],"/* 自动跟随系统设置 */\n@media (prefers-color-scheme: light) {\n  :root {\n    --bg: #fff;\n    --text: #000;\n  }\n}\n\n@media (prefers-color-scheme: dark) {\n  :root {\n    --bg: #1a1a1a;\n    --text: #fff;\n  }\n}\n\nbody {\n  background: var(--bg);\n  color: var(--text);\n}\n",[807],{"type":27,"tag":419,"props":808,"children":809},{"__ignoreMap":7},[810],{"type":32,"value":805},{"type":27,"tag":404,"props":812,"children":814},{"id":813},"方案-2javascript-切换",[815],{"type":32,"value":816},"方案 2：JavaScript 切换",{"type":27,"tag":411,"props":818,"children":823},{"className":819,"code":821,"language":822,"meta":7},[820],"language-javascript","// 检测和切换暗黑模式\nfunction initDarkMode() {\n  const isDark = localStorage.getItem('darkMode') === 'true' ||\n                 window.matchMedia('(prefers-color-scheme: dark)').matches;\n  \n  if (isDark) {\n    document.documentElement.setAttribute('data-theme', 'dark');\n  }\n}\n\nfunction toggleDarkMode() {\n  const isDark = document.documentElement.getAttribute('data-theme') === 'dark';\n  const newTheme = isDark ? 'light' : 'dark';\n  \n  document.documentElement.setAttribute('data-theme', newTheme);\n  localStorage.setItem('darkMode', newTheme === 'dark');\n}\n\n// CSS 应用\nhtml[data-theme='light'] {\n  color-scheme: light;\n}\n\nhtml[data-theme='dark'] {\n  color-scheme: dark;\n}\n","javascript",[824],{"type":27,"tag":419,"props":825,"children":826},{"__ignoreMap":7},[827],{"type":32,"value":821},{"type":27,"tag":404,"props":829,"children":831},{"id":830},"方案-3css-variables-javascript",[832],{"type":32,"value":833},"方案 3：CSS Variables + JavaScript",{"type":27,"tag":411,"props":835,"children":838},{"className":836,"code":837,"language":822,"meta":7},[820],"const themes = {\n  light: {\n    '--bg-primary': '#ffffff',\n    '--text-primary': '#000000',\n    '--accent': '#0066cc',\n    '--border': '#e0e0e0',\n  },\n  dark: {\n    '--bg-primary': '#1a1a1a',\n    '--text-primary': '#ffffff',\n    '--accent': '#4da3ff',\n    '--border': '#404040',\n  },\n};\n\nfunction applyTheme(themeName) {\n  const theme = themes[themeName];\n  Object.entries(theme).forEach(([key, value]) => {\n    document.documentElement.style.setProperty(key, value);\n  });\n  localStorage.setItem('theme', themeName);\n}\n",[839],{"type":27,"tag":419,"props":840,"children":841},{"__ignoreMap":7},[842],{"type":32,"value":837},{"type":27,"tag":70,"props":844,"children":846},{"id":845},"对比度管理",[847],{"type":32,"value":845},{"type":27,"tag":411,"props":849,"children":852},{"className":850,"code":851,"language":416,"meta":7},[414],"/* Light Mode 对比度 */\n:root {\n  --contrast-high: #000000;     /* 21:1 */\n  --contrast-medium: #333333;   /* 12.6:1 */\n  --contrast-low: #666666;      /* 5.1:1 */\n}\n\n/* Dark Mode 对比度 */\n@media (prefers-color-scheme: dark) {\n  :root {\n    --contrast-high: #ffffff;    /* 21:1 */\n    --contrast-medium: #e0e0e0;  /* 11.6:1 */\n    --contrast-low: #a0a0a0;     /* 4.5:1 */\n  }\n}\n\n/* 应用对比度 */\n.text-primary { color: var(--contrast-high); }\n.text-secondary { color: var(--contrast-medium); }\n.text-tertiary { color: var(--contrast-low); }\n",[853],{"type":27,"tag":419,"props":854,"children":855},{"__ignoreMap":7},[856],{"type":32,"value":851},{"type":27,"tag":70,"props":858,"children":860},{"id":859},"图片和图表处理",[861],{"type":32,"value":859},{"type":27,"tag":411,"props":863,"children":866},{"className":864,"code":865,"language":528,"meta":7},[526],"\u003C!-- 针对不同主题的图片 -->\n\u003Cpicture>\n  \u003Csource \n    media=\"(prefers-color-scheme: dark)\" \n    srcset=\"chart-dark.svg\"\n  />\n  \u003Cimg src=\"chart-light.svg\" alt=\"图表\" />\n\u003C/picture>\n\n\u003C!-- SVG 颜色适配 -->\n\u003Csvg class=\"icon\">\n  \u003Ccircle cx=\"50\" cy=\"50\" r=\"40\" fill=\"currentColor\" />\n\u003C/svg>\n\n\u003Cstyle>\n  .icon {\n    color: var(--text-primary);\n  }\n\u003C/style>\n",[867],{"type":27,"tag":419,"props":868,"children":869},{"__ignoreMap":7},[870],{"type":32,"value":865},{"type":27,"tag":70,"props":872,"children":874},{"id":873},"完整示例",[875],{"type":32,"value":873},{"type":27,"tag":411,"props":877,"children":880},{"className":878,"code":879,"language":468,"meta":7},[466],"import { useState, useEffect } from 'react';\n\nfunction ThemeProvider({ children }) {\n  const [theme, setTheme] = useState('light');\n  const [mounted, setMounted] = useState(false);\n  \n  useEffect(() => {\n    setMounted(true);\n    \n    // 获取保存的主题或系统偏好\n    const savedTheme = localStorage.getItem('theme');\n    const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches \n      ? 'dark' \n      : 'light';\n    \n    const initialTheme = savedTheme || systemTheme;\n    setTheme(initialTheme);\n    document.documentElement.setAttribute('data-theme', initialTheme);\n  }, []);\n  \n  const toggleTheme = () => {\n    const newTheme = theme === 'light' ? 'dark' : 'light';\n    setTheme(newTheme);\n    localStorage.setItem('theme', newTheme);\n    document.documentElement.setAttribute('data-theme', newTheme);\n  };\n  \n  // 防止闪烁\n  if (!mounted) {\n    return null;\n  }\n  \n  return (\n    \u003CThemeContext.Provider value={{ theme, toggleTheme }}>\n      {children}\n      \u003CThemeToggle theme={theme} onChange={toggleTheme} />\n    \u003C/ThemeContext.Provider>\n  );\n}\n\nfunction ThemeToggle({ theme, onChange }) {\n  return (\n    \u003Cbutton \n      onClick={onChange}\n      aria-label={`切换到${theme === 'light' ? '暗黑' : '亮色'}模式`}\n    >\n      {theme === 'light' ? '🌙' : '☀️'}\n    \u003C/button>\n  );\n}\n",[881],{"type":27,"tag":419,"props":882,"children":883},{"__ignoreMap":7},[884],{"type":32,"value":879},{"type":27,"tag":70,"props":886,"children":887},{"id":550},[888],{"type":32,"value":550},{"type":27,"tag":28,"props":890,"children":891},{},[892,893,897],{"type":32,"value":557},{"type":27,"tag":559,"props":894,"children":895},{},[896],{"type":32,"value":563},{"type":32,"value":565},{"type":27,"tag":40,"props":899,"children":900},{},[901,906,911,916,921],{"type":27,"tag":44,"props":902,"children":903},{},[904],{"type":32,"value":905},"支持系统偏好",{"type":27,"tag":44,"props":907,"children":908},{},[909],{"type":32,"value":910},"提供手动切换选项",{"type":27,"tag":44,"props":912,"children":913},{},[914],{"type":32,"value":915},"确保足够的对比度",{"type":27,"tag":44,"props":917,"children":918},{},[919],{"type":32,"value":920},"优化图片和图表",{"type":27,"tag":44,"props":922,"children":923},{},[924],{"type":32,"value":925},"防止加载闪烁",{"type":27,"tag":28,"props":927,"children":928},{},[929,930,934],{"type":32,"value":604},{"type":27,"tag":559,"props":931,"children":932},{},[933],{"type":32,"value":609},{"type":32,"value":565},{"type":27,"tag":40,"props":936,"children":937},{},[938,943,948,953,958],{"type":27,"tag":44,"props":939,"children":940},{},[941],{"type":32,"value":942},"强制单一模式",{"type":27,"tag":44,"props":944,"children":945},{},[946],{"type":32,"value":947},"忽视性能影响",{"type":27,"tag":44,"props":949,"children":950},{},[951],{"type":32,"value":952},"使用相同的颜色",{"type":27,"tag":44,"props":954,"children":955},{},[956],{"type":32,"value":957},"忘记保存用户偏好",{"type":27,"tag":44,"props":959,"children":960},{},[961],{"type":32,"value":962},"过度使用深色背景",{"type":27,"tag":70,"props":964,"children":965},{"id":656},[966],{"type":32,"value":656},{"type":27,"tag":40,"props":968,"children":970},{"className":969},[662],[971,980,989,998,1007],{"type":27,"tag":44,"props":972,"children":974},{"className":973},[667],[975,978],{"type":27,"tag":670,"props":976,"children":977},{"disabled":672,"type":673},[],{"type":32,"value":979}," 在浅色和深色模式下测试所有页面",{"type":27,"tag":44,"props":981,"children":983},{"className":982},[667],[984,987],{"type":27,"tag":670,"props":985,"children":986},{"disabled":672,"type":673},[],{"type":32,"value":988}," 检查颜色对比度符合 WCAG 标准",{"type":27,"tag":44,"props":990,"children":992},{"className":991},[667],[993,996],{"type":27,"tag":670,"props":994,"children":995},{"disabled":672,"type":673},[],{"type":32,"value":997}," 验证图片和图表在两种模式下清晰",{"type":27,"tag":44,"props":999,"children":1001},{"className":1000},[667],[1002,1005],{"type":27,"tag":670,"props":1003,"children":1004},{"disabled":672,"type":673},[],{"type":32,"value":1006}," 测试主题切换的平滑性",{"type":27,"tag":44,"props":1008,"children":1010},{"className":1009},[667],[1011,1014],{"type":27,"tag":670,"props":1012,"children":1013},{"disabled":672,"type":673},[],{"type":32,"value":1015}," 检查用户偏好是否被保存",{"title":7,"searchDepth":357,"depth":357,"links":1017},[1018,1019,1023,1028,1029,1030,1031,1032],{"id":736,"depth":360,"text":736},{"id":758,"depth":360,"text":758,"children":1020},[1021,1022],{"id":763,"depth":357,"text":766},{"id":778,"depth":357,"text":781},{"id":793,"depth":360,"text":793,"children":1024},[1025,1026,1027],{"id":798,"depth":357,"text":801},{"id":813,"depth":357,"text":816},{"id":830,"depth":357,"text":833},{"id":845,"depth":360,"text":845},{"id":859,"depth":360,"text":859},{"id":873,"depth":360,"text":873},{"id":550,"depth":360,"text":550},{"id":656,"depth":360,"text":656},"content:topics:design:dark-mode-design.md","topics/design/dark-mode-design.md","topics/design/dark-mode-design",{"_path":1037,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":1038,"description":1039,"keywords":1040,"image":1045,"author":1046,"date":385,"readingTime":744,"topic":5,"body":1047,"_type":367,"_id":1312,"_source":369,"_file":1313,"_stem":1314,"_extension":372},"/topics/design/form-controls-design","表单控件设计规范","学习输入框、选择框、复选框等表单控件的设计和实现",[1041,1042,1043,1044,383],"表单设计","Form Controls","输入框","验证反馈","/images/topics/form-controls-design.jpg","AI Content Team",{"type":24,"children":1048,"toc":1298},[1049,1053,1058,1063,1068,1077,1082,1091,1095,1104,1109,1118,1123,1132,1137,1146,1151,1160,1164,1173,1199,1208,1236,1240],{"type":27,"tag":70,"props":1050,"children":1051},{"id":1038},[1052],{"type":32,"value":1038},{"type":27,"tag":28,"props":1054,"children":1055},{},[1056],{"type":32,"value":1057},"优秀的表单设计能够提高用户完成率和满意度。",{"type":27,"tag":70,"props":1059,"children":1061},{"id":1060},"输入框设计",[1062],{"type":32,"value":1060},{"type":27,"tag":404,"props":1064,"children":1066},{"id":1065},"基础文本输入",[1067],{"type":32,"value":1065},{"type":27,"tag":411,"props":1069,"children":1072},{"className":1070,"code":1071,"language":416,"meta":7},[414],".input {\n  width: 100%;\n  padding: 12px 16px;\n  font-size: 16px;\n  border: 2px solid #e0e0e0;\n  border-radius: 4px;\n  font-family: inherit;\n  transition: border-color 0.2s;\n}\n\n.input:hover {\n  border-color: #bdbdbd;\n}\n\n.input:focus {\n  outline: none;\n  border-color: #0066cc;\n  box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1);\n}\n\n.input:disabled {\n  background-color: #f5f5f5;\n  color: #999999;\n  cursor: not-allowed;\n}\n\n.input.error {\n  border-color: #cc0000;\n}\n\n.input.success {\n  border-color: #00cc00;\n}\n",[1073],{"type":27,"tag":419,"props":1074,"children":1075},{"__ignoreMap":7},[1076],{"type":32,"value":1071},{"type":27,"tag":404,"props":1078,"children":1080},{"id":1079},"标签和提示",[1081],{"type":32,"value":1079},{"type":27,"tag":411,"props":1083,"children":1086},{"className":1084,"code":1085,"language":528,"meta":7},[526],"\u003Cdiv class=\"form-group\">\n  \u003Clabel for=\"email\" class=\"form-label\">\n    邮箱地址 \u003Cspan class=\"required\">*\u003C/span>\n  \u003C/label>\n  \u003Cinput\n    id=\"email\"\n    type=\"email\"\n    placeholder=\"user@example.com\"\n    class=\"input\"\n    aria-describedby=\"email-hint\"\n  />\n  \u003Cp id=\"email-hint\" class=\"form-hint\">\n    我们永远不会分享你的邮箱\n  \u003C/p>\n\u003C/div>\n",[1087],{"type":27,"tag":419,"props":1088,"children":1089},{"__ignoreMap":7},[1090],{"type":32,"value":1085},{"type":27,"tag":70,"props":1092,"children":1093},{"id":1044},[1094],{"type":32,"value":1044},{"type":27,"tag":411,"props":1096,"children":1099},{"className":1097,"code":1098,"language":468,"meta":7},[466],"function FormInput({ label, error, success, helperText, value, onChange, ...props }) {\n  return (\n    \u003Cdiv className=\"form-group\">\n      \u003Clabel className=\"form-label\">{label}\u003C/label>\n      \u003Cinput\n        className={`input ${\n          error ? 'error' : success ? 'success' : ''\n        }`}\n        value={value}\n        onChange={onChange}\n        {...props}\n      />\n      {error && (\n        \u003Cp className=\"form-error\" role=\"alert\">\n          {error}\n        \u003C/p>\n      )}\n      {success && (\n        \u003Cp className=\"form-success\">\n          ✓ {success}\n        \u003C/p>\n      )}\n      {helperText && (\n        \u003Cp className=\"form-hint\">{helperText}\u003C/p>\n      )}\n    \u003C/div>\n  );\n}\n",[1100],{"type":27,"tag":419,"props":1101,"children":1102},{"__ignoreMap":7},[1103],{"type":32,"value":1098},{"type":27,"tag":70,"props":1105,"children":1107},{"id":1106},"选择框设计",[1108],{"type":32,"value":1106},{"type":27,"tag":411,"props":1110,"children":1113},{"className":1111,"code":1112,"language":416,"meta":7},[414],".select {\n  appearance: none;\n  width: 100%;\n  padding: 12px 16px;\n  border: 2px solid #e0e0e0;\n  border-radius: 4px;\n  background-image: url('data:image/svg+xml;...');\n  background-repeat: no-repeat;\n  background-position: right 12px center;\n  padding-right: 40px;\n  font-size: 16px;\n  cursor: pointer;\n}\n\n.select:hover {\n  border-color: #bdbdbd;\n}\n\n.select:focus {\n  outline: none;\n  border-color: #0066cc;\n  box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1);\n}\n",[1114],{"type":27,"tag":419,"props":1115,"children":1116},{"__ignoreMap":7},[1117],{"type":32,"value":1112},{"type":27,"tag":70,"props":1119,"children":1121},{"id":1120},"复选框和单选按钮",[1122],{"type":32,"value":1120},{"type":27,"tag":411,"props":1124,"children":1127},{"className":1125,"code":1126,"language":416,"meta":7},[414],".checkbox-group {\n  display: flex;\n  gap: 12px;\n  align-items: center;\n}\n\n.checkbox-input {\n  width: 20px;\n  height: 20px;\n  cursor: pointer;\n  accent-color: #0066cc;\n}\n\n.checkbox-label {\n  cursor: pointer;\n  user-select: none;\n}\n\n/* 自定义复选框 */\n.custom-checkbox {\n  appearance: none;\n  width: 20px;\n  height: 20px;\n  border: 2px solid #e0e0e0;\n  border-radius: 4px;\n  cursor: pointer;\n  background-color: white;\n  transition: all 0.2s;\n}\n\n.custom-checkbox:checked {\n  background-color: #0066cc;\n  border-color: #0066cc;\n  background-image: url('data:image/svg+xml;...');\n}\n\n.custom-checkbox:focus {\n  box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1);\n}\n",[1128],{"type":27,"tag":419,"props":1129,"children":1130},{"__ignoreMap":7},[1131],{"type":32,"value":1126},{"type":27,"tag":70,"props":1133,"children":1135},{"id":1134},"文本区域",[1136],{"type":32,"value":1134},{"type":27,"tag":411,"props":1138,"children":1141},{"className":1139,"code":1140,"language":416,"meta":7},[414],".textarea {\n  width: 100%;\n  min-height: 120px;\n  padding: 12px 16px;\n  border: 2px solid #e0e0e0;\n  border-radius: 4px;\n  font-family: inherit;\n  font-size: 16px;\n  resize: vertical;\n  transition: border-color 0.2s;\n}\n\n.textarea:focus {\n  outline: none;\n  border-color: #0066cc;\n  box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1);\n}\n",[1142],{"type":27,"tag":419,"props":1143,"children":1144},{"__ignoreMap":7},[1145],{"type":32,"value":1140},{"type":27,"tag":70,"props":1147,"children":1149},{"id":1148},"完整表单示例",[1150],{"type":32,"value":1148},{"type":27,"tag":411,"props":1152,"children":1155},{"className":1153,"code":1154,"language":468,"meta":7},[466],"function SignupForm() {\n  const [formData, setFormData] = useState({\n    name: '',\n    email: '',\n    password: '',\n    confirmPassword: '',\n    subscribe: false,\n    terms: false,\n  });\n  \n  const [errors, setErrors] = useState({});\n  const [touched, setTouched] = useState({});\n  const [submitted, setSubmitted] = useState(false);\n  \n  const handleChange = (e) => {\n    const { name, value, type, checked } = e.target;\n    setFormData(prev => ({\n      ...prev,\n      [name]: type === 'checkbox' ? checked : value,\n    }));\n    \n    // 实时验证\n    if (touched[name]) {\n      validateField(name, type === 'checkbox' ? checked : value);\n    }\n  };\n  \n  const handleBlur = (e) => {\n    const { name } = e.target;\n    setTouched(prev => ({ ...prev, [name]: true }));\n    validateField(name, formData[name]);\n  };\n  \n  const validateField = (name, value) => {\n    const newErrors = { ...errors };\n    \n    switch (name) {\n      case 'name':\n        if (!value) newErrors.name = '名字不能为空';\n        else delete newErrors.name;\n        break;\n      case 'email':\n        if (!value) newErrors.email = '邮箱不能为空';\n        else if (!/^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$/.test(value)) {\n          newErrors.email = '请输入有效的邮箱';\n        } else {\n          delete newErrors.email;\n        }\n        break;\n      case 'password':\n        if (!value) newErrors.password = '密码不能为空';\n        else if (value.length \u003C 8) newErrors.password = '密码至少 8 位';\n        else delete newErrors.password;\n        break;\n      case 'confirmPassword':\n        if (value !== formData.password) {\n          newErrors.confirmPassword = '两次密码输入不一致';\n        } else {\n          delete newErrors.confirmPassword;\n        }\n        break;\n      case 'terms':\n        if (!value) newErrors.terms = '必须同意服务条款';\n        else delete newErrors.terms;\n        break;\n      default:\n        break;\n    }\n    \n    setErrors(newErrors);\n  };\n  \n  const validate = () => {\n    const newErrors = {};\n    \n    if (!formData.name) newErrors.name = '名字不能为空';\n    if (!formData.email) newErrors.email = '邮箱不能为空';\n    if (formData.password.length \u003C 8) newErrors.password = '密码至少 8 位';\n    if (formData.password !== formData.confirmPassword) {\n      newErrors.confirmPassword = '两次密码输入不一致';\n    }\n    if (!formData.terms) newErrors.terms = '必须同意服务条款';\n    \n    return newErrors;\n  };\n  \n  const handleSubmit = async (e) => {\n    e.preventDefault();\n    \n    // 标记所有字段已触碰\n    setTouched({\n      name: true,\n      email: true,\n      password: true,\n      confirmPassword: true,\n      terms: true,\n    });\n    \n    const newErrors = validate();\n    \n    if (Object.keys(newErrors).length === 0) {\n      setSubmitted(true);\n      // 提交表单\n      console.log('Form submitted:', formData);\n      // 重置表单\n      setFormData({\n        name: '',\n        email: '',\n        password: '',\n        confirmPassword: '',\n        subscribe: false,\n        terms: false,\n      });\n    } else {\n      setErrors(newErrors);\n    }\n  };\n  \n  return (\n    \u003Cform onSubmit={handleSubmit} noValidate>\n      {submitted && (\n        \u003Cdiv className=\"form-success-message\" role=\"alert\">\n          注册成功！\n        \u003C/div>\n      )}\n      \n      \u003CFormInput\n        label=\"姓名\"\n        name=\"name\"\n        value={formData.name}\n        onChange={handleChange}\n        onBlur={handleBlur}\n        error={touched.name && errors.name}\n        helperText=\"请输入你的全名\"\n      />\n      \n      \u003CFormInput\n        label=\"邮箱\"\n        name=\"email\"\n        type=\"email\"\n        value={formData.email}\n        onChange={handleChange}\n        onBlur={handleBlur}\n        error={touched.email && errors.email}\n      />\n      \n      \u003CFormInput\n        label=\"密码\"\n        name=\"password\"\n        type=\"password\"\n        value={formData.password}\n        onChange={handleChange}\n        onBlur={handleBlur}\n        error={touched.password && errors.password}\n        helperText=\"至少 8 个字符\"\n      />\n      \n      \u003CFormInput\n        label=\"确认密码\"\n        name=\"confirmPassword\"\n        type=\"password\"\n        value={formData.confirmPassword}\n        onChange={handleChange}\n        onBlur={handleBlur}\n        error={touched.confirmPassword && errors.confirmPassword}\n      />\n      \n      \u003Cdiv className=\"form-group\">\n        \u003Clabel className=\"checkbox-label\">\n          \u003Cinput\n            type=\"checkbox\"\n            name=\"subscribe\"\n            checked={formData.subscribe}\n            onChange={handleChange}\n            className=\"checkbox-input\"\n          />\n          订阅我们的新闻通讯\n        \u003C/label>\n      \u003C/div>\n      \n      \u003Cdiv className=\"form-group\">\n        \u003Clabel className=\"checkbox-label\">\n          \u003Cinput\n            type=\"checkbox\"\n            name=\"terms\"\n            checked={formData.terms}\n            onChange={handleChange}\n            onBlur={handleBlur}\n            className=\"checkbox-input\"\n          />\n          我同意\n          \u003Ca href=\"/terms\" target=\"_blank\" rel=\"noopener noreferrer\">\n            服务条款\n          \u003C/a>\n          和\n          \u003Ca href=\"/privacy\" target=\"_blank\" rel=\"noopener noreferrer\">\n            隐私政策\n          \u003C/a>\n        \u003C/label>\n        {touched.terms && errors.terms && (\n          \u003Cp className=\"form-error\">{errors.terms}\u003C/p>\n        )}\n      \u003C/div>\n      \n      \u003Cbutton type=\"submit\" className=\"btn btn-primary btn-block\">\n        注册\n      \u003C/button>\n    \u003C/form>\n  );\n}\n",[1156],{"type":27,"tag":419,"props":1157,"children":1158},{"__ignoreMap":7},[1159],{"type":32,"value":1154},{"type":27,"tag":70,"props":1161,"children":1162},{"id":550},[1163],{"type":32,"value":550},{"type":27,"tag":28,"props":1165,"children":1166},{},[1167,1168,1172],{"type":32,"value":557},{"type":27,"tag":559,"props":1169,"children":1170},{},[1171],{"type":32,"value":563},{"type":32,"value":565},{"type":27,"tag":40,"props":1174,"children":1175},{},[1176,1181,1186,1191,1195],{"type":27,"tag":44,"props":1177,"children":1178},{},[1179],{"type":32,"value":1180},"使用正确的输入类型",{"type":27,"tag":44,"props":1182,"children":1183},{},[1184],{"type":32,"value":1185},"提供实时验证反馈",{"type":27,"tag":44,"props":1187,"children":1188},{},[1189],{"type":32,"value":1190},"清晰的标签和提示",{"type":27,"tag":44,"props":1192,"children":1193},{},[1194],{"type":32,"value":573},{"type":27,"tag":44,"props":1196,"children":1197},{},[1198],{"type":32,"value":599},{"type":27,"tag":28,"props":1200,"children":1201},{},[1202,1203,1207],{"type":32,"value":604},{"type":27,"tag":559,"props":1204,"children":1205},{},[1206],{"type":32,"value":609},{"type":32,"value":565},{"type":27,"tag":40,"props":1209,"children":1210},{},[1211,1216,1221,1226,1231],{"type":27,"tag":44,"props":1212,"children":1213},{},[1214],{"type":32,"value":1215},"隐藏标签",{"type":27,"tag":44,"props":1217,"children":1218},{},[1219],{"type":32,"value":1220},"过度使用占位符",{"type":27,"tag":44,"props":1222,"children":1223},{},[1224],{"type":32,"value":1225},"验证后立即提交",{"type":27,"tag":44,"props":1227,"children":1228},{},[1229],{"type":32,"value":1230},"忽视无障碍性",{"type":27,"tag":44,"props":1232,"children":1233},{},[1234],{"type":32,"value":1235},"复杂的验证规则",{"type":27,"tag":70,"props":1237,"children":1238},{"id":656},[1239],{"type":32,"value":656},{"type":27,"tag":40,"props":1241,"children":1243},{"className":1242},[662],[1244,1253,1262,1271,1280,1289],{"type":27,"tag":44,"props":1245,"children":1247},{"className":1246},[667],[1248,1251],{"type":27,"tag":670,"props":1249,"children":1250},{"disabled":672,"type":673},[],{"type":32,"value":1252}," 所有控件都可用键盘导航",{"type":27,"tag":44,"props":1254,"children":1256},{"className":1255},[667],[1257,1260],{"type":27,"tag":670,"props":1258,"children":1259},{"disabled":672,"type":673},[],{"type":32,"value":1261}," 标签与输入框关联",{"type":27,"tag":44,"props":1263,"children":1265},{"className":1264},[667],[1266,1269],{"type":27,"tag":670,"props":1267,"children":1268},{"disabled":672,"type":673},[],{"type":32,"value":1270}," 验证消息清晰",{"type":27,"tag":44,"props":1272,"children":1274},{"className":1273},[667],[1275,1278],{"type":27,"tag":670,"props":1276,"children":1277},{"disabled":672,"type":673},[],{"type":32,"value":1279}," 色彩对比度足够",{"type":27,"tag":44,"props":1281,"children":1283},{"className":1282},[667],[1284,1287],{"type":27,"tag":670,"props":1285,"children":1286},{"disabled":672,"type":673},[],{"type":32,"value":1288}," 屏幕阅读器兼容",{"type":27,"tag":44,"props":1290,"children":1292},{"className":1291},[667],[1293,1296],{"type":27,"tag":670,"props":1294,"children":1295},{"disabled":672,"type":673},[],{"type":32,"value":1297}," 移动设备测试",{"title":7,"searchDepth":357,"depth":357,"links":1299},[1300,1301,1305,1306,1307,1308,1309,1310,1311],{"id":1038,"depth":360,"text":1038},{"id":1060,"depth":360,"text":1060,"children":1302},[1303,1304],{"id":1065,"depth":357,"text":1065},{"id":1079,"depth":357,"text":1079},{"id":1044,"depth":360,"text":1044},{"id":1106,"depth":360,"text":1106},{"id":1120,"depth":360,"text":1120},{"id":1134,"depth":360,"text":1134},{"id":1148,"depth":360,"text":1148},{"id":550,"depth":360,"text":550},{"id":656,"depth":360,"text":656},"content:topics:design:form-controls-design.md","topics/design/form-controls-design.md","topics/design/form-controls-design",{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"topic":5,"author":11,"tags":1316,"image":18,"imageQuery":19,"pexelsPhotoId":20,"pexelsUrl":21,"featured":6,"readingTime":22,"body":1317,"_type":367,"_id":368,"_source":369,"_file":370,"_stem":371,"_extension":372},[13,14,15,16,17],{"type":24,"children":1318,"toc":1580},[1319,1323,1327,1346,1350,1354,1358,1362,1381,1385,1389,1393,1397,1412,1416,1420,1424,1428,1447,1451,1455,1459,1463,1467,1486,1490,1494,1513,1517,1521,1544,1548,1552,1556],{"type":27,"tag":28,"props":1320,"children":1321},{},[1322],{"type":32,"value":33},{"type":27,"tag":28,"props":1324,"children":1325},{},[1326],{"type":32,"value":38},{"type":27,"tag":40,"props":1328,"children":1329},{},[1330,1334,1338,1342],{"type":27,"tag":44,"props":1331,"children":1332},{},[1333],{"type":32,"value":48},{"type":27,"tag":44,"props":1335,"children":1336},{},[1337],{"type":32,"value":53},{"type":27,"tag":44,"props":1339,"children":1340},{},[1341],{"type":32,"value":58},{"type":27,"tag":44,"props":1343,"children":1344},{},[1345],{"type":32,"value":63},{"type":27,"tag":28,"props":1347,"children":1348},{},[1349],{"type":32,"value":68},{"type":27,"tag":70,"props":1351,"children":1352},{"id":72},[1353],{"type":32,"value":72},{"type":27,"tag":28,"props":1355,"children":1356},{},[1357],{"type":32,"value":79},{"type":27,"tag":28,"props":1359,"children":1360},{},[1361],{"type":32,"value":84},{"type":27,"tag":40,"props":1363,"children":1364},{},[1365,1369,1373,1377],{"type":27,"tag":44,"props":1366,"children":1367},{},[1368],{"type":32,"value":92},{"type":27,"tag":44,"props":1370,"children":1371},{},[1372],{"type":32,"value":97},{"type":27,"tag":44,"props":1374,"children":1375},{},[1376],{"type":32,"value":102},{"type":27,"tag":44,"props":1378,"children":1379},{},[1380],{"type":32,"value":107},{"type":27,"tag":28,"props":1382,"children":1383},{},[1384],{"type":32,"value":112},{"type":27,"tag":70,"props":1386,"children":1387},{"id":115},[1388],{"type":32,"value":115},{"type":27,"tag":28,"props":1390,"children":1391},{},[1392],{"type":32,"value":122},{"type":27,"tag":28,"props":1394,"children":1395},{},[1396],{"type":32,"value":127},{"type":27,"tag":40,"props":1398,"children":1399},{},[1400,1404,1408],{"type":27,"tag":44,"props":1401,"children":1402},{},[1403],{"type":32,"value":135},{"type":27,"tag":44,"props":1405,"children":1406},{},[1407],{"type":32,"value":140},{"type":27,"tag":44,"props":1409,"children":1410},{},[1411],{"type":32,"value":145},{"type":27,"tag":28,"props":1413,"children":1414},{},[1415],{"type":32,"value":150},{"type":27,"tag":70,"props":1417,"children":1418},{"id":153},[1419],{"type":32,"value":153},{"type":27,"tag":28,"props":1421,"children":1422},{},[1423],{"type":32,"value":160},{"type":27,"tag":28,"props":1425,"children":1426},{},[1427],{"type":32,"value":165},{"type":27,"tag":40,"props":1429,"children":1430},{},[1431,1435,1439,1443],{"type":27,"tag":44,"props":1432,"children":1433},{},[1434],{"type":32,"value":173},{"type":27,"tag":44,"props":1436,"children":1437},{},[1438],{"type":32,"value":178},{"type":27,"tag":44,"props":1440,"children":1441},{},[1442],{"type":32,"value":183},{"type":27,"tag":44,"props":1444,"children":1445},{},[1446],{"type":32,"value":188},{"type":27,"tag":28,"props":1448,"children":1449},{},[1450],{"type":32,"value":193},{"type":27,"tag":70,"props":1452,"children":1453},{"id":196},[1454],{"type":32,"value":199},{"type":27,"tag":28,"props":1456,"children":1457},{},[1458],{"type":32,"value":204},{"type":27,"tag":28,"props":1460,"children":1461},{},[1462],{"type":32,"value":209},{"type":27,"tag":28,"props":1464,"children":1465},{},[1466],{"type":32,"value":214},{"type":27,"tag":40,"props":1468,"children":1469},{},[1470,1474,1478,1482],{"type":27,"tag":44,"props":1471,"children":1472},{},[1473],{"type":32,"value":222},{"type":27,"tag":44,"props":1475,"children":1476},{},[1477],{"type":32,"value":227},{"type":27,"tag":44,"props":1479,"children":1480},{},[1481],{"type":32,"value":232},{"type":27,"tag":44,"props":1483,"children":1484},{},[1485],{"type":32,"value":237},{"type":27,"tag":70,"props":1487,"children":1488},{"id":240},[1489],{"type":32,"value":243},{"type":27,"tag":28,"props":1491,"children":1492},{},[1493],{"type":32,"value":248},{"type":27,"tag":40,"props":1495,"children":1496},{},[1497,1501,1505,1509],{"type":27,"tag":44,"props":1498,"children":1499},{},[1500],{"type":32,"value":256},{"type":27,"tag":44,"props":1502,"children":1503},{},[1504],{"type":32,"value":261},{"type":27,"tag":44,"props":1506,"children":1507},{},[1508],{"type":32,"value":266},{"type":27,"tag":44,"props":1510,"children":1511},{},[1512],{"type":32,"value":271},{"type":27,"tag":28,"props":1514,"children":1515},{},[1516],{"type":32,"value":276},{"type":27,"tag":70,"props":1518,"children":1519},{"id":279},[1520],{"type":32,"value":279},{"type":27,"tag":40,"props":1522,"children":1523},{},[1524,1528,1532,1536,1540],{"type":27,"tag":44,"props":1525,"children":1526},{},[1527],{"type":32,"value":289},{"type":27,"tag":44,"props":1529,"children":1530},{},[1531],{"type":32,"value":294},{"type":27,"tag":44,"props":1533,"children":1534},{},[1535],{"type":32,"value":299},{"type":27,"tag":44,"props":1537,"children":1538},{},[1539],{"type":32,"value":304},{"type":27,"tag":44,"props":1541,"children":1542},{},[1543],{"type":32,"value":309},{"type":27,"tag":70,"props":1545,"children":1546},{"id":312},[1547],{"type":32,"value":312},{"type":27,"tag":28,"props":1549,"children":1550},{},[1551],{"type":32,"value":319},{"type":27,"tag":28,"props":1553,"children":1554},{},[1555],{"type":32,"value":324},{"type":27,"tag":40,"props":1557,"children":1558},{},[1559,1566,1573],{"type":27,"tag":44,"props":1560,"children":1561},{},[1562],{"type":27,"tag":332,"props":1563,"children":1564},{"href":334},[1565],{"type":32,"value":337},{"type":27,"tag":44,"props":1567,"children":1568},{},[1569],{"type":27,"tag":332,"props":1570,"children":1571},{"href":343},[1572],{"type":32,"value":346},{"type":27,"tag":44,"props":1574,"children":1575},{},[1576],{"type":27,"tag":332,"props":1577,"children":1578},{"href":352},[1579],{"type":32,"value":355},{"title":7,"searchDepth":357,"depth":357,"links":1581},[1582,1583,1584,1585,1586,1587,1588],{"id":72,"depth":360,"text":72},{"id":115,"depth":360,"text":115},{"id":153,"depth":360,"text":153},{"id":196,"depth":360,"text":199},{"id":240,"depth":360,"text":243},{"id":279,"depth":360,"text":279},{"id":312,"depth":360,"text":312},1777086814182]