GitHub

Text Field

Self-contained label, input, and help/error text in one form-associated Web Component. Participates in native forms via ElementInternals and emits a composed change event.

Stable wc import '@esa/ecology/esa-text-field';

Preview

<esa-text-field label="Full name" placeholder="Jane Doe" help-text="As it appears on the permit."></esa-text-field>

Types

The type attribute maps straight to the native input type.

<esa-text-field label="Email" type="email" placeholder="you@example.com"></esa-text-field>
<esa-text-field label="Password" type="password"></esa-text-field>
<esa-text-field label="Count" type="number"></esa-text-field>

Sizes

<esa-text-field label="Extra small" size="xs" placeholder="Extra small"></esa-text-field>
<esa-text-field label="Small" size="sm" placeholder="Small"></esa-text-field>
<esa-text-field label="Medium" size="md" placeholder="Medium"></esa-text-field>
<esa-text-field label="Large" size="lg" placeholder="Large"></esa-text-field>

States

<esa-text-field label="Required" required placeholder="Cannot be empty"></esa-text-field>
<esa-text-field label="With error" error-text="This field is required." required></esa-text-field>
<esa-text-field label="Disabled" disabled value="Read only"></esa-text-field>

Form participation

The element is form-associated. Submitting logs the FormData entry for contact.

<form>
  <esa-text-field label="Contact" name="contact" placeholder="Type then submit"></esa-text-field>
  <button type="submit">Submit</button>
</form>

API

PropTypeDefaultDescription
label string '' Visible label rendered above the input.
type 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' 'text' Native input type.
value string '' Current value; mirrored to the form on every input.
placeholder string '' Placeholder shown when empty.
help-text string '' Helper text shown below the input.
error-text string '' Error message; replaces help text and turns the field red when present.
size 'xs' | 'sm' | 'md' | 'lg' 'md' Control size.
required boolean false Marks the field required and renders an asterisk on the label.
disabled boolean false Disables interaction.

Events

EventTypeDefaultDescription
change CustomEvent<{ value: string }> Fired on every input. Composed and bubbling; detail.value is the current string.

Accessibility

  • Renders a native <input> associated to its <label> via for/id.
  • Sets aria-invalid="true" while error-text is present.
  • The required asterisk carries an aria-label="required".
  • Visible focus ring via --focus-ring-* tokens.

Theming surface

Every public token this component reads, extracted from its source at build time. The Resolves to column walks each token's real lineage — component → semantic → primitive → raw value — so you can see exactly where re-pointing it lands. A spoke re-skins by overriding these in its theme-<slug>.css; component internals are never edited. No hook for what you need? File it with /request-lego.

TokenTierResolves to
--focus-ring-color component #65ba74 via --color-border-focus--color-grass-8
--focus-ring-width component 2px
--form-bg component #fcfcfc via --color-surface--color-gray-1
--form-bg-disabled component #f0f0f0 via --color-disabled-bg--color-gray-3
--form-border-color component #cecece via --color-border--color-gray-7
--form-border-color-error component #e5484d via --color-danger--color-red-9
--form-border-color-focus component #65ba74 via --color-border-focus--color-grass-8
--form-border-width component 1px
--form-error-color component #ce2c31 via --color-danger-strong--color-red-11
--form-font-size-lg component clamp(0.875rem, 0.77rem + 0.52vw, 1.125rem) via --type-size-300
--form-font-size-md component clamp(0.75rem, 0.66rem + 0.44vw, 0.9375rem) via --type-size-200
--form-font-size-sm component clamp(0.625rem, 0.56rem + 0.32vw, 0.75rem) via --type-size-100
--form-font-size-xs component clamp(0.5rem, 0.44rem + 0.3vw, 0.625rem) via --type-size-050
--form-height-lg component 48px
--form-height-md component 40px
--form-height-sm component 32px
--form-height-xs component 28px
--form-help-color component #838383 via --color-text-muted--color-gray-10
--form-help-gap component 0.25rem via --spacing-100
--form-label-color component #646464 via --color-text-secondary--color-gray-11
--form-label-font-size component var(--_field-font-size)
--form-label-font-weight component 500 via --font-weight-medium
--form-label-gap component 0.25rem via --spacing-100
--form-padding-x-lg component 1rem via --spacing-400
--form-padding-x-md component 0.75rem via --spacing-300
--form-padding-x-sm component 0.625rem via --spacing-250
--form-padding-x-xs component 0.5rem via --spacing-200
--form-padding-y-lg component 0.75rem via --spacing-300
--form-padding-y-md component 0.5rem via --spacing-200
--form-padding-y-sm component 0.375rem via --spacing-150
--form-padding-y-xs component 0.25rem via --spacing-100
--form-placeholder-color component #838383 via --color-text-muted--color-gray-10
--form-radius-lg component 0.5rem via --radius-200
--form-radius-md component 0.5rem via --radius-200
--form-radius-sm component 0.25rem via --radius-100
--form-radius-xs component 0.25rem via --radius-100
--form-text-color component #202020 via --color-text-primary--color-gray-12
--form-affix-bg ad-hoc #f0f0f0 via --color-surface-sunken--color-gray-3
--form-affix-border-color ad-hoc #cecece via --form-border-color--color-border--color-gray-7
--form-affix-color ad-hoc #646464 via --color-text-secondary--color-gray-11
--color-danger-strong semantic #ce2c31 via --color-red-11
--font-sans semantic 'DM Sans', sans-serif
--transition-fast semantic 150ms ease
--type-size-050 semantic clamp(0.5rem, 0.44rem + 0.3vw, 0.625rem)
--type-size-100 semantic clamp(0.625rem, 0.56rem + 0.32vw, 0.75rem)
--type-size-150 semantic clamp(0.6875rem, 0.61rem + 0.38vw, 0.875rem)
--type-size-200 semantic clamp(0.75rem, 0.66rem + 0.44vw, 0.9375rem)
--type-size-300 semantic clamp(0.875rem, 0.77rem + 0.52vw, 1.125rem)