Forms Overview
Forms are essential for collecting user input in web applications. Terra UI provides a comprehensive set of form components that work together seamlessly to create accessible, validated forms that follow the Horizon Design System guidelines.
Form Components
Terra UI includes the following form components:
- Input - Text inputs, email, password, number, and more
- Textarea - Multi-line text input for longer content
- Select - Dropdown select fields with single or multiple selection
- Checkbox - Checkboxes for multiple selections
- Radio - Radio buttons for single selection from a group
- Radio Group - Groups radio buttons together
- Date Picker - Date and date range selection
- File Upload - File upload with drag-and-drop support
Basic Form Structure
All Terra form components integrate with native HTML forms and support standard form attributes like
name, required, disabled, and form. Here’s a basic
example:
<form id="basic-form"> <terra-input label="Full Name" name="fullName" required placeholder="Enter your full name" ></terra-input> <terra-input label="Email" name="email" type="email" required placeholder="you@example.com" help-text="We'll never share your email." ></terra-input> <terra-button type="submit" variant="primary">Submit</terra-button> </form>
Form Layouts
Single Column Layout
The simplest layout stacks all form fields vertically in a single column. This works well for shorter forms and mobile devices.
<form style="max-width: 500px;"> <terra-input label="First Name" name="firstName" required ></terra-input> <terra-input label="Last Name" name="lastName" required ></terra-input> <terra-input label="Email Address" name="email" type="email" required ></terra-input> <terra-input label="Phone Number" name="phone" type="tel" placeholder="(555) 123-4567" ></terra-input> <div style="margin-top: var(--terra-spacing-medium);"> <terra-button type="submit" variant="primary">Submit</terra-button> <terra-button type="reset" variant="text" style="margin-left: var(--terra-spacing-small);">Reset</terra-button> </div> </form>
Two Column Layout
For longer forms, you can use a two-column layout on larger screens. This makes better use of horizontal space while maintaining readability.
<form style="max-width: 800px;"> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: var(--terra-spacing-medium);"> <terra-input label="First Name" name="firstName" required ></terra-input> <terra-input label="Last Name" name="lastName" required ></terra-input> </div> <div style="margin-top: var(--terra-spacing-medium);"> <terra-input label="Email Address" name="email" type="email" required ></terra-input> </div> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: var(--terra-spacing-medium); margin-top: var(--terra-spacing-medium);"> <terra-input label="Phone Number" name="phone" type="tel" placeholder="(555) 123-4567" ></terra-input> <terra-date-picker label="Date of Birth" name="dateOfBirth" ></terra-date-picker> </div> <div style="margin-top: var(--terra-spacing-medium);"> <terra-button type="submit" variant="primary">Submit</terra-button> <terra-button type="reset" variant="text" style="margin-left: var(--terra-spacing-small);">Reset</terra-button> </div> </form>
Grouped Fields
Group related fields together using visual separators or fieldset elements for better organization.
<form style="max-width: 600px;"> <fieldset style="border: 1px solid var(--terra-color-carbon-20); border-radius: var(--terra-border-radius-medium); padding: var(--terra-spacing-medium); margin-bottom: var(--terra-spacing-medium);"> <legend style="font-weight: var(--terra-font-weight-semibold); color: var(--terra-color-carbon-80); padding: 0 var(--terra-spacing-small);">Personal Information</legend> <terra-input label="First Name" name="firstName" required ></terra-input> <terra-input label="Last Name" name="lastName" required ></terra-input> <terra-input label="Email" name="email" type="email" required ></terra-input> <terra-textarea label="Bio" name="bio" rows="3" placeholder="Tell us about yourself" ></terra-textarea> </fieldset> <fieldset style="border: 1px solid var(--terra-color-carbon-20); border-radius: var(--terra-border-radius-medium); padding: var(--terra-spacing-medium);"> <legend style="font-weight: var(--terra-font-weight-semibold); color: var(--terra-color-carbon-80); padding: 0 var(--terra-spacing-small);">Preferences</legend> <terra-checkbox name="newsletter" value="subscribe"> Subscribe to newsletter </terra-checkbox> <terra-checkbox name="updates" value="receive"> Receive product updates </terra-checkbox> </fieldset> <div style="margin-top: var(--terra-spacing-medium);"> <terra-button type="submit" variant="primary">Submit</terra-button> </div> </form>
Form Validation
Terra form components support native HTML5 validation and provide visual feedback for invalid states. Components automatically show error states when validation fails.
Required Fields
Mark fields as required using the required attribute. Required fields show an asterisk (*)
next to the label.
<form> <terra-input label="Email" name="email" type="email" required placeholder="you@example.com" ></terra-input> <terra-button type="submit" variant="primary">Submit</terra-button> </form>
Validation Patterns
Use the pattern attribute to validate input against a regular expression.
<form> <terra-input label="Phone Number" name="phone" type="tel" pattern="[0-9]{3}-[0-9]{3}-[0-9]{4}" placeholder="555-123-4567" help-text="Format: 555-123-4567" required ></terra-input> <terra-button type="submit" variant="primary">Submit</terra-button> </form>
Custom Validation
You can implement custom validation logic using the setCustomValidity() method available on
all form controls.
<form id="custom-validation-form"> <terra-input id="username-input" label="Username" name="username" required placeholder="Enter username" help-text="Username must be at least 3 characters" ></terra-input> <terra-button type="submit" variant="primary">Submit</terra-button> </form> <script> const form = document.getElementById('custom-validation-form'); const input = document.getElementById('username-input'); input.addEventListener('terra-input', (e) => { const value = e.target.value; if (value.length > 0 && value.length < 3) { input.setCustomValidity('Username must be at least 3 characters'); } else { input.setCustomValidity(''); } }); form.addEventListener('submit', (e) => { e.preventDefault(); if (form.checkValidity()) { alert('Form is valid!'); } else { form.reportValidity(); } }); </script>
Validation States
Form controls automatically receive data attributes that reflect their validation state:
data-required- The field is requireddata-optional- The field is optionaldata-invalid- The field is currently invaliddata-valid- The field is currently validdata-user-invalid- The field is invalid and the user has interacted with itdata-user-valid- The field is valid and the user has interacted with it
These attributes can be used to style validation states:
terra-input[data-user-invalid] { /* Custom invalid styling */ }
Complete Form Example
Here’s a complete example showing various form components working together:
<form id="complete-form" style="max-width: 600px;"> <h3 style="margin-top: 0; margin-bottom: var(--terra-spacing-medium);">Contact Form</h3> <div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: var(--terra-spacing-medium);"> <terra-input label="First Name" name="firstName" required ></terra-input> <terra-input label="Last Name" name="lastName" required ></terra-input> </div> <terra-input label="Email" name="email" type="email" required help-text="We'll use this to contact you" ></terra-input> <terra-input label="Phone" name="phone" type="tel" placeholder="(555) 123-4567" ></terra-input> <terra-select label="Subject" name="subject" required help-text="What is this regarding?" > <terra-option value="">Choose a subject</terra-option> <terra-option value="general">General Inquiry</terra-option> <terra-option value="support">Support</terra-option> <terra-option value="feedback">Feedback</terra-option> </terra-select> <terra-textarea label="Message" name="message" rows="5" required help-text="Please provide details about your inquiry" ></terra-textarea> <terra-radio-group label="Preferred Contact Method" name="contactMethod" value="email" required > <terra-radio value="email">Email</terra-radio> <terra-radio value="phone">Phone</terra-radio> <terra-radio value="either">Either</terra-radio> </terra-radio-group> <terra-checkbox name="newsletter" value="subscribe"> Subscribe to our newsletter </terra-checkbox> <div style="margin-top: var(--terra-spacing-large); display: flex; gap: var(--terra-spacing-small);"> <terra-button type="submit" variant="primary">Submit</terra-button> <terra-button type="reset" variant="text">Reset</terra-button> </div> </form> <script> document.getElementById('complete-form').addEventListener('submit', (e) => { e.preventDefault(); const formData = new FormData(e.target); const data = Object.fromEntries(formData); console.log('Form data:', data); alert('Form submitted! Check the console for form data.'); }); </script>
Best Practices
- Always provide labels - Every form field should have a clear, descriptive label
- Use help text - Provide guidance on what’s expected, especially for complex fields
-
Mark required fields - Use the
requiredattribute and ensure the asterisk is visible - Group related fields - Use fieldsets or visual grouping for related information
- Provide validation feedback - Show clear error messages when validation fails
- Use appropriate input types - Choose the right input type (email, tel, number, etc.) for better mobile keyboard support
- Test accessibility - Ensure forms work with screen readers and keyboard navigation
- Handle form submission - Prevent default submission and validate before processing
Accessibility
All Terra form components are built with accessibility in mind:
- Labels are properly associated with inputs
- Required fields are clearly indicated
- Error states are announced to screen readers
- Keyboard navigation is fully supported
- Focus indicators are visible and clear
For more information on individual form components, see their respective documentation pages.