Eunomia v1.1.0
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
  • Assuming a typed value survives every form.reset(): React 19's <form action={fn}> triggers one on every submit, see Forms & validation

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-valuestring—The default value applied when the field connects, and restored on form reset
placeholderstring—The placeholder shown when the field is empty
iconEunomiaIconName—An 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
minlengthnumber—The native minimum length constraint forwarded to the field
maxlengthnumber—The native maximum length constraint forwarded to the field
patternstring—The native pattern constraint forwarded to the field
minstring—The native minimum constraint forwarded to the field
maxstring—The native maximum constraint forwarded to the field
stepstring—The native step constraint forwarded to the field
empty-value-labelstring—Text displayed in place of the value when readonly and empty. Defaults to the localized string for the current locale (English: "No value set")
confirm-labelstring—The label of the confirm button in edit mode. Defaults to the localized string for the current locale (English: "Save")
cancel-labelstring—The label of the cancel button in edit mode. Defaults to the localized string for the current locale (English: "Cancel")
clear-labelstring—The accessible label of the clear button. Defaults to the localized string for the current locale (English: "Clear field")
show-password-labelstring—The accessible label of the show password button. Defaults to the localized string for the current locale (English: "Show password")
hide-password-labelstring—The accessible label of the hide password button. Defaults to the localized string for the current locale (English: "Hide password")

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
checkLocalValidators—Reads native HTML constraint validation from the underlying field element (`required`, `minlength`, `maxlength`, `pattern`, `min`, `max`, `type="email"`, etc.).
appearance'outline' | 'flat' | 'underline''outline'How the field's surface is drawn: a bordered box, a muted background with no border, or a bottom border only
roundedbooleanfalseWhether the field is fully rounded, as a pill. A custom border radius still wins over it
size'xs' | 's' | 'm' | 'l' | 'xl''m'The field size. A field and a button of the same size share the same height
labelstring—The title label of the field, rendered through the label component
instructionsstring—Instructions displayed below the label
hideLabelbooleanfalseWhether the label is visually hidden. It still gives the field its accessible name, and the instructions stay visible
namestring—The field name
valuestring—The field value
disabledbooleanfalseWhether the field is disabled
readonlybooleanfalseWhether the field is read only
requiredbooleanfalseWhether the field is required
hideErrorbooleanfalseWhether the errors are hidden. The hint stays visible
invalidbooleanfalseShows the field as invalid without any message, for an error displayed elsewhere
errorstring—An error computed outside the field, by the form or by the server. While set, the field is invalid and shows this message right away
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
euneditcancelEditCancelEventFired when an edit is cancelled in readonlyAvailable mode, after the previous value is restored

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

CSS custom properties

NameDescription
--field-backgroundSets the background color of the field, the surface color by default for the outline appearance
--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-padding-blockSets the vertical padding, as one length applied to both sides. Ignored when --field-padding is set
--field-padding-inlineSets the horizontal padding, as one length applied to both sides. Ignored when --field-padding is set
--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
--field-font-sizeSets the font size of the field's value and placeholder
--field-line-heightSets the line height of the field's value and placeholder
--field-label-font-sizeSets the font size of the label
--field-message-font-sizeSets the font size of the hint, error messages and instructions

Field types

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>

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>

Password

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

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

Labels and instructions

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>

Hidden label

hide-label hides the label visually while keeping it as the field's accessible name, so screen readers still announce what the field is for. Use it when the context already makes the field's meaning obvious, such as an editable page title. The instructions, when set, stay visible.

<eun-input
  label="Collection title"
  value="Spring collection"
  readonly-available
  hide-label
></eun-input>

Display

Appearance

appearance selects the visual style: outline (default, bordered box), flat (muted background, no border), or underline (bottom border only).

<eun-input label="Flat" appearance="flat"></eun-input>
<eun-input label="Underline" appearance="underline"></eun-input>

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

Sizes

size goes from xs to xl, with m by default, like a button. A field and a button of the same size have the same height, so a search field next to an action button lines up without any adjustment.

Add
Add
<eun-input size="l" placeholder="Large"></eun-input>
<eun-button size="l">Add</eun-button>

The same size is available on every text field: search, select, input phone, date picker, time picker and color picker.

Rounded

Set rounded for a pill shape, like a button, without touching --field-border-radius. The inline padding grows so the text stays clear of the curve. Every single-line field and the toggle group accept it too.

Search
<eun-input rounded icon="search" placeholder="Search…"></eun-input>
<eun-button rounded>Search</eun-button>

With a leading icon

<eun-input
  label="Email address"
  type="email"
  icon="mail"
  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>

States and validation

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 and dispatches euneditcancel, so you can drop anything derived from the abandoned value, such as an error message.

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.

Integration

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="mail"
    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));

Server-side rendering

The label, the instructions and the field are drawn inside the input's shadow root, so a server-rendered input takes no space until the element upgrades, then pushes the content below it. Link field-ssr.css in the page's <head> to draw them from the first paint:

<link
  rel="stylesheet"
  href="node_modules/@eunomia/elements/dist/ssr/field-ssr.css"
/>

A bundler that resolves package exports can reach it as @eunomia/elements/field-ssr.css. The same stylesheet covers eun-input, eun-search, eun-textarea, eun-select, eun-counter, eun-date-picker, eun-time-picker and eun-color-picker. It reads label, required, instructions, placeholder, value, icon, appearance, disabled, readonly and the --field-* custom properties, written in the server-rendered markup. The label and the instructions take their final height, and the field its final box. A password's value is never written out. The required asterisk, the messages and the clear and password buttons show with the upgrade.

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>

The custom properties that change colors, borders, shadows and corners, at a glance. The API tab lists every property of the component.

Variable Sets
--field-background Sets the background color of the field, the surface color by default for the outline appearance
--field-border-color Sets the border color of the field
--field-border-color-hover Sets the border color on hover
--field-border-color-focus Sets the border color on focus
--field-border-radius Sets the corner radius of the field
--field-text-color Sets the text color of the field
--field-placeholder-color Sets the color of the placeholder
--field-icon-color Sets the color of the leading icon and action buttons
--field-error-color Sets the color of the error message
--field-hint-color Sets the color of the hint message
--field-focus-outline-color Sets the color of the focus outline
--field-edit-confirm-color Sets the color of the confirm button in readonly-available edit mode
--field-edit-cancel-color Sets the color of the cancel button in readonly-available edit mode

Keyboard and focus

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>, as a native text field does

On the password-visibility toggle (type="password"), the next tab stop after the field:

Key Action
Enter / Space Shows or hides the value as plain text

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

Key Action
Enter Enters edit mode
Space Enters edit mode

While editing, Enter in the field confirms the edit, like the save button, validation included.

The clear button is rendered tabindex="-1" on purpose: it's a pointer-optimized shortcut for an action a keyboard user can already do faster natively, by selecting the whole value and typing over it. It remains reachable and operable by mouse, touch, and switch-access, and still exposes a proper accessible name, so screen-reader users navigating by their virtual cursor can find and activate it. The password-visibility toggle stays in the Tab order instead, since nothing else lets a keyboard user check what they typed.

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 appearance 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.

Semantics and screen readers

Aria attributes

  • aria-invalid="true"|"false": true while the error is shown, or while invalid is set
  • 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. When the context already names the field, keep label and add hide-label rather than dropping the label.

Field states

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 the field in edit mode, shows its error and moves the focus back into it, rather than silently discarding the edit or committing bad data. Once the edit is confirmed or cancelled, the focus moves to the static display, so it isn't lost along with the edit buttons.

Reference links

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