Eunomia v1.0.0-beta.2
AllAngularReactNext.jsJavaScriptVue
EunomiaSalmonForestVioletOceanGoldFireCustom…
🇬🇧 English🇫🇷 Français

Input

Input is a form field for a single line of text, covering plain text, email addresses, numbers, phone numbers, URLs, search terms, and passwords. It wraps a native text field internally, so it gets built-in validation, keyboard editing, and autofill for free, while adding a leading icon, a clear button, a password-visibility toggle, and an optional click-to-edit display. For multi-line text, see Textarea. For a closed list of options, see Select.

Dependencies

eun-label · if it has a label eun-icon · if using an icon, clearable, or the password toggle eun-button · in readonly-available edit mode
Overview API Examples Accessibility

When to use

Use input for any single line of typed text: a name, an email, a password, a search box. For text that can run to several lines (a comment, a message), reach for textarea instead. For picking one value from a closed list you already know, use select, since it's faster to fill and impossible to mistype. See Alternatives below for a side by side comparison.

Install & usage

Pick a framework in the toolbar above and these snippets adapt.

npm install @eunomia/elements
import "@eunomia/elements/input.js";
import "@eunomia/elements/label.js";
<eun-input label="Email address" placeholder="jane.doe@email.com"></eun-input>

Importing the file registers <eun-input> as a custom element, with no further setup needed. It works with any framework, or none, since it's a standard web component.

Alternatives

You want .. Prefers Text that can run to several lines, such as a comment or a message eun-textarea To pick one value from a closed list you already know, faster to fill and impossible to mistype eun-select

Guidance

  • Always set label (or slot one): see the Accessibility tab
  • Prefer native constraint attributes (type, required, minlength, maxlength, pattern, min, max) over custom validators when a native equivalent exists
  • Use hint for format guidance ("Include your country code"), not instructions repeated from the placeholder
  • Reserve readonly-available for genuinely inline-editable data (a profile field), not every field in a long form
  • Using the placeholder as a replacement for label: it disappears the moment the user types
  • Pairing clearable with type="password": it's disabled there on purpose (see Examples)
  • Overriding colors with inline styles instead of the --field-* CSS variables

Live testing

Properties

Attributes

NameTypeDefaultDescription
readonly-availablebooleanfalseWhether the field can switch between a static display and an editable one
type'text' | 'email' | 'number' | 'tel' | 'url' | 'search' | 'password''text'The native input type to apply
default-valuestringThe default value applied when the field connects, and restored on form reset
placeholderstringThe placeholder shown when the field is empty
iconEunomiaIconNameAn optional leading icon
clearablebooleanfalseWhether a clear button is shown when the field has a value
autocompletestring'off'The native autocomplete attribute forwarded to the field
minlengthnumberThe native minimum length constraint forwarded to the field
maxlengthnumberThe native maximum length constraint forwarded to the field
patternstringThe native pattern constraint forwarded to the field
minstringThe native minimum constraint forwarded to the field
maxstringThe native maximum constraint forwarded to the field
stepstringThe native step constraint forwarded to the field
empty-value-labelstring'No value set'Text displayed in place of the value when readonly and empty
confirm-labelstring'Save'The label of the confirm button in edit mode
cancel-labelstring'Cancel'The label of the cancel button in edit mode
aria-label-clearstring'Clear field'The accessible label of the clear button
aria-label-show-passwordstring'Show password'The accessible label of the show password button
aria-label-hide-passwordstring'Hide password'The accessible label of the hide password button

Import the exact TypeScript type behind any property above, see Types.

Properties

JS-only — no matching HTML attribute, set these from a script or a template binding.

