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
Please fix the following errors:
As it appears on official documents.
Build with the skill
No package to install — hand this to your AI to generate or migrate the component in your stack.
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
fieldto wrap every form control — it provides label, error, and helper structure. - Replace
field-helperwithfield-erroron invalid state — never show both. - Show
form-validation-summaryat 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-errorelement must have a stableid— the paired control usesaria-describedbypointing to it.form-validation-summary:role="alert"— announced immediately on submit.- Each summary item link:
aria-describedbypointing to the corresponding fieldid. - Async indicator:
aria-live="polite"region adjacent to the field — announce "Checking…" on start and result on completion. - Error cleared: update
aria-describedbytarget 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