Skip to content
ShineUI
Install

InputText

The plain field, done right.

<sui-input-text>Text inputsStable

About

Single-line text field with the shared label, help and error lines. Form-associated: it posts name=value with a plain form and binds to React, Vue (v-model) and Angular forms. Always give it a label; for an unlabeled toolbar search, use sui-icon-field.

Usage

<sui-input-text label="Customer email" name="email" type="email" placeholder="you@company.com" help="Receipts are sent here."></sui-input-text> <script type="module"> import { defineCustomElements } from "@shineui/core/loader"; import "@shineui/core/shineui.css"; defineCustomElements(); const el = document.querySelector("sui-input-text"); el.addEventListener("suiChange", (e) => console.log(e.detail)); </script>
import { SuiInputText } from "@shineui/react"; import "@shineui/core/shineui.css"; export function Example() { return <SuiInputText label="Customer email" name="email" type="email" placeholder="you@company.com" help="Receipts are sent here." onSuiChange={(e) => console.log(e.detail)} />; }
<script setup> import { SuiInputText } from "@shineui/vue"; import "@shineui/core/shineui.css"; const onChange = (e) => console.log(e.detail); </script> <template> <SuiInputText label="Customer email" name="email" type="email" placeholder="you@company.com" help="Receipts are sent here." @sui-change="onChange" /> </template>
import { Component } from "@angular/core"; import { SuiInputText } from "@shineui/angular"; @Component({ standalone: true, imports: [SuiInputText], template: `<sui-input-text label="Customer email" name="email" type="email" placeholder="you@company.com" help="Receipts are sent here." (suiChange)="onChange($event)"></sui-input-text>`, }) export class Example { onChange(e: CustomEvent) { console.log(e.detail); } }

Basic

The label is bound to the inner input, so clicking it focuses the field. Help text is wired through aria-describedby and read after the label.

<div style="max-width:340px"> <sui-input-text label="Customer email" name="email" type="email" placeholder="you@company.com" help="Receipts are sent here."></sui-input-text> </div>

Sizes

md is the default, one height with every select, picker and button, so a form built from defaults lines up. lg is the roomier field for a sign-in or marketing page; sm packs into table rows.

<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:14px;align-items:end"> <sui-input-text label="Large" size="lg" placeholder="42px"></sui-input-text> <sui-input-text label="Medium" size="md" placeholder="34px"></sui-input-text> <sui-input-text label="Small" size="sm" placeholder="28px"></sui-input-text> </div>

States

The error line replaces help and carries role="alert", so it announces when it appears. A read-only field still posts its value; a disabled one does not.

<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px"> <sui-input-text label="Customer email" required placeholder="you@company.com" help="Receipts are sent here."></sui-input-text> <sui-input-text label="Customer" error="Customer is required."></sui-input-text> <sui-input-text label="Terms" value="Net 30" disabled></sui-input-text> <sui-input-text label="Invoice #" value="INV-1043" readonly help="Assigned automatically."></sui-input-text> </div>

Named from context

A settings row already prints the term, so a label would double it. aria-label on the element names the input, and aria-labelledby names it from the term the row draws. A visible label wins over both, so the name a reader hears is always one on the page.

Display name Workspace URL
<div style="display:grid;grid-template-columns:150px 1fr;gap:10px 16px;align-items:center;max-width:440px;font:460 13.5px var(--font-sans)"> <span id="it-term-name">Display name</span> <sui-input-text value="Falls City Supply" aria-labelledby="it-term-name"></sui-input-text> <span>Workspace URL</span> <sui-input-text value="falls-city" aria-label="Workspace URL"></sui-input-text> </div>

Constraints and keyboard hints

pattern and minlength are the native attributes, checked by the browser and reported as patternMismatch or tooShort with its own message. inputmode and enterkeyhint on the host reach the input; spellcheck and autocapitalize apply from the host. The validity methods answer as on a native input.

Type two digits, then check.
<div style="display:grid;gap:14px;max-width:320px"> <sui-input-text id="it-zip" label="ZIP code" name="zip" pattern="[0-9]{5}" minlength="5" maxlength="5" inputmode="numeric" enterkeyhint="done" help="Five digits."></sui-input-text> <div style="display:flex;gap:8px;flex-wrap:wrap"> <sui-button id="it-check" variant="outline" size="md" label="Check validity"></sui-button> <sui-button id="it-taken" variant="outline" size="md" label="Mark as taken"></sui-button> </div> <output id="it-out" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Type two digits, then check.</output> </div>
// setup(root): properties and events setup(root) { const zip = root.querySelector('#it-zip'); const out = root.querySelector('#it-out'); root.querySelector('#it-check').addEventListener('click', () => { const ok = zip.checkValidity(); const flags = ['valueMissing', 'patternMismatch', 'tooShort', 'tooLong', 'typeMismatch', 'badInput', 'customError'].filter((k) => zip.validity[k]); out.textContent = ok ? 'Valid.' : `Invalid: ${flags.join(', ') || 'see the message'}. ${zip.validationMessage}`; }); root.querySelector('#it-taken').addEventListener('click', () => { zip.setCustomValidity(zip.value ? `${zip.value} is already assigned.` : ''); out.textContent = zip.value ? 'Custom message set; check again, or clear it by emptying the field and marking again.' : 'Custom message cleared.'; }); }

In a form

Each field posts one entry under its host name. Empty posts an empty string, a disabled field posts nothing, and Reset restores the value each field mounted with.

