← All components

Primitive · 3 of 13

InputText

A stacked-label field: the label is absolutely positioned over the input, and the input reserves room for it with top padding. Focus is a halo, never an outline.

Vue island src/components/common/InputText.vue rendered live from compiled/InputText.js v-model

Props

PropTypeNotes
id *stringWires label[for] to input[id].
label *stringRendered over the field, always visible.
type *'text' | 'email'No tel, url or password.
placeholder *stringRenders at 12px in --color-body-secondary.
autocompletestringPassed straight through.
isRequiredbooleanAdds the asterisk icon and the native required attribute.
isErrorbooleanSwaps text and halo to the error ramp.
errorMessagestringOnly rendered when isError.
modelValuestringv-model via update:modelValue on input.

States

The real InputText.vue, compiled and mounted. Click into the fields — hover, focus, v-model and the error halo are all live.

Default type="text" label="Name" Resting fill #0F0F15; hover lifts to #1C1C27 (the site’s gray-1100 → gray-900).
Required isRequired={true} type="email" The asterisk is an inline SVG component in source, tinted --color-action.
Filled & focus :focus Two-layer violet halo at 50% and 60% alpha. Value text is --color-hovered.
Error isError={true} errorMessage="..." Halo switches to error-200/error-100; text to --color-error.

Findings

The error message is not announced. It renders in a plain <span> with no role="alert", no aria-live, and no aria-describedby linking it to the input. The input also never gets aria-invalid. A screen reader user gets no signal that the field failed — the error is conveyed by color and position only.

Error text is #B42318 at 12px — 3.01:1. Below the 4.5:1 floor for body text, and error copy is exactly the text a user most needs to read. The system carries error-100 #FE9A9A, which clears it comfortably (9.71:1) on the #0A0A0B page. Recorded in DESIGN.md §10.2.

Placeholder and label can collide. The label sits at pl-3 pt-3 and the input reserves pt-7, so the two clear each other at the default 14px label. A longer label that wraps to a second line will overlap the value, because the reserved padding is a fixed 1.75rem rather than measured.

Autofill is handled deliberately. Four :-webkit-autofill rules force a 1000px inset shadow in --color-surface-tertiary and re-tint the text to --color-heading, defeating Chrome's default light-yellow fill. Without this the field would flash white on a dark form.