Zetta v1.6

Components · Forms & selection

Inline edit

Edit a value where it lives. At rest it's plain text with a hover-revealed pencil; click and it becomes an input with confirm and cancel controls. No modal, no separate form — change the title, the field, the cell in place.

Preview

Project Aurora

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 "Inline edit" 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 `inline-edit` spec (variants: display, editing) and CORRECT every deviation — replace off-spec values, don't only add new ones.
- New component: build it to the `inline-edit` 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

Read mode — trigger="button" (default):
  Project name  ✏           ← edit icon button always visible (inline-edit-display-icon-visible)

Read mode — trigger="hover":
  Project name              ← icon hidden at rest
  Project name  ✏           ← revealed on hover / keyboard focus / tap

Edit mode:
  ╭─────────────────────╮
  │ Project name        │  [✓] [✕]   ← input + inline-edit-actions
  ╰─────────────────────╯
State Visual
View mode — trigger="button" (default) Text value shown with a persistent edit icon button at the trailing position
View mode — trigger="hover" Text value shown; edit icon hidden until hover, keyboard focus (focus-visible), or tap
Hover (trigger="hover") Edit icon (edit) revealed at trailing position
Focus-visible (trigger="hover") Edit icon revealed by keyboard focus — parity with hover
Editing Input field replaces text, confirm + cancel icons appear
Saving Input disabled, spinner shown
Saved Briefly shows checkmark, returns to view mode
Error Input border turns {colors.danger}, error message below
Disabled Text shown, no hover/edit affordance

When to use

  • Quick edits to single field values in detail panels, card headers, or table cells.
  • Task names, issue titles, field labels where navigating to a full form would be disruptive.

When not to use

  • Multi-field forms — use a Form component.
  • When validation is complex or requires server confirmation before closing — use a modal or side panel form.
  • Rich text or long-form content — use a dedicated editor.

Do

  • Default to trigger="button" (a persistent edit icon) where discoverability matters — touch-first surfaces, dense tables, or first-time flows.
  • With trigger="hover", reveal the affordance (inline-edit-display-icon-visible) on hover, on keyboard focus (focus-visible), AND on tap — never hover-only, so keyboard and touch users can discover and activate it.
  • Confirm on Enter (single-line) or Ctrl+Enter (multiline).
  • Cancel on Escape and revert to the original value.
  • Show inline validation error below the input before confirming.

Don't

  • Don't auto-save without a confirm action — silent saves can cause unintentional data changes.
  • Don't use inline edit for critical data (IDs, permanent identifiers) where accidental edits are costly.
  • Placeholder in edit mode: match the field context — "Enter title", "Add description".
  • Confirm button aria-label: "Save changes".
  • Cancel button aria-label: "Cancel edit".
  • Error message: specific — "Title cannot be empty." not "Invalid input."
  • Empty value display: show a placeholder text like "Click to add title" in {colors.placeholder}.

Keyboard

Key Behavior
Tab Move focus to the inline edit trigger
Enter / F2 Enter edit mode
Escape Cancel edit, restore previous value
Enter (in edit mode) Confirm the change (single-line)
Ctrl + Enter (in edit mode) Confirm the change (multiline)
Tab (in edit mode) Confirm and move focus to next editable field

When trigger="hover", moving keyboard focus to the control (focus-visible) reveals the edit affordance — the same reveal as pointer hover — so the control is never hover-only for keyboard users. Enter / F2 then enters edit mode regardless of trigger.

Accessibility

  • Read mode: role="button" with aria-label="Edit [field name]" on the display element.
  • trigger="hover" reveal parity: the edit affordance must not be hover-only. Reveal it on hover, on keyboard focus (:focus-visible on the control), and on tap/touch — so keyboard and touch users get the same discoverable affordance as pointer users. trigger="button" keeps the affordance persistently visible for all input methods.
  • Edit mode: standard <input> or <textarea> — no additional ARIA needed.
  • Announce mode change: use a visually hidden live region to announce "Editing [field name]" on enter and "Saved" or "Cancelled" on exit.
  • Confirm and cancel buttons must have aria-label ("Confirm", "Cancel").

Spec

Values and token references resolved straight from the Zetta spec.

inline-edit-display
display
inline-flex
alignItems
center
gap
{spacing.xs}
paddingX
{spacing.xs}
paddingY
2px
borderRadius
{rounded.sm}
fontFamily
Geist
fontSize
14px
fontWeight
400
lineHeight
1.40
textColor
{colors.ink}
cursor
pointer
borderWidth
1px
borderStyle
solid
borderColor
transparent
minHeight
28px
inline-edit-display-hover
borderColor
{colors.hairline}
backgroundColor
{colors.surface-muted}
inline-edit-display-icon
iconSize
{icons.sizeMap.sm}
iconColor
{colors.muted}
opacity
0
inline-edit-display-icon-visible
opacity
1
inline-edit-input
fontFamily
Geist
fontSize
14px
fontWeight
400
lineHeight
1.40
textColor
{colors.ink}
backgroundColor
{colors.surface-card}
borderColor
{colors.border-focus}
borderWidth
1px
borderRadius
{rounded.sm}
paddingX
{spacing.xs}
paddingY
2px
outline
2px solid {colors.border-focus}
outlineOffset
-1px
width
100%
minHeight
28px
inline-edit-actions
display
inline-flex
alignItems
center
gap
{spacing.xs}
marginLeft
{spacing.xs}
inline-edit-confirm
iconSize
{icons.sizeMap.md}
iconColor
{colors.success}
backgroundColor
{colors.success-bg}
borderRadius
{rounded.sm}
padding
{spacing.xs}
cursor
pointer
inline-edit-cancel
iconSize
{icons.sizeMap.md}
iconColor
{colors.muted}
borderRadius
{rounded.sm}
padding
{spacing.xs}
cursor
pointer
inline-edit-cancel-hover
backgroundColor
{colors.primary-bg}
iconColor
{colors.primary}