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.
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) orCtrl+Enter(multiline). - Cancel on
Escapeand 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"witharia-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-visibleon 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}