1"use strict";(self.webpackChunktheuxshop=self.webpackChunktheuxshop||[]).push([[7408],{3390:(e,i,n)=>{n.r(i),n.d(i,{assets:()=>h,contentTitle:()=>c,default:()=>m,frontMatter:()=>d,metadata:()=>s,toc:()=>u});const s=JSON.parse('{"id":"patterns/forms","title":"Forms Patterns","description":"Input types, validation patterns, error handling, and mobile-friendly form design.","source":"@site/.tmp/docs-published/patterns/forms.mdx","sourceDirName":"patterns","slug":"/patterns/forms","permalink":"/patterns/forms","draft":false,"unlisted":false,"tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"id":"forms","title":"Forms Patterns","description":"Input types, validation patterns, error handling, and mobile-friendly form design.","sidebar_position":3},"sidebar":"patternsSidebar","previous":{"title":"Navigation Patterns","permalink":"/patterns/navigation"},"next":{"title":"Onboarding Patterns","permalink":"/patterns/onboarding"}}');var r=n(4848),l=n(8453),t=n(9176),o=n(5920),a=n(5388);const d={id:"forms",title:"Forms Patterns",description:"Input types, validation patterns, error handling, and mobile-friendly form design.",sidebar_position:3},c="Forms Patterns",h={},u=[{value:"Fundamental form principles",id:"fundamental-form-principles",level:2},{value:"Ask only what you need",id:"ask-only-what-you-need",level:3},{value:"Match fields to the data",id:"match-fields-to-the-data",level:3},{value:"Provide clear, visible labels",id:"provide-clear-visible-labels",level:3},{value:"Show requirements upfront",id:"show-requirements-upfront",level:3},{value:"Input patterns",id:"input-patterns",level:2},{value:"Text fields",id:"text-fields",level:3},{value:"Selection inputs",id:"selection-inputs",level:3},{value:"Specialized inputs",id:"specialized-inputs",level:3},{value:"Validation patterns",id:"validation-patterns",level:2},{value:"When to validate",id:"when-to-validate",level:3},{value:"How to validate",id:"how-to-validate",level:3},{value:"Validation messages",id:"validation-messages",level:3},{value:"Error handling",id:"error-handling",level:2},{value:"Inline errors",id:"inline-errors",level:3},{value:"Error summaries",id:"error-summaries",level:3},{value:"Recovery",id:"recovery",level:3},{value:"Mobile form considerations",id:"mobile-form-considerations",level:2},{value:"Touch targets",id:"touch-targets",level:3},{value:"Keyboards",id:"keyboards",level:3},{value:"Autofill",id:"autofill",level:3},{value:"Viewport considerations",id:"viewport-considerations",level:3},{value:"Progressive forms",id:"progressive-forms",level:2},{value:"Multi-step forms",id:"multi-step-forms",level:3},{value:"Conditional fields",id:"conditional-fields",level:3},{value:"Inline help",id:"inline-help",level:3}
1,{value:"Anti-patterns to avoid",id:"anti-patterns-to-avoid",level:2},{value:"Placeholder-only labels",id:"placeholder-only-labels",level:3},{value:"Excessive validation",id:"excessive-validation",level:3},{value:"Reset buttons",id:"reset-buttons",level:3},{value:"All fields required",id:"all-fields-required",level:3},{value:"Creative layouts",id:"creative-layouts",level:3}];function p(e){const i={code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",header:"header",li:"li",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,l.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(i.header,{children:(0,r.jsx)(i.h1,{id:"forms-patterns",children:"Forms Patterns"})}),"\n",(0,r.jsx)(i.p,{children:"Forms are how users provide information. Good forms feel effortless; bad forms create friction and abandonment. This document covers form patterns that make data entry as painless as possible."}),"\n",(0,r.jsx)(i.p,{children:(0,r.jsx)(i.em,{children:"Last updated: November 2025"})}),"\n",(0,r.jsx)(i.h2,{id:"fundamental-form-principles",children:"Fundamental form principles"}),"\n",(0,r.jsx)(i.h3,{id:"ask-only-what-you-need",children:"Ask only what you need"}),"\n",(0,r.jsx)(i.p,{children:'Every field creates friction. Question every field: "Do we truly need this?" If data is optional, remove it from the form entirely rather than marking it optional.'}),"\n",(0,r.jsx)(i.h3,{id:"match-fields-to-the-data",children:"Match fields to the data"}),"\n",(0,r.jsx)(i.p,{children:"Use appropriate input types. Email fields should trigger email keyboards. Phone fields should trigger numeric keyboards. Date fields should use date pickers."}),"\n",(0,r.jsx)(i.h3,{id:"provide-clear-visible-labels",children:"Provide clear, visible labels"}),"\n",(0,r.jsx)(i.p,{children:"Labels should be visible and persistent - not placeholders that disappear. Users need to verify their input against the field label."}),"\n",(0,r.jsx)(i.h3,{id:"show-requirements-upfront",children:"Show requirements upfront"}),"\n",(0,r.jsx)(i.p,{children:"Don't surprise users with requirements after they submit. If a password needs special characters, say so before they type."}),"\n",(0,r.jsx)(t.A,{variant:"note",title:"One column forms",children:(0,r.jsx)(i.p,{children:"Single-column forms outperform multi-column layouts. The eye path is clear, field relationships are obvious, and responsive behavior is simpler. Multi-column is occasionally justified (city/state/zip), but default to single-column."})}),"\n",(0,r.jsx)(i.h2,{id:"input-patterns",children:"Input patterns"}),"\n",(0,r.jsx)(i.h3,{id:"text-fields",children:"Text fields"}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Basic text input"}),": For short, free-form text (names, titles)."]}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsxs)(i.li,{children:["Use appropriate ",(0,r.jsx)(i.code,{children:"type"})," attribute"]}),"\n",(0,r.jsxs)(i.li,{children:["Set ",(0,r.jsx)(i.code,{children:"autocomplete"})," attribute to enable autofill"]}),"\n",(0,r.jsxs)(i.li,{children:["Consider ",(0,r.jsx)(i.code,{children:"inputmode"})," for mobile keyboards"]}),"\n"]}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Email"}),": Use ",(0,r.jsx)(i.code,{children:'type="email"'})," for validation and mobile keyboard optimization."]}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Phone"}),": Use ",(0,r.jsx)(i.code,{children:'type="tel"'})," for numeric keyboard. Don't enforce formatting while typing - accept various formats and normalize on the backend."]}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Password"}),": Use ",(0,r.jsx)(i.code,{children:'type="password"'}),". Include:"]}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsx)(i.li,{children:"Show/hide toggle for verification"}),"\n",(0,r.jsx)(i.li,{children:"Clear requirements stated"}),"\n",(0,r.jsx)(i.li,{children:"Strength indicator if helpful"}),"\n"]}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Textarea"}),": For multi-line input. Set reasonable height and allow resizing."]}),"\n",(0,r.jsx)(i.h3,{id:"selection-inputs",children:"Selection inputs"}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Radio buttons"}),": For selecting one option from a few choices (2-5). Use when options need to be visible simultaneously."]}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Checkboxes"}),": For selecting zero or more options. Also for single binary choices (agree to terms)."]}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Select/dropdown"}),": For selecting one option from many (5+). Appropriate when options don't fit as radios. Poor for very long lists - consider autocomplete instead."]}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Autocomplete/combobox"}
1),": For selecting from many options with search filtering. Good for long lists, country/city selection, finding items."]}),"\n",(0,r.jsx)(i.h3,{id:"specialized-inputs",children:"Specialized inputs"}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Date picker"}),": Use when specific dates matter. Native date inputs work well on mobile. Consider separate fields for contexts needing typing (birthdates)."]}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Time picker"}),": Similar considerations as date. Allow common formats; don't force 24-hour or AM/PM if users expect the other."]}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"File upload"}),": Clear allowed types and size limits. Progress indicator for large files. Preview when useful (images)."]}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Range/slider"}),": For imprecise numeric selection. Show current value. Consider whether exact numbers matter - if so, use a number input."]}),"\n",(0,r.jsx)(i.h2,{id:"validation-patterns",children:"Validation patterns"}),"\n",(0,r.jsx)(i.h3,{id:"when-to-validate",children:"When to validate"}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Real-time validation"})," (as user types): Good for format guidance. Risky if too aggressive - don't show errors before user has finished entering."]}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"On blur validation"})," (when leaving field): Good balance. User has indicated they're done with the field."]}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"On submit validation"}),": Always validate on submit as the final check. This catches what other validation missed."]}),"\n",(0,r.jsx)(i.h3,{id:"how-to-validate",children:"How to validate"}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Format validation"}),": Email format, phone format, zip codes. Provide feedback but be lenient - many formats are valid."]}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Required fields"}),": Mark required fields clearly. Only validate as missing on submit or blur of empty required fields."]}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Custom rules"}),": Password requirements, username availability. Show requirements proactively, not as errors."]}),"\n",(0,r.jsx)(i.h3,{id:"validation-messages",children:"Validation messages"}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Be specific"}),': "Password must include at least one number" is better than "Invalid password."']}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Be constructive"}),': "Enter a valid email address" is better than "Invalid email."']}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Position near the field"}),": Errors should appear close to the problematic field, not only at the top of the form."]}),"\n",(0,r.jsxs)(i.p,{children:[(0,r.jsx)(i.strong,{children:"Use color + icon + text"}),": Don't rely on color alone. Include an icon and explanatory text for accessibility."]}),"\n",(0,r.jsx)(i.h2,{id:"error-handling",children:"Error handling"}),"\n",(0,r.jsx)(i.h3,{id:"inline-errors",children:"Inline errors"}),"\n",(0,r.jsx)(i.p,{children:"Show errors adjacent to the field:"}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsx)(i.li,{children:"Below the field is most common"}),"\n",(0,r.jsx)(i.li,{children:"Red border or highlight on the field"}),"\n",(0,r.jsx)(i.li,{children:"Error icon for visibility"}),"\n",(0,r.jsx)(i.li,{children:"Clear error text"}),"\n"]}),"\n",(0,r.jsx)(i.p,{children:"Associate errors with fields programmatically:"}),"\n",(0,r.jsx)(i.pre,{children:(0,r.jsx)(i.code,{className:"language-html",children:'<input id="email" aria-describedby="email-error">\n<span id="email-error" role="alert">Please enter a valid email</span>\n'})}),"\n",(0,r.jsx)(i.h3,{id:"error-summaries",children:"Error summaries"}),"\n",(0,r.jsx)(i.p,{children:"For forms with multiple errors, also provide a summary:"}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsx)(i.li,{children:"Place at top of form"}),"\n",(0,r.jsx)(i.li,{children:"List all errors with links to problematic fields"}),"\n",(0,r.jsx)(i.li,{children:"Focus summary on form submission to ensure screen reader announcement"}),"\n"]}),"\n",(0,r.jsx)(i.h3,{id:"recovery",children:"Recovery"}),"\n",(0,r.jsx)(i.p,{children:"Make fixing errors easy:"}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsx)(i.li,{children:"Don't clear valid fields when there are errors"}),"\n",(0,r.jsx)(i.li,{children:"Preserve user input even when invalid"}),"\n",(0,r.jsx)(i.li,{children:"Allow correction without re-entering everything"}),"\n"]}),"\n",(0,r.jsx)(t.A,{variant:"tip",title:"Success states",children:(0,r.jsx)(i.p,{children:"Don't just show errors - confirm success too. A checkmark when a field is valid builds confidence. A summary confirmation after successful submission closes the loop."})}),"\n",(0,r.jsx)(i.h2,{id:"mobile-form-considerations",children:"Mobile form considerations"}),"\n",(0,r.jsx)(i.h3,{id:"touch-targets",children:"Touch targets"}),"\n",(0,r.jsx)(i.p,{children:"Inputs and buttons should be at least 44\xd744 pixels. Spacing between targets prevents mis-taps."}),"\n",(0,r.jsx)(i.h3,{id:"keyboards",children:"Keyboards"}),"\n",(0,r.jsx)(i.p,{children:"Use appropriate input types to trigger helpful keyboards:"}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.co
1de,{children:'type="email"'})," \u2192 email keyboard with @ symbol"]}),"\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.code,{children:'type="tel"'})," \u2192 numeric phone keyboard"]}),"\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.code,{children:'type="number"'})," \u2192 numeric keyboard"]}),"\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.code,{children:'inputmode="numeric"'})," \u2192 numeric keyboard for inputs that aren't numbers (credit cards)"]}),"\n"]}),"\n",(0,r.jsx)(i.h3,{id:"autofill",children:"Autofill"}),"\n",(0,r.jsxs)(i.p,{children:["Enable browser autofill with ",(0,r.jsx)(i.code,{children:"autocomplete"})," attributes:"]}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.code,{children:'autocomplete="email"'}),", ",(0,r.jsx)(i.code,{children:'autocomplete="tel"'})]}),"\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.code,{children:'autocomplete="given-name"'}),", ",(0,r.jsx)(i.code,{children:'autocomplete="family-name"'})]}),"\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.code,{children:'autocomplete="street-address"'}),", ",(0,r.jsx)(i.code,{children:'autocomplete="postal-code"'})]}),"\n",(0,r.jsxs)(i.li,{children:[(0,r.jsx)(i.code,{children:'autocomplete="cc-number"'}),", ",(0,r.jsx)(i.code,{children:'autocomplete="cc-exp"'})]}),"\n"]}),"\n",(0,r.jsx)(i.h3,{id:"viewport-considerations",children:"Viewport considerations"}),"\n",(0,r.jsx)(i.p,{children:"Forms should fit within viewport without horizontal scrolling. Test on actual devices - emulators don't reveal all issues."}),"\n",(0,r.jsx)(i.h2,{id:"progressive-forms",children:"Progressive forms"}),"\n",(0,r.jsx)(i.h3,{id:"multi-step-forms",children:"Multi-step forms"}),"\n",(0,r.jsx)(i.p,{children:"For complex forms, break into logical steps:"}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsx)(i.li,{children:"Show progress indicator"}),"\n",(0,r.jsx)(i.li,{children:"Allow back navigation"}),"\n",(0,r.jsx)(i.li,{children:"Save progress when possible"}),"\n",(0,r.jsx)(i.li,{children:"Keep steps focused (3-5 fields per step)"}),"\n"]}),"\n",(0,r.jsx)(i.h3,{id:"conditional-fields",children:"Conditional fields"}),"\n",(0,r.jsx)(i.p,{children:"Show fields only when relevant:"}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsx)(i.li,{children:'"Company name" appears only if "I\'m registering for a company" is selected'}),"\n",(0,r.jsx)(i.li,{children:"Additional details appear based on previous choices"}),"\n"]}),"\n",(0,r.jsx)(i.p,{children:"Use smooth transitions - don't jar users with sudden layout changes."}),"\n",(0,r.jsx)(i.h3,{id:"inline-help",children:"Inline help"}),"\n",(0,r.jsx)(i.p,{children:"Provide help without leaving the form:"}),"\n",(0,r.jsxs)(i.ul,{children:["\n",(0,r.jsx)(i.li,{children:"Tooltips for brief explanations"}),"\n",(0,r.jsx)(i.li,{children:"Help text below fields for important context"}),"\n",(0,r.jsx)(i.li,{children:"Expandable help for longer guidance"}),"\n"]}),"\n",(0,r.jsx)(i.h2,{id:"anti-patterns-to-avoid",children:"Anti-patterns to avoid"}),"\n",(0,r.jsx)(i.h3,{id:"placeholder-only-labels",children:"Placeholder-only labels"}),"\n",(0,r.jsx)(i.p,{children:"Placeholders disappear when users type, removing context. Always use visible labels."}),"\n",(0,r.jsx)(i.h3,{id:"excessive-validation",children:"Excessive validation"}),"\n",(0,r.jsx)(i.p,{children:"Don't validate too early, too strictly, or too often. Users need time to enter information."}),"\n",(0,r.jsx)(i.h3,{id:"reset-buttons",children:"Reset buttons"}),"\n",(0,r.jsx)(i.p,{children:"Reset buttons clear all input - rarely what users want. They usually click Reset by accident. Omit them."}),"\n",(0,r.jsx)(i.h3,{id:"all-fields-required",children:"All fields required"}),"\n",(0,r.jsx)(i.p,{children:"If every field is required, you're probably asking for too much. Question each requirement."}),"\n",(0,r.jsx)(i.h3,{id:"creative-layouts",children:"Creative layouts"}),"\n",(0,r.jsx)(i.p,{children:"Non-standard layouts confuse users. Stick to conventional form patterns unless you have strong evidence for alternatives."}),"\n",(0,r.jsx)(o.A,{items:[{question:"Should I use inline validation?",answer:"Yes, but carefully. Validate on blur (when user leaves field), not on every keystroke. Don't show errors before the user has had a chance to enter the data."},{question:"Required or optional field marking?",answer:"Mark whichever is less common. If most fields are required, mark the optional ones. If most are optional, mark the required ones. Never mark both."},{question:"How many fields before I need multiple steps?",answer:"There's no magic number. If the form feels overwhelming or covers different topics, consider steps. 5-7 focused fields per step is a reasonable guideline."},{question:"Should I disable the submit button until the form is valid?",answer:"Generally no. Users may not understand why the button is disabled. Better to allow submission and then show clear error feedback."},{question:"How do I handle optional fields?",answer:"First question whether you need them at all. If you do, mark them '(optional)'. Don't use * for required fields - it's becoming less universally understood."}]}),"\n",(0,r.jsx)(a.A,{title:"Related resources",links:[{label:"Error States Pattern",to:"/patterns/error-states"},{label:"Accessibility Checklist",to:"/guides/accessibility-checklist"},{label:"Onboarding Patterns",to:"/patterns/onboarding"},{label:"Interaction Feedback",to:"/patterns/interaction-feedback"}]})]})}function m(e={}){const{wrapper:i}={...(0,l.R)(),...e.components};return i?(0,r.jsx)(i,{...e,children:(0,r.jsx)(p,{...e})}):p(e)}},9176:(e,i,n)=>{n.d(i,{A:()=>l});n(6540);var s=n(4164),r=n(4848);function l({variant:e="default",title:i,children:n}){return(0,r.jsxs)("div",{className:(0,s.A)("ux-callout","default"!==e&&`ux-callout--${e}`),children:[(0,r.jsx)("div",{className:"ux-callout__title",children:i||{default:"Note",tip:"Tip",warning:"Warning",note:"Field Note"}[e]}),(0,r.jsx)("div",{className:"ux-callout__content",children:n})]})}},5920:(e,i,n)=>{n.d(i,{A:()=>r});n(6540);var s=n(4848);function r({items:e}){return(0,s.jsx)("div",{className:"ux-faq",children:e.map((e,i)=>(0,s.jsxs)("div",{className:"ux-faq__item",children:[(0,s.jsx)("div",{className:"ux-faq__question",children:e.question}),(0,s.jsx)("div",{className:"ux-faq__answer",children:(0,s.jsx)("p",{children:e.answer})})]},i))})}},5388:(e,i,n)=>{n.d(i,{A:()=>l});n(6540);var s=n(6289),r=n(4848);function l({title:e="Related Resources",links:i}){return(0,r.jsxs)("div",{className:"ux-related",children:[(0,r.jsx)("div",{className:"ux-related__title",children:e}),(0,r.jsx)("ul",{className:"ux-related__list",children:i.map((e,i)=>(0,r.jsx)("li",{children:(0,r.jsx)(s.A,{to:e.to,className:"ux-related__link",children:e.label})},i))})]})}},8453:(e,i,n)=>{n.d(i,{R:()=>t,x:()=>o});var s=n(6540);const r={},l=s.createContext(r);function t(e){const i=s.useContext(l);return s.useMemo(function(){return"function"==typeof e?e(i):{...i,...e}},[i,e])}function o(e){let i;return i=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:t(e.components),s.createElement(l.Provider,{value:i},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.