Submit to see what posts.
<form id="it-form" style="display:grid;gap:14px;max-width:340px"> <sui-input-text label="Customer" name="customer" required value="Falls City Supply" autocomplete="organization"></sui-input-text> <sui-input-text label="Contact" name="contact" placeholder="Billing contact"></sui-input-text> <sui-input-text label="Invoice #" name="invoice" value="INV-1043" readonly help="Assigned automatically."></sui-input-text> <sui-input-text label="Terms" name="terms" value="Net 30" disabled></sui-input-text> <div style="display:flex;gap:8px"> <sui-button type="submit" variant="brand" label="Save invoice"></sui-button> <sui-button type="reset" variant="outline" label="Reset"></sui-button> </div> <output id="it-out" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Submit to see what posts.</output> </form>
// setup(root): properties and events setup(root) { const form = root.querySelector('#it-form'); const out = root.querySelector('#it-out'); form.addEventListener('submit', (e) => { e.preventDefault(); out.textContent = [...new FormData(form)].map(([k, v]) => `${k}=${v || '(empty)'}`).join(' '); }); form.addEventListener('reset', () => { out.textContent = 'Reset. Submit to see what posts.'; }); }

Events

suiInput fires on every keystroke, suiChange on commit at blur or Enter. Both carry the current value as the detail. suiFocus and suiBlur follow the input, the pair every text field carries, so a host arms a hint on focus the same way on each.

Idle.
<div style="display:grid;gap:10px;max-width:340px"> <sui-input-text id="it-events" label="Purchase order" name="po" placeholder="PO-0000"></sui-input-text> <span id="it-log" style="font:460 13px/1.6 var(--font-sans);color:var(--ink-muted)">Idle.</span> </div>
// setup(root): properties and events setup(root) { const log = root.querySelector('#it-log'); const el = root.querySelector('#it-events'); el.addEventListener('suiFocus', () => { log.textContent = 'suiFocus'; }); el.addEventListener('suiBlur', () => { log.textContent = 'suiBlur'; }); el.addEventListener('suiInput', (e) => { log.textContent = `suiInput ${e.detail || '(empty)'}`; }); el.addEventListener('suiChange', (e) => { log.textContent = `suiChange ${e.detail || '(empty)'}`; }); }

Keyboard

KeyAction
TabMoves to the field and on to the next control; the host itself takes no tab stop.
Printable charactersType into the native input; suiInput fires on every keystroke.
EnterSubmits the surrounding form, as a native input does.

API

<sui-input-text>

Properties

PropertyAttributeTypeDefaultDescription
autocompleteautocompletestringstring | undefinedNative autocomplete hint. inputmode and enterkeyhint set on the host reach the input the same way, and spellcheck and autocapitalize on the host apply to it as they do to any descendant.
disableddisabledbooleanfalseDisables the field.
errorerrorstringstring | undefinedError message; paints the field in the danger tone and sets aria-invalid.
helphelpstringstring | undefinedHelp text under the field. Replaced by error when set.
invalidinvalidbooleanfalseDanger border without a message (validation handled elsewhere).
labellabelstringstring | undefinedField label. Without one the field takes the host's aria-label or aria-labelledby as its name, for a settings row that already prints the term; a visible label wins over both.
maxLengthmaxlengthnumbernumber | undefinedMaximum number of characters, as the native maxlength.
minLengthminlengthnumbernumber | undefinedNative minlength: a value shorter than this, once the user has typed, fails as tooShort.
namenamestringstring | undefinedForm control name. Reflected: entry construction reads the content attribute, and the three framework bindings set props, so an unreflected name would post nothing.
patternpatternstringstring | undefinedNative pattern: a regular expression the whole value must match. The browser checks it and the field reports patternMismatch on its validity, the way required reports valueMissing.
placeholderplaceholderstringstring | undefinedPlaceholder text.
readOnlyreadonlybooleanfalseDisplay only: the text can be selected and copied but not edited; the field still posts its value.
requiredrequiredbooleanfalseMarks the field required (asterisk + valueMissing validity when empty).
sizesize"lg" | "md" | "sm"'md'Control height: sm 28 for table rows, md 34 for forms and toolbars (the default, shared with the select family and the buttons, so a form built from defaults is one height), lg 42 for a sign-in or marketing page.
typetypeSuiInputType"email" | "password" | "search" | "tel" | "text" | "url"'text'Native input type. One union across the four elements that wrap a native text input.
valuevaluestring''Current value. Mutable; listen to suiInput / suiChange to observe edits.

Events

EventDetailReactVueDescription
suiBlurvoidonSuiBlur@sui-blurFires when the input loses focus; no detail. Does not bubble.
suiChangestringonSuiChange@sui-changeFires on commit (blur or Enter, native change); detail is the final value.
suiFocusvoidonSuiFocus@sui-focusFires when the input takes focus; no detail. Every text field carries the same pair, so a host arms a hint or a launcher the same way on each without listening for the native focusin. Does not bubble.
suiInputstringonSuiInput@sui-inputFires on every keystroke; detail is the current value.

Methods

MethodSignatureDescription
select()select() => Promise<void>Selects the field text.
setFocus()setFocus() => Promise<void>Moves keyboard focus into the field.

Types

SuiInputType typeThe native type values the text-input skin draws. One union for the whole family: the four elements that wrap a native text input each listed a different subset in prose, which read as a rule and was not one. Anything outside this set has its own element (sui-input-number, sui-date-picker, sui-color-picker) or its own skin.
No members.

Theming

Every design token this element can render with, read from its own stylesheets and the ones it imports. Set a token on :root or on any ancestor and every use below follows it. The values shown are the ones in the theme you are viewing. The list is read per stylesheet, so an element that imports a shared drawing module lists that module's tokens too, whether or not it paints with all of them.

See also