Text Input
Standard single-line text field with clear button and validation feedback
Prefix & Suffix
Text input with inline prefix and suffix labels inside the form control
Phone Number with Country Code
Compound input using a prefix-slot dropmenu as a value picker. The dropmenu's data-select-name opt-in captures the selected country code into a hidden input that ships with the form, and updates the trigger label to a compact display variant. Add nds-phone to the input itself to strip non-digits and any leading zero as the user types, since the country code is already prepended via the prefix slot.
Number Input
Numeric field with increment and decrement buttons for precise value adjustments
Search Input
Search field with leading icon, voice input, and clear button
Email Input
Email field with leading icon and built-in format validation
Password Input
Password field with visibility toggle and Arabic character filtering. For live strength rules and confirm-match checking, see Password
Textarea
Multi-line text entry for longer content like messages and descriptions
Custom Select
Custom dropdown select with keyboard navigation and hidden input for form submission
Native Select
Standard <select> element wrapped in the form container for native mobile picker behavior. In-form choice fields use the custom select by default, whatever the option count. Use this one only when the design asks for the native picker
Form Actions
The form-level action row for submit and navigation buttons. The row carries its own top margin and gap
Form Status API
Set the error status on any form container with automatic feedback display. A field carries error, help, or nothing. Help is neutral gray with a "?" icon, for hint text. Any other status renders as a plain neutral message.
Server-rendered Errors
A server-rendered page paints its own errors on load. Loop over the errors from the server, call setStatus for each field, then focus the first one. setStatus sets the status and inserts the message, but it never moves focus — only the caller knows which error comes first. Focus also scrolls the field into view, so an error below the fold reaches the user.
Loading State
Flip data-state="loading" on any form-container / form-group / form-control (via NDS.State.add) and forms handles the visual UX: auto-creates a [data-loading-slot] spinner in .nds-form-action (creating the slot if missing), hides sibling action buttons, and stamps data-state="loading" on the shell — .nds-btn's CSS renders the spinner. On exit the siblings restore; .nds-clear re-computes from the current input value. Zero markup burden — same UX for remote validation, autocomplete, custom fetch, anything
Permanent Feedback
Tips and hints that persist across validation cycles, hidden during errors and restored when cleared
The data-feedback-target element sets where a field's feedback renders. It is optional: without one, the API creates feedback in the field's container. Place it anywhere inside the container — a nds-form-footer puts messages below the input, a nds-form-header puts them above. A hidden target is shown when a message lands and hidden again when the last message clears.
Built-in Features
What you get out of the box with zero configuration
All form inputs auto-initialize on page load. Two-way state binding syncs disabled and data-required between inputs and containers. For dynamic content, call NDS.Forms.init().
Uses HTML5 validity checks but replaces browser popups with styled inline feedback. Error messages auto-clear on blur or change once the field is corrected. Custom messages via data-error-message.
Custom components hook into submit validation by stamping setCustomValidity('message') on their input (empty string when valid) — validateForm blocks the submit and shows the stamped message as-is, no forms changes needed.
Feedback is created by the API: setStatus places the message in the field's container, or in a data-feedback-target element when one is present. A field shows error or help; any other status renders neutral. Permanent hints survive validation cycles with data-permanent.
Focus, active, typing, filled, and disabled states are tracked automatically via data-state. Clear buttons auto-show when the input has a value and hide when empty.
Add a button with nds-toggle-password class to toggle between password and text input. The icon updates automatically to show the current visibility state.
Add a button with nds-voice-input class to enable speech-to-text on any input. Uses the Web Speech API with automatic language detection from the page.
Password fields automatically strip Arabic and RTL characters on input, ensuring passwords contain only compatible characters without manual validation logic.
Add nds-number-increment and nds-number-decrement buttons as prefix/suffix actions. Supports min, max, and step attributes, with accelerated stepping on long press.
Usage Guidelines
When and how to use form inputs effectively
Best Practices
- Use form inputs for any data collection scenario: registration flows, search interfaces, settings pages, and inline editing. Every input type shares the same container structure, so switching between text, email, password, or select requires minimal markup changes.
- A
nds-formelement draws no box of its own — it is a validation hook, not a layout container. Give its fields a gapped wrapper:nds-card-contentornds-flex nds-col. Fields in a bare container render flush against each other. - Use
nds-search-inputfor search fields where voice input and automatic clear actions improve discoverability. The search variant includes a leading icon and optional microphone button out of the box. - Wrap a form's submit and navigation buttons in
nds-form-actions. The plural class is the form-level row and carries its own top margin. The singularnds-form-actionis the per-input slot insidends-form-control. Never use a section action slot for a form's buttons. - Use the custom select dropdown (
nds-select) for in-form choice fields, whatever the option count — a native select beside styled fields breaks the form's look. It ships with keyboard navigation and a hidden input so it submits with the form. Wrap a standard<select>in the samends-form-container nds-selectshell only when the design calls for the native mobile picker, as shown in the Native Select demo. Withdata-requiredon the container, form validation reads the hidden value input at submit, the same as any other field. - Do not use form inputs for binary choices or toggles. Use switches for instant on/off toggles, checkboxes for multiple selections, or radio buttons for single selection from a set.
- Do not use plain text inputs for specialized data types. Use date picker for dates, OTP input for verification codes, and autocomplete for search-as-you-type with remote data.
- Choose
nds-lg(default) for standalone forms and primary data entry. Usends-mdfor compact layouts like table filters, inline editing, or sidebars where space is limited. - Add
data-requiredto the container rather thanrequiredon the input. The system syncs the two automatically and adds the required indicator to the label. - Use permanent feedback (
data-permanent) for format hints and character requirements. These persist across validation cycles: they hide when an error appears and restore when the error clears. - Use prefix and suffix slots for units, currency symbols, or action buttons that contextualize the input. Prefix/suffix buttons support both subtle and solid styles, and can include icons or dropmenus for compound inputs.
- For compound inputs (phone + country code, amount + currency, measurement + unit), use a prefix-slot dropmenu with
data-select-name. The dropmenu renders a hidden input that submits with the form and keeps the trigger label compact while the dropdown rows stay descriptive. See the Phone Number with Country Code demo for the full pattern. - Validation feedback needs no markup of its own:
setStatusand submit validation create the message element and place it in the field's container. Add adata-feedback-targetelement only when the design wants a permanent feedback position — see the Permanent Feedback section. - Set
data-error-messageon the container to override default browser validation messages with context-specific text that guides the user toward correction.
Modifier Classes
| Class | Element | Description |
|---|---|---|
nds-select | nds-form-container | Required on a container that holds a native <select>. It hides the browser's own arrow so the NDS chevron is the only one, and sizes the control for the select. Without it both arrows render |
nds-textarea | nds-form-container | Required on a container that holds a <textarea>. Without it the control keeps single-line input sizing, and the textarea loses its padding and minimum height |
nds-md | nds-form-container | Medium size with reduced height (32px) and smaller font |
nds-lighter | nds-form-container | Filled-background variant using the lighter surface token. Removes the input border so the fill alone defines the field boundary. Not applied to file upload containers. |
nds-darker | nds-form-container | Filled-background variant using the darker surface token. Same border behavior as nds-lighter. Not applied to file upload containers. |
nds-rowView | nds-form-group | Lays out checkbox, radio, or switch group items in a horizontal wrapping row instead of the default vertical column. |
nds-phone | nds-input (tel) | Set on a tel input that sits next to a country-code prefix slot. On every keystroke, strips non-digit characters and any leading zero so the value submitted is the local number ready to concatenate with the prefix. Length is still controlled by the input's maxlength. |
Validation Attributes
Validation runs on the browser's own constraint API. These are standard HTML attributes, not NDS ones, and they go on the <input>. NDS supplies the message text in Arabic and English and places it in the field footer.
| Attribute | Message when the value fails it |
|---|---|
required | This field is required. Prefer data-required on the container: it sets this attribute for you and adds the asterisk. |
type="email" | Please enter a valid email address |
type="url" | Please enter a valid URL |
minlength | Input is too short (minimum N characters) |
maxlength | Input is too long (maximum N characters). The browser also blocks typing past the limit, so this message appears only for a value set in code or pasted. |
pattern | Please match the requested format. The value must match the whole expression, so pattern="[0-9]{10}" accepts exactly ten digits and nothing else. Give the field a data-error-message that states the rule: the default text does not say what the format is. |
min / max | Value must be at least N / Value must be no more than N. On number inputs these also bound the stepper buttons: see the Data Attributes table below. |
Any other failure reads Invalid input. A field that a component validates itself, such as a date picker, carries that component's own message instead.
Data Attributes
| Attribute | Description |
|---|---|
data-required | Set on nds-form-container to mark the field as required. Automatically syncs to the input's required attribute and adds the asterisk indicator. |
data-error-message | Set on the <input> to replace every validation message that field would otherwise show, whichever constraint failed. For a checkbox or radio group, set it on the nds-form-group instead. It is read from those two elements only, not from the container. |
data-permanent | Set on a feedback element inside the footer. Permanent feedback hides during validation errors and restores when cleared. |
min | Set on number inputs to define the minimum allowed value (default: 0). |
max | Set on number inputs to define the maximum allowed value (default: 1000). |
step | Set on number inputs to define the increment/decrement size (default: 1). Long press accelerates to 10x this value. |
data-select-name | Set on an nds-dropmenu (including prefix/suffix dropmenus) to turn it into a value picker. Renders a hidden <input> with this name; item clicks write the item's data-value into it and update the trigger label. |
data-select-value | Set on a select-mode dropmenu to pre-select the item whose data-value matches. Takes precedence over any pre-rendered data-state="selected" item. |
data-required (on dropmenu) | Set on a select-mode dropmenu to add required to the hidden input, so native form submission blocks when nothing is picked. |
data-trigger-label | Set on an nds-dropmenu-item to override the trigger label text when that item is chosen. Useful when the dropdown row is descriptive (Saudi Arabia (+966)) but the trigger slot needs something compact (+966). |
data-min-checked | Set on nds-form-group (checkbox groups). The group is invalid unless at least this many checkboxes are checked. Also adds the required asterisk to the group label. |
data-max-checked | Set on nds-form-group (checkbox groups). The group is invalid when more than this many checkboxes are checked. Defaults to the total checkbox count when absent. |
data-required (on a rating group) | Set on an nds-form-group that holds a Rating. The group is invalid while data-rating is 0, and the first star takes focus. Picking a star clears the message, the way typing in an input does; submit re-checks the rule. The stars are buttons, not inputs, so add a hidden input if the score must post with the form. |
data-ajax | Set on nds-form. When present, the form's submit event is intercepted and preventDefault() is called after validation passes, allowing you to handle submission via fetch or XHR without a page reload. |
CSS Custom Properties
| Property | Default | Description |
|---|---|---|
--form-width | 100% | Controls the width of the form container |
--input-size | 40px | Height of the input field (32px for nds-md) |
--input-radius | var(--radius-sm) | Border radius of the input field |
--nds-input-size | 16px | Size (width and height) of the checkbox and radio control element. Size modifier classes set it to 20px (nds-md) or 24px (nds-lg). Set directly to use a custom size. |
JavaScript API
The NDS.Forms API manages initialization, status feedback, state management, and form validation. For dynamically added form elements, call NDS.Forms.initializeContainer(element) to initialize new inputs.