Form field

Collects one answer: a visible label, the input, an optional hint and an error that says how to fix it.

Molecule · stable · v1.0.0 · owner: Site system lead · contract 19 of 20 points (open: figma)

Do and don't

Do: A required email

Rendered by form-field.ts

Open example: Form field · Do: A required email Use the full-page example to follow page links.

Visible contact label, right keyboard and autocomplete; the input is actually empty.

Do: An error with a fix

Rendered by form-field.ts

Open example: Form field · Do: An error with a fix Use the full-page example to follow page links.

The existing contact error says how to fix the empty required field.

Do: An optional company

Rendered by form-field.ts

Open example: Form field · Do: An optional company Use the full-page example to follow page links.

The existing optional label is visible; the input does not acquire a required attribute.

Do: Message hint and error together

Rendered by form-field.ts

Open example: Form field · Do: Message hint and error together Use the full-page example to follow page links.

Both existing descriptions remain associated with the empty textarea; a form summary is not fabricated.

Do: Japanese required email

Rendered by form-field.ts

Open example: Form field · Do: Japanese required email Use the full-page example to follow page links.

The existing Japanese contact label uses the same native email semantics.

Do: Japanese message error and hint

Rendered by form-field.ts

Open example: Form field · Do: Japanese message error and hint Use the full-page example to follow page links.

The existing Japanese hint and fix are both referenced; native language review remains a separate obligation.

Don't: Placeholder as label

Rendered by form-field.ts

Open example: Form field · Don't: Placeholder as label Use the full-page example to follow page links.

A field without a visible label fails WCAG 3.3.2.

Don't: An error without a fix

Rendered by form-field.ts

Open example: Form field · Don't: An error without a fix Use the full-page example to follow page links.

The reader does not know what to change.

Rules

RuleWhyCheck
Every field has a visible label tied to its input.Placeholders disappear and are not read reliably.Automated: axe label rule (tests/site/contact-form.spec.ts)
Errors say how to fix them and are listed at the top on submit.People fix what they can find.Automated: tests/site/contact-form.spec.ts
Personal fields set autocomplete.Paste and autofill must work (WCAG 3.3.7, 3.3.8).Automated: tests/site/contact-form.spec.ts

API

PropTypeRequiredDefaultWhat it does
idstringYesThe input's id (the label's for).
namestringYesThe submitted name.
labelstringYesThe question, visible.
typetext | email | textareaNo"text"The input type.
requiredbooleanNofalseRequired fields are the default; optional ones are marked.
optionalstringNoThe word 'optional' in the page's language.
hintstringNoHelp shown between label and input.
errorstringNoThe error message, shown after a failed submit.
autocompletestringNoThe autocomplete token, so browsers and password managers fill it.
maxlengthnumberNoThe limit the server enforces.

Contract details

The full contract: identity, anatomy, variants, states, tokens, layout, motion, accessibility, content, tests

Identity

AspectValue
Idform-field
LayerMolecules
Categoryinput
OwnerSite system lead
Statusstable
Version1.0.0
Sourceapps/showcase/site/system/molecules/form-field.ts (private repository; the contract and the render function are this one module)
VersionDateChange
1.0.02026-10-26Label, input, optional hint and error, for the contact form, the health check and the survey.

Use when

  • Any question in a form. Why: One field pattern makes every form on the site behave the same.

Do not use when

  • For a single on-page control in a showcase. Why: That is a Control atom.
  • Without a visible label. Why: Placeholders vanish while typing and are not labels (WCAG 3.3.2).

Anatomy

PartWhat it is
rootdiv.field
labellabel (with span.optional when optional)
inputinput or textarea
hintp.small between label and input
errorp.field-error after the input, linked by aria-describedby

Made of: text, callout, tokens:type, tokens:colour, tokens:radius.

Used in: contact-form, cost-calculator, health-check, read-your-stylesheet, stage-finder, survey.

Variants

VariantWhenWhy
textShort answers.One line.
emailEmail.The right keyboard on phones (inputmode=email).
textareaA message.Room to write.

States

StateRendered or not applicable
defaultLabel above an outlined input
hoverNot applicable: Inputs do not change on hover; the cursor already shows they take text.
focus-visibleA 2 px --focus outline.
activeNot applicable: Typing is the active state, shown by the focus outline.
disabledNot applicable: Fields are never disabled; a field that cannot be used is not shown.
loadingNot applicable: Submission loading belongs to the containing form and submit Button; this field renderer has no busy lifecycle.
emptyEmpty input with its label; no placeholder text.
errorMadder message under the input, aria-invalid=true and a linked description. The containing form owns the error summary.
currentNot applicable: Not a navigation element.
visitedNot applicable: Not a link.
expandedNot applicable: Does not open.