NameTypeDefaultDescription
checkLocalValidatorsReads native HTML constraint validation from the underlying field element (`required`, `minlength`, `maxlength`, `pattern`, `min`, `max`, `type="email"`, etc.).
variant'outline' | 'fill' | 'underline''outline'The visual variant to apply to the field
labelstringThe title label of the field, rendered through the label component
instructionsstringInstructions displayed below the label
namestringThe field name
valuestringThe field value
disabledbooleanfalseWhether the field is disabled
readonlybooleanfalseWhether the field is read only
requiredbooleanfalseWhether the field is required
hideErrorbooleanfalseWhether the errors are hidden
validatorsArray<Validators<string>>The list of validation rules applied to the field value
readonlyAvailablebooleanfalseWhether the field can switch between a static display and an editable one

Slots

NameDescription
iconReplaces the leading icon entirely with any content, not just an icon element. Falls back to icon when empty

Events

NameTypeDescription
eunchangeChangeEventFired on every value change
euncommitCommitEventFired when an edit is confirmed in readonlyAvailable mode

Every event above follows the same naming convention, covered in Events.

CSS custom properties

NameDescription
--field-backgroundSets the background color of the field
--field-border-colorSets the border color of the field
--field-border-color-hoverSets the border color on hover
--field-border-color-focusSets the border color on focus
--field-border-radiusSets the corner radius of the field
--field-paddingSets the padding of the field
--field-text-colorSets the text color of the field
--field-placeholder-colorSets the color of the placeholder
--field-icon-colorSets the color of the leading icon and action buttons
--field-error-colorSets the color of the error message
--field-hint-colorSets the color of the hint message
--field-focus-outline-colorSets the color of the focus outline
--field-edit-confirm-colorSets the color of the confirm button in readonly-available edit mode
--field-edit-cancel-colorSets the color of the cancel button in readonly-available edit mode

Types

type selects the native input type: text (default), email, number, tel, url, search, or password.

<eun-input
  label="Email"
  type="email"
  placeholder="jane.doe@email.com"
></eun-input>

Instructions

instructions is separate from hint: it renders below the label itself (via the internal eun-label), stays visible no matter what's typed, and is never replaced by an error message. Reach for it for context about the field itself, why it's asked or how the value is used, and reserve hint for guidance about the value being typed. See Forms & validation: Composing a label for the full comparison.

<eun-input
  label="Email address"
  type="email"
  placeholder="jane.doe@email.com"
  instructions="We'll never share this with third parties"
></eun-input>

Number constraints

type="number" accepts the same native min, max, and step constraint attributes as a plain <input>, so out-of-range or off-step values are rejected by checkLocalValidators, exactly like required/pattern/etc.

<eun-input
  label="Quantity"
  type="number"
  min="1"
  max="10"
  step="1"
  hint="Between 1 and 10"
></eun-input>

Variants

variant selects the visual style: outline (default, bordered box), fill (filled background, no border), or underline (bottom border only).

<eun-input label="Fill" variant="fill"></eun-input>
<eun-input label="Underline" variant="underline"></eun-input>

Part of the shared vocabulary covered in Variants, alongside every other component that reuses it.

With a leading icon

<eun-input
  label="Email address"
  type="email"
  icon="email"
  placeholder="jane.doe@email.com"
></eun-input>

Need something other than a eun-icon, or a different icon library entirely? Slot your own content into icon instead. It replaces the prop-driven icon entirely.

<eun-input label="Email address" type="email" placeholder="jane.doe@email.com">
  <svg slot="icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
    <path d="M4 4h16v16H4z"></path>
    <path d="m4 4 8 8 8-8"></path>
  </svg>
</eun-input>

Clearable

Shows a clear button once the field has a value. Not available on type="password": a clear button there would defeat masking the value without offering anything a Backspace/Delete selection doesn't already.

<eun-input label="Search" type="search" clearable value="eunomia"></eun-input>

Password

Renders a show/hide toggle that flips the underlying native type between password and text.

<eun-input label="Password" type="password"></eun-input>

States

<eun-input label="Disabled" disabled value="Can't edit"></eun-input>
<eun-input
  label="Readonly"
  readonly
  value="Can't edit, still submits"
