role textbox
Text field draft v0.1.0
Lets people enter or edit a single line of text.
A text field collects one line of free text: a name, an email address, a search term, a quantity typed as text. It has a visible label above, an optional description and, when its value fails validation, an error message under it. Several lines of text are a Textarea; a choice among known values is a Select.
Specification
From packages/components/specs/components/text-field.spec.json
Raw file packages/components/specs/components/text-field.spec.json
{
"$schema": "../schema/component.schema.json",
"id": "text-field",
"name": "Text field",
"version": "0.1.0",
"status": "draft",
"role": "textbox",
"summary": "Lets people enter or edit a single line of text.",
"description": "A text field collects one line of free text: a name, an email address, a search term, a quantity typed as text. It has a visible label above, an optional description and, when its value fails validation, an error message under it. Several lines of text are a Textarea; a choice among known values is a Select.",
"tags": [
"form",
"input",
"text"
],
"anatomy": [
{
"id": "container",
"role": "container",
"required": true,
"description": "Root. Stacks the label, the control, the description and the error message; carries the size and the forced state."
},
{
"id": "label",
"role": "label",
"required": true,
"description": "Text naming the field. Provides the accessible name; activating it focuses the control."
},
{
"id": "control",
"role": "value",
"required": true,
"description": "The editable box: the native single-line text input, drawn by the component. Receives focus, holds the value and shows the placeholder while empty."
},
{
"id": "description",
"role": "description",
"required": false,
"description": "Optional help text under the control (format, example, consequence), associated with it as its description."
},
{
"id": "error-message",
"role": "errormessage",
"required": false,
"visibleIn": [
"invalid"
],
"description": "Text describing the error when the field is invalid, associated with the control as a description. Shown only while invalid."
}
],
"properties": [
{
"id": "size",
"name": "Size",
"type": "enum",
"default": "medium",
"description": "Control height, inline padding and text size scale together; label, description and error keep their size.",
"affects": [
"control"
],
"options": [
{
"value": "small",
"description": "Dense forms, toolbars, table rows (32 px high)."
},
{
"value": "medium",
"description": "Default (40 px)."
},
{
"value": "large",
"description": "Prominent, single fields: a search page, a sign-in form (48 px)."
}
]
},
{
"id": "label",
"name": "Label",
"type": "string",
"required": true,
"bindsTo": "label",
"description": "What the field asks for, as a noun phrase (Email address). Always visible."
},
{
"id": "value",
"name": "Value",
"type": "string",
"bindsTo": "control",
"description": "Current text of the field."
},
{
"id": "placeholder",
"name": "Placeholder",
"type": "string",
"description": "Short hint shown while the field is empty, in a lighter color. It disappears when people type: never the label, never an instruction people must remember."
},
{
"id": "description",
"name": "Description",
"type": "string",
"bindsTo": "description",
"description": "Help text: expected format, example, or what the value is used for."
},
{
"id": "errorMessage",
"name": "Error message",
"type": "string",
"bindsTo": "error-message",
"description": "What is wrong and how to fix it, shown when the field is invalid."
},
{
"id": "required",
"name": "Required",
"type": "boolean",
"default": false,
"description": "The field must be filled to submit the form."
},
{
"id": "readonly",
"name": "Read-only",
"type": "boolean",
"default": false,
"description": "The value can be read, focused and copied but not edited (a generated identifier)."
},
{
"id": "invalid",
"name": "Invalid",
"type": "boolean",
"default": false,
"description": "The value failed validation: the border takes the error color and the error message is shown."
},
{
"id": "disabled",
"name": "Disabled",
"type": "boolean",
"default": false,
"description": "Non-interactive and skipped by the keyboard; the value stays visible. Prefer read-only when people may need to read or copy the value."
}
],
"states": [
{
"id": "default",
"trigger": "interaction",
"description": "Resting: an outlined box on the default surface."
},
{
"id": "hover",
"trigger": "interaction",
"inputs": [
"pointer"
],
"description": "Pointer over the control. The box takes the surface hover tint; the border does not change. Needs a pointing device: touch has no hover."
},
{
"id": "focus-visible",
"trigger": "interaction",
"description": "Focus on the control, from the keyboard or a pointer: an editable field shows the shared focus ring outside its border however it was focused."
},
{
"id": "invalid",
"trigger": "property",
"property": "invalid",
"description": "Validation failed: the border takes the error color and the error message is shown under the control."
},
{
"id": "readonly",
"trigger": "property",
"property": "readonly",
"description": "Not editable: the box takes the level 1 surface; the value stays focusable and selectable."
},
{
"id": "disabled",
"trigger": "property",
"property": "disabled",
"description": "Non-interactive. Box, value, label and description drop to the shared disabled colors; hover and focus do not apply."
}
],
"layout": {
"direction": "vertical",
"align": "stretch",
"justify": "start",
"wrap": false,
"sizing": "fill",
"notes": [
"Label, control, description and error message stack, separated by container.gap; the control fills the width of the component, which the layout sizes.",
"Inline padding, minimum height and text size come from the size option; the corner radius and the border width do not change."
]
},
"accessibility": {
"ariaRole": "textbox",
"name": {
"text": "Computed from the label, associated with the control as its label element on the web. The description and the error message are exposed as the accessible description, not as part of the name. The placeholder is exposed as a placeholder and used as a name only when nothing else names the control, which never happens here.",
"refs": [
"wai-labels",
"html-aam-name-input",
"apg-describing",
"html-aam-placeholder"
]
},
"keyboard": [
{
"key": "Tab",
"action": "Moves focus to the control. A disabled control is not focusable and is skipped.",
"refs": [
"wcag:2.1.1",
"html-focusable-area",
"html-disabled"
],
"platforms": [
"web"
]
}
],
"guidelines": [
{
"text": "Expose the textbox role, the name, the value and the required, read-only, disabled and invalid states through the platform's native text input, so assistive technology receives them without extra work.",
"refs": [
"wcag:4.1.2",
"aria-textbox"
]
},
{
"text": "Use the native input type=text (or email, tel, url for those purposes) without a list attribute: it exposes the textbox role, its value and states without ARIA.",
"refs": [
"html-aria-input-text",
"html-aam-input-text"
],
"platforms": [
"web"
]
},
{
"text": "Every field has a visible label associated with the control programmatically; help text that people need to fill the field (format, example) is visible too, as the description.",
"refs": [
"wcag:3.3.2",
"wcag:1.3.1",
"wai-labels",
"wai-instructions",
"html-label"
]
},
{
"text": "Associate the label with a label element whose for attribute names the control's id; reference the description and the error message with aria-describedby, so they are read after the name.",
"refs": [
"wcag-h44",
"wcag-aria1",
"apg-describing"
],
"platforms": [
"web"
]
},
{
"text": "The placeholder is a short hint, never the label and never an instruction: it disappears as soon as people type, and is not exposed as the name when a label exists.",
"refs": [
"html-placeholder",
"wai-instructions-placeholder",
"html-aam-placeholder"
],
"platforms": [
"web"
]
},
{
"text": "Show a hint in a text field to help communicate its purpose; because placeholder text disappears when people start typing, include a separate label describing the field.",
"refs": [
"hig-text-fields"
],
"platforms": [
"ios"
]
},
{
"text": "When the value is invalid, identify the field and describe the error in text next to it: the error message names what is wrong and how to fix it, and is associated with the control.",
"refs": [
"wcag:3.3.1",
"wai-notifications",
"wcag-g83"
]
},
{
"text": "Say that the value is invalid with aria-invalid on the control, and that a field is required with the required attribute, which the browser exposes as required.",
"refs": [
"wcag-aria21",
"aria-invalid",
"html-required",
"html-aam-required",
"wai-validation"
],
"platforms": [
"web"
]
},
{
"text": "Mark required fields in the label or the description, never by the error color alone.",
"refs": [
"wcag-h90",
"wcag-f81",
"wcag:1.4.1"
]
},
{
"text": "Label, value, description and error text contrast at least 4.5:1 with their background in every color mode and enabled state: label and value 19.07:1 (light) and 11.27:1 (dark) on the default surface, description 12.87:1 and 8.80:1, placeholder 6.08:1 and 6.36:1, error text 4.52:1 and 4.97:1 (4.26:1 on a level 1 surface in light mode, a Figma library limit tracked as a known issue). Disabled text is inactive and exempt.",
"refs": [
"wcag:1.4.3"
]
},
{
"text": "The border is what identifies the field, so it contrasts at least 3:1 with the adjacent surface in every color mode: 12.87:1 (light) and 8.80:1 (dark) on the default surface at rest, 12.14:1 and 8.06:1 on the hover tint; the error border 6.12:1 and 3.82:1; the focus ring 5.79:1 and 12.80:1. Disabled is exempt.",
"refs": [
"wcag:1.4.11",
"understanding-non-text-contrast-boundaries"
]
},
{
"text": "A visible focus indicator appears when the control receives focus, drawn outside its border.",
"refs": [
"wcag:2.4.7",
"wcag:2.4.13"
]
},
{
"text": "The focus ring is drawn with :focus-visible, which browsers match whenever an editable field has focus, pointer or keyboard.",
"refs": [
"mdn-focus-visible"
],
"platforms": [
"web"
]
},
{
"text": "Every size is a pointer target of at least 24 x 24 CSS pixels through its minimum height: 32, 40 and 48 pixels.",
"refs": [
"wcag:2.5.8"
]
},
{
"text": "Fields that collect information about the person (name, email, phone, address) carry the matching autocomplete token, so browsers and assistive technology can identify their purpose. The template passes it through its extra attributes.",
"refs": [
"wcag:1.3.5",
"wcag-h98"
],
"platforms": [
"web"
]
},
{
"text": "A read-only field keeps its value focusable and selectable and is exposed as read-only; a disabled field leaves the focus order. Prefer read-only when people may need to read or copy the value.",
"refs": [
"html-readonly",
"html-aam-readonly",
"html-disabled",
"apg-disabled-focus"
],
"platforms": [
"web"
]
},
{
"text": "Apple recommends a hit region of at least 44 x 44 pt; the small and medium sizes (32 and 40 pt) fall under it on iOS today: the Figma library has no platform heights.",
"refs": [
"hig-accessibility-mobility"
],
"platforms": [
"ios"
]
},
{
"text": "Android recommends a touch target of at least 48 x 48 dp; the small and medium sizes (32 and 40 dp) fall under it on Android today. Expose an invalid value with the error semantics so the message is announced with the field.",
"refs": [
"android-large-controls",
"compose-target-sizes",
"compose-error-components"
],
"platforms": [
"android"
]
},
{
"text": "Color transitions of the control are removed when the user asks for reduced motion.",
"refs": [
"wcag:2.3.3",
"mdn-reduced-motion"
]
}
],
"references": [
{
"id": "aria-textbox",
"title": "WAI-ARIA 1.2: textbox role",
"url": "https://www.w3.org/TR/wai-aria-1.2/#textbox"
},
{
"id": "aria-invalid",
"title": "WAI-ARIA 1.2: aria-invalid",
"url": "https://www.w3.org/TR/wai-aria-1.2/#aria-invalid"
},
{
"id": "apg-describing",
"title": "APG: Describing with aria-describedby",
"url": "https://www.w3.org/WAI/ARIA/apg/practices/names-and-descriptions/#describing_with_aria-describedby"
},
{
"id": "apg-disabled-focus",
"title": "APG: Focusability of disabled controls",
"url": "https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_disabled_controls"
},
{
"id": "html-aria-input-text",
"title": "ARIA in HTML: input type=text (role textbox)",
"url": "https://www.w3.org/TR/html-aria/#el-input-text"
},
{
"id": "html-aam-input-text",
"title": "HTML-AAM: input type=text (role textbox)",
"url": "https://www.w3.org/TR/html-aam-1.0/#el-input-text"
},
{
"id": "html-aam-name-input",
"title": "HTML-AAM: accessible name computation of input elements (associated label element)",
"url": "https://www.w3.org/TR/html-aam-1.0/#input-type-text-input-type-password-input-type-number-input-type-search-input-type-tel-input-type-email-input-type-url-and-textarea-elements-accessible-name-computation"
},
{
"id": "html-aam-placeholder",
"title": "HTML-AAM: placeholder attribute (aria-placeholder, a last-resort name)",
"url": "https://www.w3.org/TR/html-aam-1.0/#att-placeholder"
},
{
"id": "html-aam-readonly",
"title": "HTML-AAM: readonly attribute (aria-readonly)",
"url": "https://www.w3.org/TR/html-aam-1.0/#att-readonly"
},
{
"id": "html-aam-required",
"title": "HTML-AAM: required attribute (aria-required)",
"url": "https://www.w3.org/TR/html-aam-1.0/#att-required"
},
{
"id": "html-label",
"title": "HTML: the label element (for attribute, labeled control)",
"url": "https://html.spec.whatwg.org/multipage/forms.html#the-label-element"
},
{
"id": "html-placeholder",
"title": "HTML: the placeholder attribute (not an alternative to a label)",
"url": "https://html.spec.whatwg.org/multipage/input.html#attr-input-placeholder"
},
{
"id": "html-readonly",
"title": "HTML: the readonly attribute",
"url": "https://html.spec.whatwg.org/multipage/input.html#attr-input-readonly"
},
{
"id": "html-required",
"title": "HTML: the required attribute",
"url": "https://html.spec.whatwg.org/multipage/input.html#attr-input-required"
},
{
"id": "html-disabled",
"title": "HTML: disabled form controls",
"url": "https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#concept-fe-disabled"
},
{
"id": "html-focusable-area",
"title": "HTML: focusable area",
"url": "https://html.spec.whatwg.org/multipage/interaction.html#focusable-area"
},
{
"id": "mdn-focus-visible",
"title": "MDN: :focus-visible",
"url": "https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:focus-visible"
},
{
"id": "mdn-reduced-motion",
"title": "MDN: prefers-reduced-motion",
"url": "https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-reduced-motion"
},
{
"id": "wai-labels",
"title": "WAI tutorials: Labeling controls",
"url": "https://www.w3.org/WAI/tutorials/forms/labels/"
},
{
"id": "wai-instructions",
"title": "WAI tutorials: Form instructions (required fields, placeholder text, aria-describedby)",
"url": "https://www.w3.org/WAI/tutorials/forms/instructions/"
},
{
"id": "wai-instructions-placeholder",
"title": "WAI tutorials: Form instructions, placeholder text",
"url": "https://www.w3.org/WAI/tutorials/forms/instructions/#placeholder-text"
},
{
"id": "wai-validation",
"title": "WAI tutorials: Validating input (the required attribute)",
"url": "https://www.w3.org/WAI/tutorials/forms/validation/"
},
{
"id": "wai-notifications",
"title": "WAI tutorials: User notifications (error messages)",
"url": "https://www.w3.org/WAI/tutorials/forms/notifications/"
},
{
"id": "wcag-h44",
"title": "WCAG technique H44: label elements to associate text labels with form controls",
"url": "https://www.w3.org/WAI/WCAG22/Techniques/html/H44"
},
{
"id": "wcag-h90",
"title": "WCAG technique H90: indicating required form controls",
"url": "https://www.w3.org/WAI/WCAG22/Techniques/html/H90"
},
{
"id": "wcag-h98",
"title": "WCAG technique H98: HTML autocomplete attributes to identify the purpose of input fields",
"url": "https://www.w3.org/WAI/WCAG22/Techniques/html/H98"
},
{
"id": "wcag-g83",
"title": "WCAG technique G83: text descriptions for required fields not completed",
"url": "https://www.w3.org/WAI/WCAG22/Techniques/general/G83"
},
{
"id": "wcag-aria1",
"title": "WCAG technique ARIA1: aria-describedby for a descriptive label",
"url": "https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA1"
},
{
"id": "wcag-aria21",
"title": "WCAG technique ARIA21: aria-invalid to indicate an error field",
"url": "https://www.w3.org/WAI/WCAG22/Techniques/aria/ARIA21"
},
{
"id": "wcag-f81",
"title": "WCAG failure F81: required or error fields identified by color only",
"url": "https://www.w3.org/WAI/WCAG22/Techniques/failures/F81"
},
{
"id": "understanding-non-text-contrast-boundaries",
"title": "Understanding WCAG 1.4.11 Non-text Contrast: user interface components and boundaries",
"url": "https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html#boundaries"
},
{
"id": "hig-text-fields",
"title": "Apple HIG: Text fields, best practices (hints and labels)",
"url": "https://developer.apple.com/design/human-interface-guidelines/text-fields#Best-practices"
},
{
"id": "hig-accessibility-mobility",
"title": "Apple HIG: Accessibility, mobility (control sizes)",
"url": "https://developer.apple.com/design/human-interface-guidelines/accessibility#Mobility"
},
{
"id": "android-large-controls",
"title": "Android accessibility: use large, simple controls",
"url": "https://developer.android.com/guide/topics/ui/accessibility/apps#large-controls"
},
{
"id": "compose-target-sizes",
"title": "Jetpack Compose accessibility: minimum touch target sizes",
"url": "https://developer.android.com/develop/ui/compose/accessibility/api-defaults#minimum-target-sizes"
},
{
"id": "compose-error-components",
"title": "Jetpack Compose accessibility: error semantics of components",
"url": "https://developer.android.com/develop/ui/compose/accessibility/semantics#error-components"
}
]
},
"content": {
"guidelines": [
"The label is a noun phrase naming the information asked for: Email address, Company name. Sentence case, no colon, no trailing punctuation.",
"The description gives the format or an example when it is not obvious: We will only use it to send your receipt.",
"The placeholder, when any, is a short example of a valid value (name@example.com), never an instruction and never the label.",
"The error message says what is wrong and how to fix it: Enter an email address with an @."
]
},
"examples": [
{
"id": "medium",
"name": "Medium",
"props": {
"size": "medium",
"label": "Full name",
"value": "Ada Lovelace"
}
},
{
"id": "medium-description",
"name": "Medium, with description and placeholder",
"props": {
"size": "medium",
"label": "Email address",
"placeholder": "name@example.com",
"description": "We will only use it to send your receipt.",
"required": true
}
},
{
"id": "small",
"name": "Small",
"props": {
"size": "small",
"label": "Search",
"placeholder": "Filter by name"
}
},
{
"id": "large",
"name": "Large",
"props": {
"size": "large",
"label": "Project name",
"value": "Quantum Design System"
}
},
{
"id": "medium-invalid",
"name": "Medium, invalid",
"description": "Validation failed: the error message says how to fix it.",
"props": {
"size": "medium",
"label": "Email address",
"value": "ada.example.com",
"invalid": true,
"errorMessage": "Enter an email address with an @.",
"required": true
}
},
{
"id": "medium-readonly",
"name": "Medium, read-only",
"props": {
"size": "medium",
"label": "Workspace identifier",
"value": "ws_8f3a2c",
"description": "Generated when the workspace was created.",
"readonly": true
}
},
{
"id": "medium-disabled",
"name": "Medium, disabled",
"props": {
"size": "medium",
"label": "Billing email",
"value": "billing@example.com",
"disabled": true
}
}
],
"tokens": {
"source": "packages/tokens/src/components/text-field.tokens.json",
"prefix": "text-field"
},
"implementations": {
"web": {
"css": "packages/components/src/components/text-field/text-field.css",
"template": "packages/components/src/components/text-field/text-field.template.mjs",
"selector": ".ds-text-field",
"element": "div",
"attributes": {
"size": "data-size",
"disabled": "data-disabled",
"invalid": "data-invalid",
"readonly": "data-readonly"
},
"stateAttribute": "data-state",
"rolePart": "control",
"partSelectors": {
"container": ".ds-text-field",
"label": ".ds-text-field__label",
"control": ".ds-text-field__control",
"description": ".ds-text-field__description",
"error-message": ".ds-text-field__error-message"
}
}
},
"related": [
"textarea",
"select"
],
"decisions": [
{
"id": "field-colors",
"date": "2026-10-03",
"version": "0.1.0",
"area": "color",
"status": "active",
"text": "The control is a box on the default surface outlined in the accent neutral border color; hover tints it with the surface hover color and keeps the border; invalid takes the base error border; read-only takes the level 1 surface; disabled takes the shared disabled colors. Value text is primary, the placeholder tertiary, the description secondary, the error message the error text color.",
"rationale": "The Figma library maps no color to form controls. The accent neutral border is the lightest neutral border that keeps 3:1 against the surface in both modes (WCAG 1.4.11); the hover tint reuses the surface's own hover step.",
"tokens": [
"text-field.control.background.*",
"text-field.control.border-color.*",
"text-field.control.color.*"
],
"refs": [
"wcag:1.4.11"
]
},
{
"id": "sizes",
"date": "2026-10-03",
"version": "0.1.0",
"area": "size",
"status": "active",
"text": "Three sizes from the Figma library's space and body scales: small (32 px high, 8 px inline padding, 14 px text), medium (40, 12, 16, the default) and large (48, 16, 20); the border is 0.6 px (stroke-width.sm) and the corners 4 px (corner-radius.xs) at every size; label, description and error message keep 14 px.",
"tokens": [
"text-field.control.min-height.*",
"text-field.control.padding-inline.*",
"text-field.control.typography.*",
"text-field.control.border-width",
"text-field.control.border-radius"
]
},
{
"id": "label-description-error",
"date": "2026-10-03",
"version": "0.1.0",
"area": "anatomy",
"status": "active",
"text": "A field always has a visible label above the control; help text goes under the control as the description, and the error message under it while the field is invalid. Both are exposed as the control's description.",
"refs": [
"wcag:3.3.2",
"wai-labels",
"wai-instructions",
"apg-describing"
]
},
{
"id": "single-line-text-only",
"date": "2026-10-03",
"version": "0.1.0",
"area": "behaviour",
"status": "active",
"text": "The text field is a single-line text input; the input type (text, email, tel, url) and the autocomplete token are passed as extra attributes of the web template and do not change its look. Search, password and number fields are separate decisions.",
"refs": [
"html-aria-input-text",
"wcag-h98"
]
}
],
"changelog": [
{
"version": "0.1.0",
"date": "2026-10-03",
"note": "Initial draft (decisions field-colors, sizes, label-description-error, single-line-text-only). Native text input in a wrapper with a label element, a description and an error message; states default, hover, focus-visible, invalid, readonly, disabled; sizes small, medium, large. Contrast on the default surface, light / dark: label and value 19.07 / 11.27, description 12.87 / 8.80, placeholder 6.08 / 6.36, error text 4.52 / 4.97 (4.26 on level 1 in light mode), border at rest 12.87 / 8.80 and 12.14 / 8.06 on hover, error border 6.12 / 3.82, focus ring 5.79 / 12.80."
}
]
}
Follows the Color foundation (16 tokens), the Focus ring foundation (3 tokens), the Motion foundation (2 tokens), the Shape foundation (2 tokens), the Spacing and sizing foundation (7 tokens), the Typography foundation (6 tokens).
Anatomy
We will only use it to send your receipt.
We will only use it to send your receipt.
Generated when the workspace was created.
Generated when the workspace was created.
| Part | Role | Required | Description | Web selector |
|---|---|---|---|---|
container |
container |
yes | Root. Stacks the label, the control, the description and the error message; carries the size and the forced state. | .ds-text-field |
label |
label |
yes | Text naming the field. Provides the accessible name; activating it focuses the control. | .ds-text-field__label |
control |
value |
yes | The editable box: the native single-line text input, drawn by the component. Receives focus, holds the value and shows the placeholder while empty. | .ds-text-field__control |
description |
description |
no | Optional help text under the control (format, example, consequence), associated with it as its description. | .ds-text-field__description |
error-message |
errormessage |
no | Text describing the error when the field is invalid, associated with the control as a description. Shown only while invalid. Shown when invalid |
.ds-text-field__error-message |
Hover or focus a row to outline the part in the rendering above; hover the rendering to find its row.
Properties
| Property | Type | Options / default | Binds to / affects | Description |
|---|---|---|---|---|
size |
enum |
|
affects
controlweb:
data-size |
Control height, inline padding and text size scale together; label, description and error keep their size. |
label required |
string | — |
binds
label |
What the field asks for, as a noun phrase (Email address). Always visible. |
value |
string | — |
binds
control |
Current text of the field. |
placeholder |
string | — | Short hint shown while the field is empty, in a lighter color. It disappears when people type: never the label, never an instruction people must remember. | |
description |
string | — |
binds
description |
Help text: expected format, example, or what the value is used for. |
errorMessage |
string | — |
binds
error-message |
What is wrong and how to fix it, shown when the field is invalid. |
required |
boolean |
false
|
The field must be filled to submit the form. | |
readonly |
boolean |
false
|
web:
data-readonly |
The value can be read, focused and copied but not edited (a generated identifier). |
invalid |
boolean |
false
|
web:
data-invalid |
The value failed validation: the border takes the error color and the error message is shown. |
disabled |
boolean |
false
|
web:
data-disabled |
Non-interactive and skipped by the keyboard; the value stays visible. Prefer read-only when people may need to read or copy the value. |
States
| State | Trigger | Applies to | Description |
|---|---|---|---|
default |
interaction | every platform and input | Resting: an outlined box on the default surface. |
hover |
interaction | input: pointer | Pointer over the control. The box takes the surface hover tint; the border does not change. Needs a pointing device: touch has no hover. |
focus-visible |
interaction | every platform and input | Focus on the control, from the keyboard or a pointer: an editable field shows the shared focus ring outside its border however it was focused. |
invalid |
property (invalid) |
every platform and input | Validation failed: the border takes the error color and the error message is shown under the control. |
readonly |
property (readonly) |
every platform and input | Not editable: the box takes the level 1 surface; the value stays focusable and selectable. |
disabled |
property (disabled) |
every platform and input | Non-interactive. Box, value, label and description drop to the shared disabled colors; hover and focus do not apply. |
Layout
direction verticalalign stretchjustify startwrap falsesizing fill
- Label, control, description and error message stack, separated by container.gap; the control fills the width of the component, which the layout sizes.
- Inline padding, minimum height and text size come from the size option; the corner radius and the border width do not change.
Accessibility
Target: WCAG 2.2 level AA. Every statement cites the trusted source it comes from.
ARIA role textbox.
Accessible name: Computed from the label, associated with the control as its label element on the web. The description and the error message are exposed as the accessible description, not as part of the name. The placeholder is exposed as a placeholder and used as a name only when nothing else names the control, which never happens here. WAI tutorials: Labeling controlsHTML-AAM: accessible name computation of input elements (associated label element)APG: Describing with aria-describedbyHTML-AAM: placeholder attribute (aria-placeholder, a last-resort name)
| Key | Action | Platforms | Sources |
|---|---|---|---|
| Tab | Moves focus to the control. A disabled control is not focusable and is skipped. | Web | WCAG 2.1.1 Keyboard (A)HTML: focusable areaHTML: disabled form controls |
Every platform
- Expose the textbox role, the name, the value and the required, read-only, disabled and invalid states through the platform's native text input, so assistive technology receives them without extra work. WCAG 4.1.2 Name, Role, Value (A)WAI-ARIA 1.2: textbox role
- Every field has a visible label associated with the control programmatically; help text that people need to fill the field (format, example) is visible too, as the description. WCAG 3.3.2 Labels or Instructions (A)WCAG 1.3.1 Info and Relationships (A)WAI tutorials: Labeling controlsWAI tutorials: Form instructions (required fields, placeholder text, aria-describedby)HTML: the label element (for attribute, labeled control)
- When the value is invalid, identify the field and describe the error in text next to it: the error message names what is wrong and how to fix it, and is associated with the control. WCAG 3.3.1 Error Identification (A)WAI tutorials: User notifications (error messages)WCAG technique G83: text descriptions for required fields not completed
- Mark required fields in the label or the description, never by the error color alone. WCAG technique H90: indicating required form controlsWCAG failure F81: required or error fields identified by color onlyWCAG 1.4.1 Use of Color (A)
- Label, value, description and error text contrast at least 4.5:1 with their background in every color mode and enabled state: label and value 19.07:1 (light) and 11.27:1 (dark) on the default surface, description 12.87:1 and 8.80:1, placeholder 6.08:1 and 6.36:1, error text 4.52:1 and 4.97:1 (4.26:1 on a level 1 surface in light mode, a Figma library limit tracked as a known issue). Disabled text is inactive and exempt. WCAG 1.4.3 Contrast (Minimum) (AA)
- The border is what identifies the field, so it contrasts at least 3:1 with the adjacent surface in every color mode: 12.87:1 (light) and 8.80:1 (dark) on the default surface at rest, 12.14:1 and 8.06:1 on the hover tint; the error border 6.12:1 and 3.82:1; the focus ring 5.79:1 and 12.80:1. Disabled is exempt. WCAG 1.4.11 Non-text Contrast (AA)Understanding WCAG 1.4.11 Non-text Contrast: user interface components and boundaries
- A visible focus indicator appears when the control receives focus, drawn outside its border. WCAG 2.4.7 Focus Visible (AA)WCAG 2.4.13 Focus Appearance (AAA)
- Every size is a pointer target of at least 24 x 24 CSS pixels through its minimum height: 32, 40 and 48 pixels. WCAG 2.5.8 Target Size (Minimum) (AA)
- Color transitions of the control are removed when the user asks for reduced motion. WCAG 2.3.3 Animation from Interactions (AAA)MDN: prefers-reduced-motion
Web
- Use the native input type=text (or email, tel, url for those purposes) without a list attribute: it exposes the textbox role, its value and states without ARIA. ARIA in HTML: input type=text (role textbox)HTML-AAM: input type=text (role textbox)
- Associate the label with a label element whose for attribute names the control's id; reference the description and the error message with aria-describedby, so they are read after the name. WCAG technique H44: label elements to associate text labels with form controlsWCAG technique ARIA1: aria-describedby for a descriptive labelAPG: Describing with aria-describedby
- The placeholder is a short hint, never the label and never an instruction: it disappears as soon as people type, and is not exposed as the name when a label exists. HTML: the placeholder attribute (not an alternative to a label)WAI tutorials: Form instructions, placeholder textHTML-AAM: placeholder attribute (aria-placeholder, a last-resort name)
- Say that the value is invalid with aria-invalid on the control, and that a field is required with the required attribute, which the browser exposes as required. WCAG technique ARIA21: aria-invalid to indicate an error fieldWAI-ARIA 1.2: aria-invalidHTML: the required attributeHTML-AAM: required attribute (aria-required)WAI tutorials: Validating input (the required attribute)
- The focus ring is drawn with :focus-visible, which browsers match whenever an editable field has focus, pointer or keyboard. MDN: :focus-visible
- Fields that collect information about the person (name, email, phone, address) carry the matching autocomplete token, so browsers and assistive technology can identify their purpose. The template passes it through its extra attributes. WCAG 1.3.5 Identify Input Purpose (AA)WCAG technique H98: HTML autocomplete attributes to identify the purpose of input fields
- A read-only field keeps its value focusable and selectable and is exposed as read-only; a disabled field leaves the focus order. Prefer read-only when people may need to read or copy the value. HTML: the readonly attributeHTML-AAM: readonly attribute (aria-readonly)HTML: disabled form controlsAPG: Focusability of disabled controls
iOS
- Show a hint in a text field to help communicate its purpose; because placeholder text disappears when people start typing, include a separate label describing the field. Apple HIG: Text fields, best practices (hints and labels)
- Apple recommends a hit region of at least 44 x 44 pt; the small and medium sizes (32 and 40 pt) fall under it on iOS today: the Figma library has no platform heights. Apple HIG: Accessibility, mobility (control sizes)
Android
- Android recommends a touch target of at least 48 x 48 dp; the small and medium sizes (32 and 40 dp) fall under it on Android today. Expose an invalid value with the error semantics so the message is announced with the field. Android accessibility: use large, simple controlsJetpack Compose accessibility: minimum touch target sizesJetpack Compose accessibility: error semantics of components
WCAG success criteria
| Criterion | Level | Understanding |
|---|---|---|
| 1.3.1 Info and Relationships | A | Understanding 1.3.1 |
| 1.3.5 Identify Input Purpose | AA | Understanding 1.3.5 |
| 1.4.1 Use of Color | A | Understanding 1.4.1 |
| 1.4.3 Contrast (Minimum) | AA | Understanding 1.4.3 |
| 1.4.11 Non-text Contrast | AA | Understanding 1.4.11 |
| 2.1.1 Keyboard | A | Understanding 2.1.1 |
| 2.3.3 Animation from Interactions | AAA (beyond the AA target) | Understanding 2.3.3 |
| 2.4.7 Focus Visible | AA | Understanding 2.4.7 |
| 2.4.13 Focus Appearance | AAA (beyond the AA target) | Understanding 2.4.13 |
| 2.5.8 Target Size (Minimum) | AA | Understanding 2.5.8 |
| 3.3.1 Error Identification | A | Understanding 3.3.1 |
| 3.3.2 Labels or Instructions | A | Understanding 3.3.2 |
| 4.1.2 Name, Role, Value | A | Understanding 4.1.2 |
References
- WAI-ARIA 1.2: textbox role W3C Accessible Rich Internet Applications (WAI-ARIA) · standard
- WAI-ARIA 1.2: aria-invalid W3C Accessible Rich Internet Applications (WAI-ARIA) · standard
- APG: Describing with aria-describedby W3C WAI-ARIA Authoring Practices Guide (APG) · guidance
- APG: Focusability of disabled controls W3C WAI-ARIA Authoring Practices Guide (APG) · guidance
- ARIA in HTML: input type=text (role textbox) W3C ARIA in HTML · standard
- HTML-AAM: input type=text (role textbox) W3C HTML Accessibility API Mappings (HTML-AAM) · standard
- HTML-AAM: accessible name computation of input elements (associated label element) W3C HTML Accessibility API Mappings (HTML-AAM) · standard
- HTML-AAM: placeholder attribute (aria-placeholder, a last-resort name) W3C HTML Accessibility API Mappings (HTML-AAM) · standard
- HTML-AAM: readonly attribute (aria-readonly) W3C HTML Accessibility API Mappings (HTML-AAM) · standard
- HTML-AAM: required attribute (aria-required) W3C HTML Accessibility API Mappings (HTML-AAM) · standard
- HTML: the label element (for attribute, labeled control) WHATWG HTML Living Standard · standard
- HTML: the placeholder attribute (not an alternative to a label) WHATWG HTML Living Standard · standard
- HTML: the readonly attribute WHATWG HTML Living Standard · standard
- HTML: the required attribute WHATWG HTML Living Standard · standard
- HTML: disabled form controls WHATWG HTML Living Standard · standard
- HTML: focusable area WHATWG HTML Living Standard · standard
- MDN: :focus-visible MDN Web Docs · reference
- MDN: prefers-reduced-motion MDN Web Docs · reference
- WAI tutorials: Labeling controls W3C WAI tutorials · guidance
- WAI tutorials: Form instructions (required fields, placeholder text, aria-describedby) W3C WAI tutorials · guidance
- WAI tutorials: Form instructions, placeholder text W3C WAI tutorials · guidance
- WAI tutorials: Validating input (the required attribute) W3C WAI tutorials · guidance
- WAI tutorials: User notifications (error messages) W3C WAI tutorials · guidance
- WCAG technique H44: label elements to associate text labels with form controls W3C WAI Understanding and Techniques for WCAG 2.2 · guidance
- WCAG technique H90: indicating required form controls W3C WAI Understanding and Techniques for WCAG 2.2 · guidance
- WCAG technique H98: HTML autocomplete attributes to identify the purpose of input fields W3C WAI Understanding and Techniques for WCAG 2.2 · guidance
- WCAG technique G83: text descriptions for required fields not completed W3C WAI Understanding and Techniques for WCAG 2.2 · guidance
- WCAG technique ARIA1: aria-describedby for a descriptive label W3C WAI Understanding and Techniques for WCAG 2.2 · guidance
- WCAG technique ARIA21: aria-invalid to indicate an error field W3C WAI Understanding and Techniques for WCAG 2.2 · guidance
- WCAG failure F81: required or error fields identified by color only W3C WAI Understanding and Techniques for WCAG 2.2 · guidance
- Understanding WCAG 1.4.11 Non-text Contrast: user interface components and boundaries W3C WAI Understanding and Techniques for WCAG 2.2 · guidance
- Apple HIG: Text fields, best practices (hints and labels) Apple Human Interface Guidelines and accessibility documentation · platform
- Apple HIG: Accessibility, mobility (control sizes) Apple Human Interface Guidelines and accessibility documentation · platform
- Android accessibility: use large, simple controls Android accessibility documentation · platform
- Jetpack Compose accessibility: minimum touch target sizes Android accessibility documentation · platform
- Jetpack Compose accessibility: error semantics of components Android accessibility documentation · platform
Content
- The label is a noun phrase naming the information asked for: Email address, Company name. Sentence case, no colon, no trailing punctuation.
- The description gives the format or an example when it is not obvious: We will only use it to send your receipt.
- The placeholder, when any, is a short example of a valid value (name@example.com), never an instruction and never the label.
- The error message says what is wrong and how to fix it: Enter an email address with an @.
Changelog
0.1.02026-10-03 — Initial draft (decisions field-colors, sizes, label-description-error, single-line-text-only). Native text input in a wrapper with a label element, a description and an error message; states default, hover, focus-visible, invalid, readonly, disabled; sizes small, medium, large. Contrast on the default surface, light / dark: label and value 19.07 / 11.27, description 12.87 / 8.80, placeholder 6.08 / 6.36, error text 4.52 / 4.97 (4.26 on level 1 in light mode), border at rest 12.87 / 8.80 and 12.14 / 8.06 on hover, error border 6.12 / 3.82, focus ring 5.79 / 12.80.