Text field
Single-line free-text entry, covering every native input type that renders as a line box; search, number and password are variants, not separate components.
stable · light DOM · 4.0.0. Visible native controls retain FormData, labels, constraints, disabled fieldsets and reset. No duplicate form-value mechanism is installed.
Live variants
default
Shown on your profile and in the window title.
<j3w1-text-field id="example-text-field-default"><div class="text-field">
<label class="text-field-label" for="example-text-field-default-tf-name">Display name <span class="text-field-required" aria-hidden="true">*</span><span class="text-field-required sr-only"> required</span></label>
<div class="text-field-root">
<input class="text-field-input" id="example-text-field-default-tf-name" type="text" value="j3w1" placeholder="Your name" aria-describedby="example-text-field-default-tf-name-help example-text-field-default-tf-name-message" name="example-text-field-default-tf-name">
<span class="text-field-loading" aria-hidden="true">⋯</span>
</div>
<p class="text-field-help" id="example-text-field-default-tf-name-help">Shown on your profile and in the window title.</p>
<p class="text-field-message" id="example-text-field-default-tf-name-message">✕ Use 2 to 32 characters.</p>
</div></j3w1-text-field>password
Twelve characters or more; spaces allowed.
<j3w1-text-field id="example-text-field-password"><div class="text-field">
<label class="text-field-label" for="example-text-field-password-tf-pass">Passphrase <span class="text-field-required" aria-hidden="true">*</span><span class="text-field-required sr-only"> required</span></label>
<div class="text-field-root">
<input class="text-field-input" id="example-text-field-password-tf-pass" type="password" required="" value="correct horse" placeholder="At least 12 characters" aria-describedby="example-text-field-password-tf-pass-help example-text-field-password-tf-pass-message" name="example-text-field-password-tf-pass">
<button class="text-field-action" type="button" aria-pressed="false" aria-label="Reveal passphrase">reveal</button>
<span class="text-field-loading" aria-hidden="true">⋯</span>
</div>
<p class="text-field-help" id="example-text-field-password-tf-pass-help">Twelve characters or more; spaces allowed.</p>
<p class="text-field-message" id="example-text-field-password-tf-pass-message">✕ Too short.</p>
</div></j3w1-text-field>search
Escape clears the query.
<j3w1-text-field id="example-text-field-search"><div class="text-field">
<label class="text-field-label" for="example-text-field-search-tf-search">Search files <span class="text-field-required" aria-hidden="true">*</span><span class="text-field-required sr-only"> required</span></label>
<div class="text-field-root">
<span class="text-field-affix" aria-hidden="true">/</span>
<input class="text-field-input" id="example-text-field-search-tf-search" type="search" value="site.css" placeholder="Name or path" aria-describedby="example-text-field-search-tf-search-help example-text-field-search-tf-search-message">
<button class="text-field-action" type="button" aria-label="Clear search">clear</button>
<span class="text-field-loading" aria-hidden="true">⋯</span>
</div>
<p class="text-field-help" id="example-text-field-search-tf-search-help">Escape clears the query.</p>
<p class="text-field-message" id="example-text-field-search-tf-search-message">✕ No such path.</p>
</div></j3w1-text-field>number
Up and Down step by one; Shift steps by ten.
<j3w1-text-field id="example-text-field-number"><div class="text-field">
<label class="text-field-label" for="example-text-field-number-tf-gap">Inner gap <span class="text-field-required" aria-hidden="true">*</span><span class="text-field-required sr-only"> required</span></label>
<div class="text-field-root">
<input class="text-field-input text-field-input-numeric" id="example-text-field-number-tf-gap" type="number" value="14" min="0" max="64" step="1" placeholder="0" aria-describedby="example-text-field-number-tf-gap-help example-text-field-number-tf-gap-message" name="example-text-field-number-tf-gap">
<span class="text-field-affix" aria-hidden="true">px</span>
<span class="text-field-stepper">
<button class="text-field-action" type="button" aria-label="Increase inner gap">+</button>
<button class="text-field-action" type="button" aria-label="Decrease inner gap">−</button>
</span>
<span class="text-field-loading" aria-hidden="true">⋯</span>
</div>
<p class="text-field-help" id="example-text-field-number-tf-gap-help">Up and Down step by one; Shift steps by ten.</p>
<p class="text-field-message" id="example-text-field-number-tf-gap-message">✕ Between 0 and 64.</p>
</div></j3w1-text-field>affix
Host only; the path is fixed.
<j3w1-text-field id="example-text-field-affix"><div class="text-field">
<label class="text-field-label" for="example-text-field-affix-tf-site">Site <span class="text-field-required" aria-hidden="true">*</span><span class="text-field-required sr-only"> required</span></label>
<div class="text-field-root">
<span class="text-field-affix" aria-hidden="true">https://</span>
<input class="text-field-input" id="example-text-field-affix-tf-site" type="text" value="j3w1.github.io" placeholder="host" aria-describedby="example-text-field-affix-tf-site-help example-text-field-affix-tf-site-message" name="example-text-field-affix-tf-site">
<span class="text-field-affix" aria-hidden="true">/theme/</span>
<span class="text-field-loading" aria-hidden="true">⋯</span>
</div>
<p class="text-field-help" id="example-text-field-affix-tf-site-help">Host only; the path is fixed.</p>
<p class="text-field-message" id="example-text-field-affix-tf-site-message">✕ Not a valid host.</p>
</div></j3w1-text-field>Install and register
Use the exact version. If npm publication is pending, install the release tarball from the installation guide.
npm install --save-exact @j3w1/ui@4.0.0import '@j3w1/ui/tokens.css';
import '@j3w1/ui/styles/text-field.css';
import '@j3w1/ui/register/text-field';Insert the maintained markup above after loading these imports. Its native children provide accessible names and form behavior; the empty custom tag alone does not create a control.
Copy the complete implementation
npx --package=@j3w1/ui@4.0.0 j3w1-ui copy text-field --out ./vendor/j3w1/text-fieldThe CLI verifies every file before writing and refuses overwrites. Serve the output over HTTP and open index.html. Keep its modules, CSS, tokens and licence notices together.
Public API
attributes
| Name | Type / signature | Meaning |
|---|---|---|
disabled | CustomEvent | Disable the native controls owned by this instance. |
loading | CustomEvent | Mark the primary control busy and prevent duplicate activation. |
value | CustomEvent | Forwards value to the primary native control. |
required | CustomEvent | Forwards required to the primary native control. |
readonly | CustomEvent | Forwards readonly to the primary native control. |
checked | CustomEvent | Forwards checked to the primary native control. |
properties
| Name | Type / signature | Meaning |
|---|---|---|
disabled | boolean | Delegates disabled state to native children. |
name | string | Native control name; application form keys remain explicit. |
value | string | Primary native value; assignment does not fabricate user events. |
checked | boolean | Native checkbox state. |
required | boolean | Native required constraint. |
readOnly | boolean | Native readonly property. |
form | HTMLFormElement | null | Owning native form. Read only. |
methods
| Name | Type / signature | Meaning |
|---|---|---|
refresh | (): void | Reconnect behavior after replacing native child structure. |
focus | (options?: FocusOptions): void | Focus the first usable native control. |
checkValidity | (): boolean | Check owned native constraints. |
reportValidity | (): boolean | Report the first invalid owned control. |
clear | (): void | Clear an editable primary input and emit native input/change. |
stepBy | (amount: number): void | Step a number input by the supplied number of native steps. |
events
| Name | Type / signature | Meaning |
|---|---|---|
j3w1-change | CustomEvent | Native change from an owned control, alongside the original native event. |
Dependencies and verification
Registered components: none. Style dependencies: none.
Package presence establishes an available implementation. Actual passes depend on the tested artifact and environment. Read specification execution evidence; read packed-consumer evidence; inspect the complete state matrix.
Full implementation manifest, API and token dependency closure
Canonical specification
Text field text-field
Single-line free-text entry, covering every native input type that renders as a line box; search, number and password are variants, not separate components.
Report an issue with this component
View source and copy recipe · Download standalone example
Tokens
Focus a role to inspect its values; follow its link for persistent usage and source details.
| Part | Role | Value | Use |
|---|---|---|---|
root.bg | color.surface.inputapproveduse | #000000 | Form-control fill. |
root.border | color.border.controlapproveduse | #c81a1a | D-041 owner correction: red control and floating-frame borders, distinct from rose text. |
root.bg-hover | color.interaction.hover.bgapproveduse | #1c1010 | D-041 interaction.hover.bg: color.primitive.modern.hover. |
root.border-focus | color.border.activeobserveduse | #e53935 | Active or focused control border. |
root.ring | color.interaction.focus.ringobserveduse | #e53935 | 1px dashed focus ring on controls and rows. |
root.ring-invalid | color.interaction.focus.ring-containerobserveduse | #ffa2a7 | Focus colour for containers and explicitly mapped filled controls (D-015); component rules determine ring geometry. |
root.border-invalid | color.status.danger.borderobserveduse | #e53935 | Danger border, 2px on invalid inputs. |
root.border-disabled | color.border.disabledapproveduse | #482b2b | D-041 border.disabled: color.primitive.modern.divider. |
root.bg-disabled | color.interaction.disabled.bgapproveduse | #0d0707 | D-041 interaction.disabled.bg: color.primitive.modern.panel. |
root.border-readonly | color.border.dividerapproveduse | #482b2b | D-041 border.divider: color.primitive.modern.divider. |
input.text | color.text.defaultapproveduse | #e99499 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
input.text-disabled | color.text.disabledapproveduse | #a3676b | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
input.placeholder | color.text.placeholderapproveduse | #bd787d | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
input.caret | color.code.caretapproveduse | #ffa2a7 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
input.selection-bg | color.interaction.text-selection.bgobserveduse | #420f0c | D-041 subdued burgundy selection keeps rose text readable in hosts retaining its foreground. |
input.selection-text | color.interaction.text-selection.textobserveduse | #f4eeee | ::selection text. |
label.text | color.text.brightapproveduse | #ffa2a7 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
label.required-mark | color.status.danger.textobserveduse | #f07178 | D-041 readable error text on neutral surfaces and native warning tints. |
help.text | color.text.mutedapproveduse | #d4868b | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
message.text | color.status.danger.textobserveduse | #f07178 | D-041 readable error text on neutral surfaces and native warning tints. |
affix.text | color.text.mutedapproveduse | #d4868b | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
affix.border | color.border.dividerapproveduse | #482b2b | D-041 border.divider: color.primitive.modern.divider. |
action.text | color.action.tertiary.textapproveduse | #e99499 | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
action.bg-hover | color.action.tertiary.hover-bgobserveduse | #1c0a09 | Ghost and icon button hover. |
loading.glyph | color.text.mutedapproveduse | #d4868b | D-041 owner correction: rose is the default UI and reading foreground; near-white is reserved for document headings, highlights and contrast-critical fills. |
State matrix
Every declared state and combination, statically rendered; the default cell is live. Sizes: compact, comfortable (switch with the density control above).
| State | Text | Password with reveal | Search with clear | Number with stepper | With prefix and suffix |
|---|---|---|---|---|---|
default | Shown on your profile and in the window title. | Twelve characters or more; spaces allowed. | Escape clears the query. | Up and Down step by one; Shift steps by ten. | Host only; the path is fixed. |
hover | |||||
focus-visible | |||||
placeholder-shown | |||||
filled | |||||
required | |||||
invalid | |||||
invalid+focus-visible | |||||
invalid+hover | |||||
disabled | |||||
disabled+filled | |||||
read-only | |||||
read-only+focus-visible | |||||
loading |
Purpose
Single-line free-text entry for every native type that renders as a line
box (text, password, search, number, email, url, tel). Date and
time inputs are the date-picker and time-picker components.
Anatomy
Parts as listed in the anatomy.
States
| State | Visual | Non-colour channel |
|---|---|---|
| default | 1px color.border.control on color.surface.input; text color.text.default | — |
| hover | background → color.interaction.hover.bg; border unchanged | cursor: text |
| focus-visible | border → color.border.active; 1px dashed color.interaction.focus.ring ring at −2px | the ring |
| placeholder-shown | placeholder in color.text.placeholder, italic | italic |
| filled | as default with a value | — |
| required | * after the label in color.status.danger.text plus visually hidden “required” | required attribute; the mark |
| invalid | 2px color.status.danger.border; message with the ✕ glyph | border 1 → 2px; glyph; aria-invalid |
| invalid+focus-visible | the 2px danger border plus a 1px dashed color.interaction.focus.ring-container ring at −4px (the control form recoloured, never the 2px container form) | double boundary |
| invalid+hover | as invalid with the hover background | cursor |
| disabled | text color.text.disabled, border color.border.disabled, background color.interaction.disabled.bg; no hover; label and help keep their colours | disabled; cursor: not-allowed |
| disabled+filled | as disabled with a value | disabled |
| read-only | no box: 1px dotted color.border.divider bottom edge on color.surface.canvas; text color.text.default; focusable and selectable | readonly; dotted edge |
| read-only+focus-visible | the dotted edge unchanged, plus the dashed ring at −2px | the ring |
| loading | size unchanged; static ⋯ in color.text.muted after the input, before any trailing action; still editable | glyph; aria-busy |
Precedence: disabled > loading > invalid > hover; focus-visible is always drawn.
Keyboard
As in the key table; the label is not a stop, and clear or reveal precede the stepper buttons.
Accessibility
The clear button is type="button" with aria-label="Clear <label>"; the
reveal button toggles type between password and text and its own
aria-pressed. aria-invalid is never set on first paint. The message is
announced on submit through the form’s live region, not on every keystroke.
Text meets 4.5:1 and borders and rings 3:1; disabled text is exempt (house
floor 3:1). The box is at least 24×24 CSS pixels in compact.
Portability
Border, background and outline express everything; the only pseudo-element is the required mark. Toolkits without dashed outlines draw a solid 1px ring in the same colour; toolkits without a read-only style show read-only as default text. Both record the deviation.
Non-examples
Rounded corners. A floating or animated label. An underline-only field. A glow instead of the dashed ring. Red placeholder text. Disabling by opacity. Colour alone for the invalid state. A hover that changes the text colour.
Contrast report
20 declared pairs measured on resolved sRGB values with the WCAG 2.x formula; 0 waived (decorative only), 0 failing. A pass is a design target for the named role, not an application-wide conformance claim.
| Pair | State | Kind | Foreground | Background | Ratio | Min | Result |
|---|---|---|---|---|---|---|---|
| text-field default text | default | text | color.text.default #e99499 | color.surface.input #000000 | 9.16 | 4.5 | pass |
| text-field default border | default | ui | color.border.control #c81a1a | color.surface.input #000000 | 3.62 | 3 | pass |
| text-field hover text | hover | text | color.text.default #e99499 | color.interaction.hover.bg #1c1010 | 8.10 | 4.5 | pass |
| text-field hover border | hover | ui | color.border.control #c81a1a | color.interaction.hover.bg #1c1010 | 3.20 | 3 | pass |
| text-field focus-visible text | focus-visible | text | color.text.default #e99499 | color.surface.input #000000 | 9.16 | 4.5 | pass |
| text-field focus-visible border | focus-visible | ui | color.border.active #e53935 | color.surface.input #000000 | 4.97 | 3 | pass |
| text-field focus-visible outline | focus-visible | ui | color.interaction.focus.ring #e53935 | color.surface.input #000000 | 4.97 | 3 | pass |
| text-field placeholder-shown text | placeholder-shown | text | color.text.placeholder #bd787d | color.surface.input #000000 | 6.15 | 4.5 | pass |
| text-field placeholder-shown border | placeholder-shown | ui | color.border.control #c81a1a | color.surface.input #000000 | 3.62 | 3 | pass |
| text-field invalid text | invalid | text | color.text.default #e99499 | color.surface.input #000000 | 9.16 | 4.5 | pass |
| text-field invalid border | invalid | ui | color.status.danger.border #e53935 | color.surface.input #000000 | 4.97 | 3 | pass |
| text-field invalid+focus-visible text | invalid+focus-visible | text | color.text.default #e99499 | color.surface.input #000000 | 9.16 | 4.5 | pass |
| text-field invalid+focus-visible border | invalid+focus-visible | ui | color.status.danger.border #e53935 | color.surface.input #000000 | 4.97 | 3 | pass |
| text-field invalid+focus-visible outline | invalid+focus-visible | ui | color.interaction.focus.ring-container #ffa2a7 | color.surface.input #000000 | 10.98 | 3 | pass |
| text-field read-only text | read-only | text | color.text.default #e99499 | color.surface.canvas #000000 | 9.16 | 4.5 | pass |
| validation message on the panel surface | — | text | color.status.danger.text #f07178 | color.surface.default #0d0707 | 6.98 | 4.5 | pass |
| help text on the panel surface | — | text | color.text.muted #d4868b | color.surface.default #0d0707 | 7.22 | 4.5 | pass |
| disabled text (exempt; house floor 3:1) | disabled | ui | color.text.disabled #a3676b | color.interaction.disabled.bg #0d0707 | 4.48 | 3 | pass |
| disabled border (exempt) | disabled | ui | color.border.disabled #482b2b | color.interaction.disabled.bg #0d0707 | 1.58 | 1 | pass |
| read-only bottom edge (decorative; the field has no box) | read-only | ui | color.border.divider #482b2b | color.surface.canvas #000000 | 1.66 | 1 | pass |
Keys
| Key | Action |
|---|---|
| Tab / Shift+Tab | Moves focus in and out; affix actions (clear, reveal, stepper) are separate tab stops after the input. |
| Escape | Clears the value in the search variant when it is non-empty; otherwise nothing. |
| Enter | Submits the owning form (native). |
| Up / Down | Number variant steps by the step attribute; Shift multiplies by ten. |
For agents
Component JSON ·Implementation brief ·Build a task kit ·
Related: textarea select combobox search-field number-field field
Stress fixtures: FX-LONG FX-320 FX-360 FX-ZOOM-200 FX-I18N FX-RTL FX-RM FX-HC FX-TOUCH FX-DENSITY FX-STATE-MATRIX