></eun-input>
<eun-input label="Required" required hint="This field is mandatory"></eun-input>

readonly and disabled are not interchangeable: a readonly field still submits its value and stays focusable/selectable, while a disabled one does neither. Try tabbing to (or clicking into) the "Readonly" field above: it takes focus exactly like the others. See the Accessibility tab.

Error and validation state

Native constraints (required, minlength, pattern, …) are checked automatically. Custom rules go through validators, an array of { isValid, message } (or { isInvalid, message }, or a Standard Schema validator like Zod/Valibot). Type fewer than 4 characters below to see the hint switch to an error, and clear the field to see it switch back.

<eun-input
  id="username"
  label="Username"
  hint="At least 4 characters"
></eun-input>
const field = document.querySelector("#username");
field.validators = [
  {
    isValid: (value) => !value || value.length > 3,
    message: "Please enter at least 4 characters",
  },
];
field.addEventListener("eunchange", () => field.checkValidity(field.value));

Click to edit

Set readonly-available to let a field switch between a static, clickable display of its current value and a real editable input, instead of always being one or the other. Clicking the display (or activating it with Enter/Space) switches into edit mode, showing Save and Cancel buttons (confirmLabel/cancelLabel) alongside the field. Confirming dispatches euncommit with the new value and switches back to the static display, while canceling reverts to the previous value instead, with no event fired.

