Zetta v1.6

Components · Forms & selection

Field

Form field wrapper composing label, control, helper text, error message, and validation summary. Split from zetta-form-v1_4.md (removed in v1.5) in v1.4.

Preview

As it appears on official documents.
Email address is invalid.
Username is available.

Build with the skill

No package to install — hand this to your AI to generate or migrate the component in your stack.

Prompt for your AI
Apply the Zetta "Field" component to this project using the zetta-design-md skill.

WHEN TO USE
- Migrating or fixing existing code (the common case): audit the current implementation against the `field` spec and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `field` spec in this project's stack.

AUDIT & FIX (when the component already exists)
Compare it to the skill's spec and fix, don't just append: hardcoded colors/hex, off-scale radius, wrong fonts, dark/navy fills, shadows used in place of borders (or missing borders), brand lime used as a CTA or interaction (must be primary indigo), and any missing or wrong hover / focus / active / disabled / invalid state or missing 2px focus ring. Replace each with the correct Zetta token.

ALWAYS
- Pull the authoritative spec and tokens from the skill — never guess or hardcode values.
- Use this project's own component conventions and framework idioms.
- Honor the Zetta guardrails (brand = identity only, primary = interaction, shadows for overlays) and verify in Light, Dark, and Accessibility.

Anatomy & rules

┌─ field ─────────────────────────────────┐
│  Email address *  ?     ← Label         │
│  ┌─────────────────────────────────┐    │
│  │  you@company.com                │    │  ← control (any input)
│  └─────────────────────────────────┘    │
│  Helper text or error message           │  ← field-helper OR field-error
└─────────────────────────────────────────┘

Rule: field-error replaces field-helper when the field is invalid. Never show both simultaneously.

State Helper text Error text Async indicator
Default Visible ({colors.muted}) Hidden Hidden
Invalid Hidden Visible ({colors.danger-text}) Hidden
Async validating Hidden Hidden Spinner visible
Async success Hidden Hidden Check icon ({colors.success})
Async error Hidden Visible Hidden

Do

  • Always use field to wrap every form control — it provides label, error, and helper structure.
  • Replace field-helper with field-error on invalid state — never show both.
  • Show form-validation-summary at the top of the form on submit with errors.

Don't

  • Don't skip the field wrapper and place controls without label association.
  • Don't validate on every keystroke — use blur or submit timing.
  • Helper text: proactive guidance in one sentence. "Must be at least 8 characters." — not an error description.
  • Error text: lead with what went wrong. "Email address is invalid." — not "Please enter a valid email."
  • Validation summary title: "Please fix the following errors:" or "N errors found."
  • Summary items: mirror the exact field-error text. Link text = error text.

Keyboard

Key Behavior
Tab Moves through form controls
Validation summary item Enter Scrolls to and focuses the invalid field

Accessibility

  • field-error element must have a stable id — the paired control uses aria-describedby pointing to it.
  • form-validation-summary: role="alert" — announced immediately on submit.
  • Each summary item link: aria-describedby pointing to the corresponding field id.
  • Async indicator: aria-live="polite" region adjacent to the field — announce "Checking…" on start and result on completion.
  • Error cleared: update aria-describedby target so screen readers re-read the (now empty) region.

Spec

Values and token references resolved straight from the Zetta spec.

field
display
flex
flexDirection
column
gap
{spacing.xs}
field-helper
textColor
{colors.muted}
fontFamily
Geist
fontSize
12px
fontWeight
400
lineHeight
1.5
field-error
textColor
{colors.danger-text}
fontFamily
Geist
fontSize
12px
fontWeight
400
lineHeight
1.5
field-async-indicator
iconSize
{icons.sizeMap.sm}
iconColor
{colors.muted}
position
absolute
right
10px
top
50%
transform
translateY(-50%)
field-success-indicator
iconSize
{icons.sizeMap.sm}
iconColor
{colors.success}
position
absolute
right
10px
top
50%
transform
translateY(-50%)
form-validation-summary
backgroundColor
{colors.danger-bg}
borderColor
{colors.danger}
borderWidth
1px
borderLeftWidth
4px
borderRadius
{rounded.base}
padding
{spacing.md}
marginBottom
{spacing.md}
form-validation-summary-title
fontFamily
Geist
fontSize
14px
fontWeight
600
textColor
{colors.danger-text}
marginBottom
{spacing.xs}
form-validation-summary-list
listStyle
none
padding
0
margin
0
display
flex
flexDirection
column
gap
{spacing.xs}
form-validation-summary-item
fontFamily
Geist
fontSize
13px
fontWeight
400
textColor
{colors.danger-text}
cursor
pointer
textDecoration
underline
textDecorationStyle
dotted