← All components

Primitive · 4 of 13

TextField

The multi-line sibling of InputText, but built differently: the label is in normal flow rather than absolutely positioned, and the focus halo lives on the wrapper via :focus-within so it surrounds the whole box.

Vue island src/components/common/TextField.vue rendered live from compiled/TextField.js auto-resize

Props

PropTypeNotes
id *stringWires label to textarea.
label *stringIn flow above the field, select-none.
placeholder *string12px, nudged down with placeholder:pt-1.5.
modelValuestringEmitted on change, not input — see findings.

Note what is absent compared to InputText: no isRequired, no isError, no errorMessage. The textarea cannot express a validation state at all.

Stories

The real TextField.vue, compiled and mounted. Type into the second one — it grows. The textarea has overflow: hidden and its height is set from scrollHeight on every input, so it never scrolls internally.

Empty label="Message" min-height 7.5rem. Hover lifts the whole box #0F0F15 → #1C1C27 (gray-1100 → gray-900).
Filled & auto-resizing :focus-within Focus tints the text --color-hovered and haloes the wrapper at 50%/80% alpha — a touch stronger than InputText's 50%/60%.

Findings

The model updates on change, not input. The template binds @change="$emit('update:modelValue', …)" while the auto-resize runs on @input. So the parent's v-model only syncs when the textarea loses focus. A form that validates or enables its submit button as the user types will look frozen, and a submit triggered without blurring first can read a stale value. InputText emits on input, so the two fields behave differently in the same form.

The auto-resize does not run on programmatic value changes. Height is only recalculated in the @input handler. Setting modelValue from the parent — restoring a draft, prefilling — leaves the box at its minimum height with the content clipped, because overflow is hidden and nothing re-measures.

No error state exists. Unlike InputText there is no isError path, so a failed required-message validation has nowhere to render. The consultation form works around this by not validating the message field.

The halo is on the wrapper, and that is correct. Because the label sits inside the same box, putting the shadow on the textarea alone would draw a glowing rectangle underneath the label. Using :focus-within on the container keeps the focus ring around the whole control.