clearable still applies normally once editing starts: the static display never shows a clear button itself (there's nothing to clear from plain text), so click into the field below first.

<eun-input
  readonly-available
  label="Email address"
  value="jane.doe@email.com"
  clearable
></eun-input>
document
  .querySelector("eun-input[readonly-available]")
  .addEventListener("euncommit", (event) => save(event.target.value));

The static display is clickable and keyboard-activatable (Enter/Space) to enter edit mode. Confirming re-runs validation first: an invalid value stays in edit mode instead of committing. See the Accessibility tab for the full keyboard flow.

Complete form

A <form> of eun-input fields behaves like any native form: eun-button type="submit" submits it, type="reset" reverts every field to its initial value (including clearing fields that had none), and each field's name becomes a key in the submitted FormData. The JSON panel below updates live on every keystroke (eunchange) and on submit/reset, so you can see exactly what a server (or a JS submit handler) would receive. For the deeper patterns this example is built from (composing labels, choosing native constraints vs. custom validators, hint/error wording), see the Forms & validation guide.

Submit Reset
{}
<form id="my-form">
  <eun-input name="firstName" label="First name" required></eun-input>
  <eun-input
    name="email"
    label="Email address"
    type="email"
    icon="email"
    clearable
    required
  ></eun-input>
  <eun-input name="age" label="Age" type="number" min="0" max="120"></eun-input>
  <eun-button type="submit" rounded>Submit</eun-button>
  <eun-button type="reset" appearance="flat" rounded>Reset</eun-button>
</form>
const form = document.querySelector("#my-form");
const renderOutput = () => console.log(Object.fromEntries(new FormData(form)));

form.addEventListener("eunchange", renderOutput);
form.addEventListener("submit", (event) => {
  event.preventDefault(); // replace with a real submission
  renderOutput();
});
form.addEventListener("reset", () => setTimeout(renderOutput));

Theming

Colors come from the active theme's --eun-* tokens (see Theming): switch the toolbar above and every demo on this page repaints immediately. For one-off overrides, target the component's own --field-* custom properties instead of the theme tokens directly (full list in the API tab), since these are shared across every text-like field component (eun-input, eun-textarea, and eun-select), not just this one.

<eun-input
  label="Custom"
  style="
    --field-border-color: #b45309;
    --field-border-color-hover: #92400e;
    --field-border-color-focus: #92400e;
    --field-border-radius: 2px;
  "
></eun-input>

Keyboard interactions

When focus is on the field:

Key Action
Tab / Shift+Tab Moves focus in/out of the field, natively (single tab stop)
Any character key Types into the field, natively
Enter Submits the closest ancestor <form>, natively (unaffected by eun-input itself)

On the read-only click-to-edit display (readonly-available, not editing):

Key Action
Enter Enters edit mode
Space Enters edit mode

The clear button and password-visibility toggle are rendered tabindex="-1" on purpose: they're pointer-optimized shortcuts for actions a keyboard user can already do faster natively (select-all and type over the value, while the show/hide toggle has no keyboard-only equivalent need since the value being typed is already visible to its author at the OS input level). They remain reachable and operable by mouse, touch, and switch-access, and both still expose a proper accessible name and (for the toggle) aria-pressed, so screen-reader users navigating by their virtual cursor can find and activate them, and are simply excluded from the sequential Tab order.

Focus indicator

The native field renders a :focus-visible outline (--field-focus-outline-color, falling back to --eun-color-primary-500) that switches to --field-error-color while invalid, so the color itself communicates validity state to sighted keyboard users, not just the border. The underline variant suppresses this outline (the border-color change is kept as the sole focus indicator there) since an outline would visually clash with the bottom-only border.

Aria attributes

  • aria-invalid="true"|"false": kept in sync with valid
  • aria-required: set natively by the underlying required attribute
  • aria-label: set from the label property directly on the native field. Not aria-labelledby pointing at the rendered <eun-label>: eun-label renders its text inside its own shadow root, a different shadow tree than this field's <input>, and ID references (aria-labelledby, for) don't cross shadow-root boundaries in current browsers. Computing a plain-text aria-label from the same label property sidesteps that entirely and works everywhere today. If you only slot custom label content (no label property) instead of using the property, set your own aria-label on eun-input to match, since the automatic one only derives from the property.
  • aria-describedby="description": set on the field whenever a hint or error message is actually rendered below it (and only then, to avoid a dangling ID reference when nothing is shown), so the message is announced together with the field, not just visually adjacent to it
  • aria-pressed="true"|"false": on the password-visibility toggle, reflecting whether the value is currently shown as plain text
  • On the readonly-available static display, role="button" plus the same aria-label computed from label, so its accessible name identifies the field even though its visible text is the current value (or the empty-value placeholder)

Label accessibility

Always set label (or slot equivalent content into the label slot via eun-label's API): an input with no accessible name is one of the most common, and most disruptive, screen-reader failures. placeholder is never a substitute: it vanishes as soon as a value is typed, so anyone who tabs past a filled field, or who never saw the placeholder due to autofill, has no way to recover what the field is for.

Disabled vs. readonly

disabled removes the field from the tab order (tabindex="-1", aria-disabled="true") and excludes its value from form submission entirely. readonly (native readonly attribute, reflected as aria-readonly="true") keeps the field focusable and its value selectable and submitted, communicating "you can't change this right now", not "this doesn't apply". Prefer readonly over disabled whenever the value is still meaningful context for the user (e.g. a computed total), and reserve disabled for fields that are genuinely not part of the current interaction at all.

Error / validation state

Error messages replace the hint text in the same #description region (never shown alongside it, so screen-reader users aren't read stale guidance and an active error at once) and are linked via aria-describedby as described above. Prefer native constraint attributes (required, pattern, minlength, …) over custom validators where an equivalent exists: they're validated by the browser itself, so their semantics are already understood by assistive technology and autofill, independent of anything this component does.

Readonly-available / click-to-edit flow

The static display exposes role="button" and full keyboard access (Tab to reach it, Enter/Space to activate it), so it's operable without a pointer despite looking like plain text. Confirming an edit re-validates first: an invalid value keeps focus in the field and the error message shown, rather than silently discarding the edit or committing bad data.

Reference links

  • WAI-ARIA Authoring Practices: Forms patterns
  • WAI Web Accessibility Tutorials: Labeling Controls
  • WHATWG HTML: The input element