Tokens

PartToken
input.border--rule-hair --line-strong
input.background--surface
input.font--type-body
label.font--type-small-strong
error.color--madder-ink
input.min-height48 px
--form-field-outline-rule-hairvar(--rule-hair)
--form-field-outline-focusvar(--focus)
--form-field-outline-offset-space-2var(--space-2)
--form-field-font-type-labelvar(--type-label)
--form-field-letter-spacing-ls-labelvar(--ls-label)
--form-field-gap-space-3var(--space-3)
--form-field-font-type-small-strongvar(--type-small-strong)
--form-field-margin-left-space-3var(--space-3)
--form-field-font-type-smallvar(--type-small)
--form-field-color-ink-2var(--ink-2)
--form-field-gap-space-2var(--space-2)
--form-field-gap-space-5var(--space-5)
--form-field-margin-space-1var(--space-1)
--form-field-color-madder-inkvar(--madder-ink)
--form-field-padding-space-6var(--space-6)
--form-field-padding-space-7var(--space-7)
--form-field-background-surfacevar(--surface)
--form-field-border-top-rulevar(--rule)
--form-field-border-top-inkvar(--ink)
--form-field-border-radius-radiusvar(--radius)
--form-field-margin-bottom-space-2var(--space-2)
--form-field-font-type-strongvar(--type-strong)
--form-field-margin-bottom-space-3var(--space-3)
--form-field-gap-space-4var(--space-4)
--form-field-padding-top-space-4var(--space-4)
--form-field-padding-space-4var(--space-4)
--form-field-border-rule-hairvar(--rule-hair)
--form-field-border-line-strongvar(--line-strong)
--form-field-transition-dur-fastvar(--dur-fast)
--form-field-transition-ease-standardvar(--ease-standard)
--form-field-border-color-inkvar(--ink)
--form-field-background-inkvar(--ink)
--form-field-color-cottonvar(--cotton)
--form-field-box-shadow-space-2var(--space-2)
--form-field-box-shadow-saffronvar(--saffron)
--form-field-outline-offset-space-1var(--space-1)
--form-field-padding-space-5var(--space-5)
--form-field-margin-top-space-9var(--space-9)
--form-field-border-inkvar(--ink)
--form-field-color-inkvar(--ink)
--form-field-font-size-fs-smallvar(--fs-small)
--form-field-margin-top-space-3var(--space-3)

Layout

AspectValue
max--w-text
breakpointsRequired review at 390, 768, 1024 and 1440 px; execution is not implied by this declaration; full width of the form.
wrappingLabels and errors wrap.
textExpansionJapanese labels wrap; no fixed heights.

Spacing and rhythm

AspectValue
internal--space-3 between label, hint, input and error.
external--space-6 between fields.
rhythmInside a form panel.

Motion

AspectValue
NoneFields do not animate; errors appear in place on submit.

Runtime and reuse

  1. Render-only element; mount any registered child enhancements separately. Preserve required Site CSS, fonts, responsive and locale rules from nativeElement.

Accessibility passport

AspectValue
roletextbox
nameThe label (for/id)
keyboardTab to it, type; Enter submits from text fields.
focusIn form order; the error summary takes focus on a failed submit.
screenReaderReads label, hint (aria-describedby), the required state and the error.
contrastInk on surface 14:1; madder-ink error 6.4:1.
target48 px tall.

Content

SlotLengthVoice
label4 wordsThe question, plainly
error1 sentenceWhat to do: 'Enter your work email.'

Good: “Your name” · “Enter your work email.”

Bad: “Name*” · “Invalid input”

Japanese: Strings come from the page's i18n files (contact/i18n/ja.json), reviewed per page.

Icons and illustrations

None: this element uses no icon or illustration of its own.

Decisions and exceptions

Usage

48 uses across the built pages (measured by npm run site:system).

Tests

AspectValue
unittests/unit/site-system.test.ts: renders every do and don't example through the registry and checks the classes it owns
visualNot yet: there are no screenshot baselines; the Library page is checked for sideways scroll at 1440 px by tests/site/system.spec.ts
a11ytests/site/system.spec.ts: axe (0 serious or critical) on the element's Library page in light and dark
parityscripts/site/system/check.ts: Library examples are rendered by the same render function the site imports

Figma

not-yet: The Loom Site Figma library is the last stage of the plan; this element is not mapped yet.

Open in Storybook

Start with a conversation about your system.

We read your system, show you what we found, and agree with your team what to fix first.