Changing one small value — a name, a status, a quantity — sometimes gets a whole navigation to a
dedicated edit page for a change that takes one field and one second to type. That round trip
costs the reader a page load out and a page load back for a change that didn’t need either. But
inline editing isn’t free of cost either: it has to signal that a value is editable at all before
anyone clicks it, it has to make committing and cancelling both obvious, and it has to say what
happens to the value if the save fails while the reader has already moved on.The failure mode in each direction is different. Skip inline editing where it belongs and every
trivial change becomes a small ceremony. Use it where it doesn’t belong — a change with side
effects on other data, or one that genuinely needs its own validation and context — and the
reader edits a field with no idea what else just changed, or hits a save failure with nowhere to
see why.
Inline editing is for changes with no story to tell. The moment a change needs to explain
itself — what else it affects, why a value is rejected — it has outgrown a single field edited
in place, and belongs on Form validation’s
ground instead.
Value renders as plain text. A hover treatment (and a pencil icon, per Constraints) signals it’s editable
Entering edit mode
Click on the value (or its edit icon) replaces it with an Input or Select, focused and with the current value selected
Committing
Enter, or the commit icon. Saves the new value and returns to resting state
Cancelling
Escape, or the cancel icon. Reverts to the original value with no save attempted
Clicking outside the field
Treated as commit if the value is valid, cancel if it isn’t — see Constraints
Save succeeds
Returns to resting state showing the new value
Save fails
No component expresses this cleanly — see Known issues and Error handling. This page’s proposal: the field stays open, in its edited state, with the error rendered inline below it
An editable value shows a hover treatment and a pencil icon before it’s clicked. Neither is always visible at rest.
A permanently visible edit icon on every editable value in a dense list adds visual noise to a page’s resting state; a hover cue costs nothing until the reader is already pointing at the value.
TITAN-INLINE-02
A value editable by a single click needs no separate edit icon. A value where an accidental click would be costly, or that shares its space with another click target, gets an explicit icon-only edit button instead.
Click-to-edit is the lower-friction default; an explicit icon is the higher-friction choice reserved for where an accidental edit-mode entry is more than a minor annoyance.
TITAN-INLINE-03
Enter commits; Escape cancels and reverts to the original value. Both are always available alongside their icon-only equivalents.
A real, well-established convention across text-editing surfaces — a reader does not have to discover it in this one interface.
TITAN-INLINE-04
Commit and cancel controls are icon-only and each carries a Tooltip and an accessible name — never a bare icon with neither.
An icon-only button with no accessible name announces as “button” and nothing else.
TITAN-INLINE-05
A failed save keeps the field open, in its edited, unsaved state, with the error rendered inline. It never silently reverts to the old value.
Reverting on failure discards the reader’s typed correction with no way to recover it — the same reasoning applies to a failed destructive action that closes its dialog on failure.
TITAN-INLINE-06
Clicking outside a field with a valid, changed value commits it. Clicking outside a field with an invalid value cancels it and reverts.
Treating every outside click as cancel silently discards a valid edit the reader believed was saved; treating every outside click as commit can save a value that never passed validation.
TITAN-INLINE-07
Inline editing never surfaces a page-level error summary. A change that needs one belongs on Form validation instead.
A single field editing in place has no page-level chrome to summarize into — needing one is the signal the change has outgrown this pattern.
TITAN-INLINE-08
Navigating away from a page with an uncommitted inline edit follows Destructive confirmation’s existing guidance for unsaved edits, not a rule invented here.
One definition of “leaving with unsaved work,” not two that can disagree.
Entering edit mode moves focus into the field automatically — the reader who clicked or
keyboard-activated the value should not have to Tab into it separately.
The commit and cancel controls are reachable by Tab, in addition to their Enter and
Escape equivalents — a reader who cannot use keyboard shortcuts still needs a visible
path to both.
Each icon-only control’s accessible name states the action and the field it acts on (“Save
campaign name,” not “Save”) when a page has more than one inline-editable field, so a
screen-reader user moving between rows can tell them apart.
A failed save is announced (a live region, or the error text’s own aria-describedby
association to the field) — not only shown visually, per the same reasoning as
Input’s error state.
Reverting a failed save with no trace. The reader retyped a value, pressed save, and the
field quietly snapped back to the old value with no error and no way to tell what happened to
their edit.An edit affordance nobody can find. A value that’s editable only via a hover-revealed icon,
on a device or in a context where hovering isn’t possible, has no path into edit mode at all.Inline editing a value with real consequences. A field that looks like a quick inline edit
but actually changes billing, live routing, or another record’s data gives the reader no
indication that clicking away committed something bigger than the one visible field.No keyboard equivalent for commit or cancel. Requiring a mouse click on a tiny icon to save
or discard an inline edit, with no Enter or Escape alternative, breaks a
convention every reader already expects from editing text anywhere else.
No component in Titan expresses a failed action today — there is a loading state and a disabled state, and nothing between them for failure. TITAN-INLINE-05’s inline error treatment is this page’s proposal for what fills that gap, not a description of an existing primitive.
Inline editing borrows Enter and Escape rather than inventing its own
commit gesture, because the convention already exists everywhere else text is edited. A
reader who has never seen this specific composition still knows what those two keys do the
moment the field becomes editable — this pattern is deliberately unoriginal here.The failed-save behavior is argued from the same principle as a failed destructive action, not
derived independently. Reverting silently on failure and closing a dialog on failure destroy
the same thing — the reader’s belief that their input is still there. Once one of those cases is
settled, the other isn’t a new decision; it’s the same decision applied to a different surface.
Input and
Select, the fields this pattern swaps in during
editing. Button and
Tooltip, for the icon-only commit, cancel,
and edit affordances. Form validation, for a
change that has outgrown a single inline field.
Destructive confirmation,
for leaving a page with an uncommitted edit. TITAN-GAP-22
and Error handling, for the general treatment of
a failed action this page’s failed-save behavior works around.
Proposal — first pass, not an audited Invoca screen. No Titan design library or shipped
product surface was available while writing this page. What follows is built from general
interaction-design practice and from the constraints Foundations and Components already
establish — see Status.
Last modified on September 17, 2026
Was this page helpful?
{"docsConfig":{"theme":"mint","$schema":"https://mintlify.com/docs.json","name":"Invoca","colors":{"primary":"#00A87F","light":"#00A87F","dark":"#0EEEB7"},"logo":{"light":"https://mintcdn.com/invoca-5bd45748/9Md68DqArcq3WD3L/logo/light.svg?fit=max\u0026auto=format\u0026n=9Md68DqArcq3WD3L\u0026q=85\u0026s=3ed629c5be28d08bd1a6580e72b6dbfa","dark":"https://mintcdn.com/invoca-5bd45748/9Md68DqArcq3WD3L/logo/dark.svg?fit=max\u0026auto=format\u0026n=9Md68DqArcq3WD3L\u0026q=85\u0026s=b64ce8e5cfa4f8c7893b2738e8131f45","href":"https://www.invoca.com/"},"favicon":"https://mintcdn.com/invoca-5bd45748/9Md68DqArcq3WD3L/favicon.png?fit=max\u0026auto=format\u0026n=9Md68DqArcq3WD3L\u0026q=85\u0026s=c846713bb1afdf274154a01d7e9abb8d","appearance":{"default":"light"},"background":{"decoration":"gradient"},"navbar":{"primary":{"type":"button","label":"Invoca Academy","href":"https://academy.invoca.com/"}},"footer":{"socials":{"linkedin":"https://www.linkedin.com/company/invoca-com","youtube":"https://www.youtube.com/channel/UCLQwNQFnN1OV1ACnlXICPKw"}},"fonts":{"body":{"family":"Gt America","source":"https://cdn.prod.website-files.com/5d82e225060d003d65ddae98/677d87009656fca63c0c7d8f_GT-America-Standard-Regular.woff2","format":"woff2"}},"contextual":{"options":["copy","download-pdf","view","assistant","chatgpt","claude","perplexity"],"display":"header"},"metadata":{"timestamp":true},"styling":{"latex":true},"navigation":{"pages":[]}},"deployment":{"deploymentMetadataValue":{"subdomain":"invoca-5bd45748","hasAudioNarration":false,"actualSubdomain":"invoca-5bd45748","customDomains":["docs.invoca.com"],"gitSource":{"type":"github","owner":"invoca","repo":"docs","deployBranch":"main","contentDirectory":"","isPrivate":true,"mountPath":"","label":""},"chroma":{"liveDeploymentHistoryId":"6ae8bdbaca78e468455a6b97","collectionId":"54b26950-9181-41ce-985a-32d474ce8dd0","schemaVersion":"v3","latestBuiltDeploymentHistoryId":"6ae8bdbaca78e468455a6b97"},"feedback":{"thumbs":true,"agentFeedback":true,"contextualFeedback":{"allowEmail":false}},"entitlements":{"AI_CHAT":{"status":"ENABLED"},"REMOVE_BRANDING":{"status":"ENABLED"},"CONTEXTUAL_FEEDBACK":{"status":"ENABLED","enabled":"ENABLED"},"CODE_SNIPPET_FEEDBACK":{"status":"ENABLED"},"WEB_MCP":{"status":"ENABLED"},"NARRATION":{"status":"ENABLED"}},"buildId":"6ae8bdbaca78e468455a6b97","clientVersion":"0.0.4045","searchFilterCounts":[{"language":"default","tag":"Invoca Docs","count":666},{"language":"default","tag":"Guide","count":665},{"language":"default","tag":"Developer","count":191},{"language":"default","tag":"Integrations","count":184},{"language":"default","tag":"Invoca Design System","count":163},{"language":"default","tag":"API Documentation","count":151},{"language":"default","tag":"Campaign \u0026 Call Handling","count":127},{"language":"default","tag":"Insights \u0026 Analytics","count":101},{"language":"default","tag":"Network Integration","count":99},{"language":"default","tag":"Components","count":75}]},"assistantConfig":{"enableAskAiButton":false,"enableStarterQuestions":true,"enableContextualStarterQuestions":true,"enableAskAiSearchBar":true,"deflection":{"enabled":true,"type":"link","url":"https://support.invoca.com/s/contactsupport"}},"basePath":"","hasChatPermissions":true,"hasWebMcpPermissions":true,"toggles":[{"name":"kill-isr","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-mintlify-slack","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"agent-integrations-settings","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-pr-grouping","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"custom-prefix","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"analytics-v7","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"user-flows-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"no-publish-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-onboarding","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"llms-txt-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving-live","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"stripTemperatureMiddleware","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"support-platform-pylon-zendesk","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"snapshot-serving","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"instant-rollback","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications-server","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"granular-notifications","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automations-fable","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fixed-ai-action-pricing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"automation-resolve-merge-conflict","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"scoped-publishing","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"mintql","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"page-level-analytics","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"editor-version-history-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-page-v2","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"full-page-search","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workspace-projects","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"assistant-v5","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"byok-sep-25","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"fork-pr-previews","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"revalidation-jwt","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"vite-origin","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"billing-updated-9-15","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-sso-scim","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"hobby-abuse-scan","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"dashboard-inbox","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-signup","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"turbopuffer-dual-write","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"workos-enabled","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"approve-review-flow","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false},{"name":"sso-setup-deadline-banner","enabled":true,"variant":{"name":"disabled","enabled":false,"feature_enabled":true},"impressionData":false}],"googleFontsInlined":false},"customCss":[{"filePath":"style.css","content":".custom-card .card img{width:60%;padding:1rem;margin:0 auto}a[href=\"https://academy.invoca.com/\"] span.absolute{background-color:#004030}#assistant-entry,#assistant-entry-mobile{background-color:#00a87f;border-color:#00a87f}#assistant-entry-mobile:hover,#assistant-entry:hover{background-color:#009973}#assistant-entry span,#assistant-entry svg,#assistant-entry-mobile span,#assistant-entry-mobile svg{color:#fff}.ivk-home{--ivk-forest:#0A231E;--ivk-footer-bg:#004030;--ivk-accent:#00A87F;--ivk-ink:#1F2121;--ivk-muted:#626464;--ivk-line:#E7E9E4;--ivk-bg:#FFFFFF;--ivk-sand:#F8FAF1;--ivk-banner:#D1FFD1;--ivk-card:#FFFFFF;--ivk-radius-btn:6px;--ivk-radius-card:16px;color:var(--ivk-ink);width:100%;max-width:1600px;margin:0 auto;padding:2rem clamp(1.25rem,4vw,4rem) 4rem}.dark .ivk-home,:root[data-theme=dark] .ivk-home{--ivk-bg:#10201B;--ivk-card:#16261F;--ivk-ink:#EAF1EC;--ivk-muted:#9BAAA1;--ivk-line:#223229;--ivk-sand:#16261F;--ivk-banner:#1C3A2C}.ivk-home a{color:inherit;text-decoration:none}.ivk-home footer a[href*=mintlify]{color:#9ea3a2}.dark .ivk-home footer a[href*=mintlify]{color:#6f7573}.ivk-home footer a[href*=mintlify]:hover{color:#4f5553}.dark .ivk-home footer a[href*=mintlify]:hover{color:#ced3d1}.ivk-home img,.ivk-home svg{display:block}.ivk-home-announce,.ivk-home-hero{border-radius:var(--ivk-radius-card)}.ivk-home-announce{background:var(--ivk-banner);color:var(--ivk-ink);font-size:13.5px;font-weight:500;text-align:center;padding:9px 16px;margin-bottom:20px}.ivk-home-announce a{color:var(--ivk-footer-bg);font-weight:600;text-decoration:underline;margin-left:6px}.ivk-home-hero{position:relative;overflow:hidden;background:var(--ivk-forest);color:#fff;padding:56px 32px}.ivk-home-hero-eyebrow{display:inline-flex;align-items:center;gap:8px;color:#fff;font-size:13px;font-weight:500;border:1px solid rgba(255,255,255,.28);border-radius:999px;padding:6px 14px;margin:0 0 22px}.ivk-home-hero h1{font-size:clamp(28px,4.2vw,44px);font-weight:700;line-height:1.1;letter-spacing:-.01em;max-width:17ch;margin:0 0 16px;color:#fff}.ivk-home-hero h1 .ivk-home-hl{color:#00b388}.ivk-home-hero-lede{font-size:16.5px;color:rgba(255,255,255,.78);max-width:48ch;margin:0 0 30px;line-height:1.55}.ivk-home-search-field{display:flex;align-items:center;gap:10px;background:#fff;border-radius:var(--ivk-radius-btn);padding:13px 16px;max-width:560px;box-shadow:0 14px 34px rgba(0,0,0,.28);cursor:pointer;border:0;width:100%;text-align:left;font-family:inherit}.ivk-home-search-field input{border:0;outline:0;flex:1;font-size:15px;font-family:inherit;color:#1f2121;background:0 0;pointer-events:none}.ivk-home-search-field input::placeholder{color:#626464}.ivk-home-search-field .ivk-home-icon{color:#626464;flex:none}.ivk-home-search-field kbd{font-size:11.5px;color:#626464;border:1px solid #e7e9e4;border-radius:5px;padding:2px 6px}.ivk-home-popular{margin-top:26px;display:flex;flex-wrap:wrap;gap:8px;align-items:center}.ivk-home-popular .ivk-home-label{color:rgba(255,255,255,.62);font-size:13px;margin-right:4px}.ivk-home-chip{display:inline-flex;align-items:center;gap:6px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.2);color:#fff;font-size:13px;padding:7px 12px;border-radius:999px}.ivk-home-chip:hover{background:rgba(255,255,255,.16)}.ivk-home-note-banner{background:var(--ivk-sand);border:1px solid var(--ivk-line);border-radius:var(--ivk-radius-card);padding:16px 20px;font-size:13.5px;color:var(--ivk-muted);line-height:1.55;margin-top:24px}.ivk-home-note-banner strong{color:var(--ivk-ink)}.ivk-home-band{padding:60px 0}.ivk-home-band-alt{background:var(--ivk-sand);border-radius:var(--ivk-radius-card)}.ivk-home-section-head{display:flex;align-items:baseline;justify-content:space-between;gap:16px;margin-bottom:26px;flex-wrap:wrap}.ivk-home-section-head h2,.ivk-home-topic-tile h3{font-size:23px;font-weight:700;letter-spacing:-.01em;margin:0;color:var(--ivk-ink)}.ivk-home-section-head .ivk-home-meta,.ivk-home-section-sub{color:var(--ivk-muted);font-size:13.5px}.ivk-home-section-sub{font-size:14.5px;max-width:62ch;margin:-14px 0 26px;line-height:1.55}.ivk-home-topic-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;align-items:start}@media (min-width:1400px){.ivk-home-topic-grid{grid-template-columns:repeat(4,1fr)}}@media (max-width:860px){.ivk-home-topic-grid{grid-template-columns:repeat(2,1fr)}}@media (max-width:600px){.ivk-home-topic-grid{grid-template-columns:1fr}}.ivk-home-topic-tile{background:var(--ivk-card);border:1px solid var(--ivk-line);border-radius:var(--ivk-radius-card);transition:border-color .15s ease}.ivk-home-topic-tile:hover{border-color:color-mix(in srgb,var(--ivk-accent) 40%,var(--ivk-line))}.ivk-home-t-head{display:block;width:100%;padding:22px 22px 18px;cursor:pointer;background:0 0;border:0;text-align:left;font-family:inherit;color:inherit}.ivk-home-row-top{display:flex;align-items:center;justify-content:space-between;gap:10px}.ivk-home-icon-wrap{width:36px;height:36px;border-radius:10px;background:rgba(0,168,127,.12);color:var(--ivk-accent);display:flex;align-items:center;justify-content:center}.ivk-home-count{font-size:12px;color:var(--ivk-muted);white-space:nowrap}.ivk-home-title-row{display:flex;align-items:flex-start;gap:6px;margin:12px 0 4px}.ivk-home-topic-tile h3{font-size:16.5px;font-weight:600;letter-spacing:-.005em;line-height:1.3;min-height:2.6em;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.ivk-home-tag-pill{flex:none;margin-top:2px;padding:2px 4px;border-radius:6px;font-size:.65rem;font-weight:700;line-height:13px;color:var(--ivk-accent);background:rgba(0,168,127,.1)}.dark .ivk-home-tag-pill,:root[data-theme=dark] .ivk-home-tag-pill{color:#1dc79a;background:rgba(29,199,154,.15)}.ivk-home-desc{font-size:13.5px;color:var(--ivk-muted);margin:0;line-height:1.5;min-height:3em;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.ivk-home-expand-cue{display:flex;align-items:center;gap:6px;margin-top:14px;font-size:12.5px;color:var(--ivk-accent);font-weight:600}.ivk-home-expand-cue svg{transition:transform .18s ease}.ivk-home-topic-tile.ivk-home-open .ivk-home-expand-cue svg{transform:rotate(180deg)}.ivk-home-t-panel{border-top:1px solid var(--ivk-line);margin-top:2px;padding:16px 22px 22px}.ivk-home-t-panel ul{list-style:none;margin:0 0 12px;padding:0;display:flex;flex-direction:column;gap:9px}.ivk-home-t-panel li a{font-size:13px;color:var(--ivk-ink);display:flex;align-items:center;gap:7px}.ivk-home-dev-card h3 a:hover,.ivk-home-t-panel li a:hover,.ivk-home-viewall{color:var(--ivk-accent)}.ivk-home-t-panel li a svg{flex:none;opacity:.5}.ivk-home-viewall{font-size:12.5px;font-weight:600}.ivk-home-dev-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}@media (max-width:700px){.ivk-home-dev-grid{grid-template-columns:1fr}}.ivk-home-dev-card{background:var(--ivk-card);border:1px solid var(--ivk-line);border-radius:var(--ivk-radius-card);padding:18px;display:flex;gap:12px;align-items:flex-start}.ivk-home-dev-card .ivk-home-icon-wrap{background:var(--ivk-forest);color:#fff;width:32px;height:32px;border-radius:9px}.ivk-home-dev-card h3{font-size:14.5px;font-weight:600;margin:0 0 3px;color:var(--ivk-ink)}.ivk-home-dev-card p{font-size:13px;color:var(--ivk-muted);margin:0;line-height:1.45}"}],"page":{"pagePath":"invoca-design-system/patterns/inline-editing","slug":"/invoca-design-system/patterns/inline-editing","pageMetadata":{"title":"Inline editing","description":"Editing a value in place, and when that stops being the right idea.","icon":"pencil","keywords":["inline edit","edit in place","click to edit","quick edit","commit","cancel"],"href":"/invoca-design-system/patterns/inline-editing"},"description":{"compiledSource":"\"use strict\";\nconst {jsx: _jsx} = arguments[0];\nconst {useMDXComponents: _provideComponents} = arguments[0];\nfunction _createMdxContent(props) {\n const _components = {\n p: \"p\",\n ..._provideComponents(),\n ...props.components\n };\n return _jsx(_components.p, {\n children: \"Editing a value in place, and when that stops being the right idea.\"\n });\n}\nfunction MDXContent(props = {}) {\n const {wrapper: MDXLayout} = {\n ..._provideComponents(),\n ...props.components\n };\n return MDXLayout ? _jsx(MDXLayout, {\n ...props,\n children: _jsx(_createMdxContent, {\n ...props\n })\n }) : _createMdxContent(props);\n}\nreturn {\n default: MDXContent\n};\n","frontmatter":{},"scope":{}},"pageType":"default","mdx":{"mdxSource":{"compiledSource":"\"use strict\";const{Fragment:_Fragment,jsx:_jsx,jsxs:_jsxs}=arguments[0];const{useMDXComponents:_provideComponents}=arguments[0];function _createMdxContent(props){const _components={a:`a`,code:`code`,em:`em`,li:`li`,p:`p`,pre:`pre`,span:`span`,strong:`strong`,tbody:`tbody`,td:`td`,th:`th`,thead:`thead`,tr:`tr`,ul:`ul`,..._provideComponents(),...props.components},{CodeBlock,Heading,Mermaid,Table,Tip}=_components;if(!CodeBlock)_missingMdxReference(`CodeBlock`,true);if(!Heading)_missingMdxReference(`Heading`,true);if(!Mermaid)_missingMdxReference(`Mermaid`,true);if(!Table)_missingMdxReference(`Table`,true);if(!Tip)_missingMdxReference(`Tip`,true);return _jsxs(_Fragment,{children:[_jsx(Heading,{level:`2`,id:`the-problem`,children:`The problem`}),`\n`,_jsx(_components.p,{children:`Changing one small value — a name, a status, a quantity — sometimes gets a whole navigation to a\ndedicated edit page for a change that takes one field and one second to type. That round trip\ncosts the reader a page load out and a page load back for a change that didn’t need either. But\ninline editing isn’t free of cost either: it has to signal that a value is editable at all before\nanyone clicks it, it has to make committing and cancelling both obvious, and it has to say what\nhappens to the value if the save fails while the reader has already moved on.`}),`\n`,_jsx(_components.p,{children:`The failure mode in each direction is different. Skip inline editing where it belongs and every\ntrivial change becomes a small ceremony. Use it where it doesn’t belong — a change with side\neffects on other data, or one that genuinely needs its own validation and context — and the\nreader edits a field with no idea what else just changed, or hits a save failure with nowhere to\nsee why.`}),`\n`,_jsx(Heading,{level:`2`,id:`inline-or-a-dedicated-view`,children:`Inline, or a dedicated view?`}),`\n`,_jsx(Mermaid,{chart:`flowchart TD\n A[A value needs to be changed] --> B{Does changing it affect\u003cbr/>anything beyond this one field?}\n B -->|Yes — other records, billing,\u003cbr/>routing, other people| C[Dedicated view or Form validation.\u003cbr/>The change needs its own context.]\n B -->|No| D{Does the new value need validation\u003cbr/>beyond a simple format check?}\n D -->|Yes — cross-field rules, an async check| C\n D -->|No| E[Inline editing]`}),`\n`,_jsx(Tip,{children:_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Inline editing is for changes with no story to tell.`}),` The moment a change needs to explain\nitself — what else it affects, why a value is rejected — it has outgrown a single field edited\nin place, and belongs on `,_jsx(_components.a,{href:`/invoca-design-system/patterns/form-validation`,children:`Form validation`}),`’s\nground instead.`]})}),`\n`,_jsx(Heading,{level:`2`,id:`vocabulary`,children:`Vocabulary`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Term`}),_jsx(_components.th,{children:`Also called`}),_jsx(_components.th,{children:`The system uses`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Resting`}),_jsx(_components.td,{children:`Display mode, view mode, read state`}),_jsxs(_components.td,{children:[_jsx(_components.strong,{children:`Resting`}),` — the value shown as plain text before editing starts`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Commit`}),_jsx(_components.td,{children:`Save, confirm, apply`}),_jsxs(_components.td,{children:[_jsx(_components.strong,{children:`Commit`}),` — accepting the new value and returning to resting state`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Cancel`}),_jsx(_components.td,{children:`Discard, revert, undo`}),_jsxs(_components.td,{children:[_jsx(_components.strong,{children:`Cancel`}),` — reverting to the original value with no save attempted`]})]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`when-this-applies`,children:`When this applies`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`The value is a single field, and changing it has no side effects on other data.`}),`\n`,_jsxs(_components.li,{children:[`The new value needs no validation beyond what a single `,_jsx(_components.a,{href:`/invoca-design-system/components/forms/input`,children:`Input`}),`\nor `,_jsx(_components.a,{href:`/invoca-design-system/components/forms/select`,children:`Select`}),` already checks.`]}),`\n`,_jsx(_components.li,{children:`The reader benefits from staying on the page they’re already looking at.`}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`when-it-doesn’t`,children:`When it doesn’t`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Situation`}),_jsx(_components.th,{children:`Do this instead`}),_jsx(_components.th,{children:`Why`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`The change has side effects on other records, billing, or live routing`}),_jsx(_components.td,{children:`A dedicated view`}),_jsx(_components.td,{children:`The consequences need their own space to be shown, not a single field with no room to explain them`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`The value needs real validation — cross-field rules, an async check`}),_jsx(_components.td,{children:_jsx(_components.a,{href:`/invoca-design-system/patterns/form-validation`,children:`Form validation`})}),_jsx(_components.td,{children:`Inline editing has no room for a page-level error summary or multi-field context`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`The reader is navigating away with the field still uncommitted`}),_jsx(_components.td,{children:_jsx(_components.a,{href:`/invoca-design-system/patterns/destructive-confirmation#when-it-doesnt`,children:`Destructive confirmation`})}),_jsx(_components.td,{children:`Its own “when it doesn’t” table already covers unsaved edits on navigation — a custom modal on the way out is routinely bypassed by the back button`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`The action is destructive rather than a simple value change`}),_jsx(_components.td,{children:_jsx(_components.a,{href:`/invoca-design-system/patterns/destructive-confirmation`,children:`Destructive confirmation`})}),_jsx(_components.td,{children:`Deleting or removing something is a different decision than editing a value in place`})]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`structure`,children:`Structure`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Order`}),_jsx(_components.th,{children:`Component`}),_jsx(_components.th,{children:`Role`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{\"data-numeric\":`true`,children:`1`}),_jsx(_components.td,{children:`Resting value`}),_jsxs(_components.td,{children:[`Plain text, with a hover treatment signaling it’s editable — see `,_jsx(_components.a,{href:`#constraints`,children:`Constraints`})]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{\"data-numeric\":`true`,children:`2`}),_jsxs(_components.td,{children:[_jsx(_components.a,{href:`/invoca-design-system/components/actions/button`,children:`Button`}),` `,_jsx(_components.code,{children:`icon-only`})]}),_jsxs(_components.td,{children:[`Optional explicit edit affordance, for a low-discoverability context — see `,_jsx(_components.a,{href:`#constraints`,children:`Constraints`})]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{\"data-numeric\":`true`,children:`3`}),_jsxs(_components.td,{children:[_jsx(_components.a,{href:`/invoca-design-system/components/forms/input`,children:`Input`}),` or `,_jsx(_components.a,{href:`/invoca-design-system/components/forms/select`,children:`Select`})]}),_jsx(_components.td,{children:`The field, in place of the resting value once editing starts`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{\"data-numeric\":`true`,children:`4`}),_jsxs(_components.td,{children:[_jsx(_components.a,{href:`/invoca-design-system/components/actions/button`,children:`Button`}),` `,_jsx(_components.code,{children:`icon-only`}),` × 2`]}),_jsx(_components.td,{children:`Commit and cancel, shown only while editing`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{\"data-numeric\":`true`,children:`5`}),_jsx(_components.td,{children:_jsx(_components.a,{href:`/invoca-design-system/components/containment/tooltip`,children:`Tooltip`})}),_jsx(_components.td,{children:`Labels the icon-only commit/cancel controls — required, since neither carries visible text`})]})]})]}),`\n`,_jsx(CodeBlock,{numberOfLines:`19`,language:`text`,children:_jsx(_components.pre,{className:`shiki shiki-themes github-light-default dark-plus`,style:{backgroundColor:`#ffffff`,\"--shiki-dark-bg\":`#0B0C0E`,color:`#1f2328`,\"--shiki-dark\":`#D4D4D4`},language:`text`,children:_jsxs(_components.code,{language:`text`,numberOfLines:`19`,children:[_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Resting:`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`┌───────────────────────────┐`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`│ Campaign name │`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`│ Q3 Paid Search ✎ │ ← pencil on hover, not always visible`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`└───────────────────────────┘`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Editing:`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`┌───────────────────────────────────┐`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`│ Campaign name │`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`│ [ Q3 Paid Search Retest ] ✓ ✕ │ ← commit / cancel, icon-only`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`└───────────────────────────────────┘`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`Failed save:`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`┌───────────────────────────────────┐`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`│ Campaign name │`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`│ [ Q3 Paid Search Retest ] ✓ ✕ │`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`│ Couldn't save. Try again. │ ← inline error, edit stays open`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{children:`└───────────────────────────────────┘`})}),`\n`,_jsx(_components.span,{className:`line`,children:_jsx(_components.span,{})})]})})}),`\n`,_jsx(Heading,{level:`2`,id:`behavior`,children:`Behavior`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`State`}),_jsx(_components.th,{children:`Behavior`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Resting`}),_jsxs(_components.td,{children:[`Value renders as plain text. A hover treatment (and a pencil icon, per `,_jsx(_components.a,{href:`#constraints`,children:`Constraints`}),`) signals it’s editable`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Entering edit mode`}),_jsxs(_components.td,{children:[`Click on the value (or its edit icon) replaces it with an `,_jsx(_components.a,{href:`/invoca-design-system/components/forms/input`,children:`Input`}),` or `,_jsx(_components.a,{href:`/invoca-design-system/components/forms/select`,children:`Select`}),`, focused and with the current value selected`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Committing`}),_jsxs(_components.td,{children:[_jsx(`kbd`,{children:`Enter`}),`, or the commit icon. Saves the new value and returns to resting state`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Cancelling`}),_jsxs(_components.td,{children:[_jsx(`kbd`,{children:`Escape`}),`, or the cancel icon. Reverts to the original value with no save attempted`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Clicking outside the field`}),_jsxs(_components.td,{children:[`Treated as commit if the value is valid, cancel if it isn’t — see `,_jsx(_components.a,{href:`#constraints`,children:`Constraints`})]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Save succeeds`}),_jsx(_components.td,{children:`Returns to resting state showing the new value`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Save fails`}),_jsxs(_components.td,{children:[_jsx(_components.strong,{children:`No component expresses this cleanly`}),` — see `,_jsx(_components.a,{href:`#known-issues`,children:`Known issues`}),` and `,_jsx(_components.a,{href:`/invoca-design-system/patterns/error-handling`,children:`Error handling`}),`. This page’s proposal: the field stays open, in its edited state, with the error rendered inline below it`]})]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`constraints`,children:`Constraints`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`ID`}),_jsx(_components.th,{children:`Constraint`}),_jsx(_components.th,{children:`Rationale`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`TITAN-INLINE-01`})}),_jsx(_components.td,{children:`An editable value shows a hover treatment and a pencil icon before it’s clicked. Neither is always visible at rest.`}),_jsx(_components.td,{children:`A permanently visible edit icon on every editable value in a dense list adds visual noise to a page’s resting state; a hover cue costs nothing until the reader is already pointing at the value.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`TITAN-INLINE-02`})}),_jsx(_components.td,{children:`A value editable by a single click needs no separate edit icon. A value where an accidental click would be costly, or that shares its space with another click target, gets an explicit icon-only edit button instead.`}),_jsx(_components.td,{children:`Click-to-edit is the lower-friction default; an explicit icon is the higher-friction choice reserved for where an accidental edit-mode entry is more than a minor annoyance.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`TITAN-INLINE-03`})}),_jsxs(_components.td,{children:[_jsx(`kbd`,{children:`Enter`}),` commits; `,_jsx(`kbd`,{children:`Escape`}),` cancels and reverts to the original value. Both are always available alongside their icon-only equivalents.`]}),_jsx(_components.td,{children:`A real, well-established convention across text-editing surfaces — a reader does not have to discover it in this one interface.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`TITAN-INLINE-04`})}),_jsxs(_components.td,{children:[`Commit and cancel controls are icon-only and each carries a `,_jsx(_components.a,{href:`/invoca-design-system/components/containment/tooltip`,children:`Tooltip`}),` and an accessible name — never a bare icon with neither.`]}),_jsx(_components.td,{children:`An icon-only button with no accessible name announces as “button” and nothing else.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`TITAN-INLINE-05`})}),_jsx(_components.td,{children:`A failed save keeps the field open, in its edited, unsaved state, with the error rendered inline. It never silently reverts to the old value.`}),_jsx(_components.td,{children:`Reverting on failure discards the reader’s typed correction with no way to recover it — the same reasoning applies to a failed destructive action that closes its dialog on failure.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`TITAN-INLINE-06`})}),_jsx(_components.td,{children:`Clicking outside a field with a valid, changed value commits it. Clicking outside a field with an invalid value cancels it and reverts.`}),_jsx(_components.td,{children:`Treating every outside click as cancel silently discards a valid edit the reader believed was saved; treating every outside click as commit can save a value that never passed validation.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`TITAN-INLINE-07`})}),_jsxs(_components.td,{children:[`Inline editing never surfaces a page-level error summary. A change that needs one belongs on `,_jsx(_components.a,{href:`/invoca-design-system/patterns/form-validation`,children:`Form validation`}),` instead.`]}),_jsx(_components.td,{children:`A single field editing in place has no page-level chrome to summarize into — needing one is the signal the change has outgrown this pattern.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.strong,{children:`TITAN-INLINE-08`})}),_jsxs(_components.td,{children:[`Navigating away from a page with an uncommitted inline edit follows `,_jsx(_components.a,{href:`/invoca-design-system/patterns/destructive-confirmation#when-it-doesnt`,children:`Destructive confirmation`}),`’s existing guidance for unsaved edits, not a rule invented here.`]}),_jsx(_components.td,{children:`One definition of “leaving with unsaved work,” not two that can disagree.`})]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`content`,children:`Content`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Element`}),_jsx(_components.th,{children:`✅`}),_jsx(_components.th,{children:`❌`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Failed save, inline`}),_jsx(_components.td,{children:`Couldn’t save. Try again.`}),_jsx(_components.td,{children:`Error.`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Commit icon’s accessible name`}),_jsx(_components.td,{children:`Save campaign name`}),_jsx(_components.td,{children:_jsx(_components.em,{children:`(unlabeled checkmark)`})})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Cancel icon’s accessible name`}),_jsx(_components.td,{children:`Cancel edit`}),_jsx(_components.td,{children:_jsx(_components.em,{children:`(unlabeled ✕)`})})]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`accessibility`,children:`Accessibility`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`Entering edit mode moves focus into the field automatically — the reader who clicked or\nkeyboard-activated the value should not have to Tab into it separately.`}),`\n`,_jsxs(_components.li,{children:[`The commit and cancel controls are reachable by Tab, in addition to their `,_jsx(`kbd`,{children:`Enter`}),` and\n`,_jsx(`kbd`,{children:`Escape`}),` equivalents — a reader who cannot use keyboard shortcuts still needs a visible\npath to both.`]}),`\n`,_jsxs(_components.li,{children:[`Each icon-only control’s accessible name states the action `,_jsx(_components.strong,{children:`and`}),` the field it acts on (“Save\ncampaign name,” not “Save”) when a page has more than one inline-editable field, so a\nscreen-reader user moving between rows can tell them apart.`]}),`\n`,_jsxs(_components.li,{children:[`A failed save is announced (a live region, or the error text’s own `,_jsx(_components.code,{children:`aria-describedby`}),`\nassociation to the field) — not only shown visually, per the same reasoning as\n`,_jsx(_components.a,{href:`/invoca-design-system/components/forms/input#accessibility`,children:`Input’s error state`}),`.`]}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`variations`,children:`Variations`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`Variation`}),_jsx(_components.th,{children:`When`}),_jsx(_components.th,{children:`Change`})]})}),_jsxs(_components.tbody,{children:[_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Table-cell inline edit`}),_jsxs(_components.td,{children:[`The value is one cell in a `,_jsx(_components.a,{href:`/invoca-design-system/components/data-display/table`,children:`Table`}),` row`]}),_jsx(_components.td,{children:`Edit mode is scoped to the single cell; commit/cancel icons appear only in that cell, not the whole row`})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Select-backed inline edit`}),_jsx(_components.td,{children:`The value comes from a fixed, enumerable set (a status)`}),_jsxs(_components.td,{children:[`The field becomes a `,_jsx(_components.a,{href:`/invoca-design-system/components/forms/select`,children:`Select`}),` instead of an Input; committing happens on selection, with no separate commit icon needed`]})]}),_jsxs(_components.tr,{children:[_jsx(_components.td,{children:`Always-open edit icon`}),_jsx(_components.td,{children:`The value sits in a low-pointer-precision context (touch, dense mobile layout)`}),_jsx(_components.td,{children:`The pencil icon is always visible rather than hover-only, since “hover to discover” doesn’t exist on touch`})]})]})]}),`\n`,_jsx(Heading,{level:`2`,id:`anti-patterns`,children:`Anti-patterns`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Reverting a failed save with no trace.`}),` The reader retyped a value, pressed save, and the\nfield quietly snapped back to the old value with no error and no way to tell what happened to\ntheir edit.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`An edit affordance nobody can find.`}),` A value that’s editable only via a hover-revealed icon,\non a device or in a context where hovering isn’t possible, has no path into edit mode at all.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Inline editing a value with real consequences.`}),` A field that looks like a quick inline edit\nbut actually changes billing, live routing, or another record’s data gives the reader no\nindication that clicking away committed something bigger than the one visible field.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`No keyboard equivalent for commit or cancel.`}),` Requiring a mouse click on a tiny icon to save\nor discard an inline edit, with no `,_jsx(`kbd`,{children:`Enter`}),` or `,_jsx(`kbd`,{children:`Escape`}),` alternative, breaks a\nconvention every reader already expects from editing text anywhere else.`]}),`\n`,_jsx(Heading,{level:`2`,id:`known-issues`,children:`Known issues`}),`\n`,_jsxs(Table,{children:[_jsx(_components.thead,{children:_jsxs(_components.tr,{children:[_jsx(_components.th,{children:`ID`}),_jsx(_components.th,{children:`Kind`}),_jsx(_components.th,{children:`What`})]})}),_jsx(_components.tbody,{children:_jsxs(_components.tr,{children:[_jsx(_components.td,{children:_jsx(_components.a,{href:`/invoca-design-system/foundations/open-decisions#titan-gap-22`,children:`TITAN-GAP-22`})}),_jsx(_components.td,{children:`Open decision`}),_jsxs(_components.td,{children:[`No component in Titan expresses a failed action today — there is a `,_jsx(_components.code,{children:`loading`}),` state and a `,_jsx(_components.code,{children:`disabled`}),` state, and nothing between them for failure. `,_jsx(_components.a,{href:`#constraints`,children:`TITAN-INLINE-05`}),`’s inline error treatment is this page’s proposal for what fills that gap, not a description of an existing primitive.`]})]})})]}),`\n`,_jsx(Heading,{level:`2`,id:`gaps-in-the-current-rules`,children:`Gaps in the current rules`}),`\n`,_jsxs(_components.ul,{children:[`\n`,_jsx(_components.li,{children:`Whether committing one inline edit should be blocked while another inline edit on the same\nrow or page is still open and uncommitted.`}),`\n`,_jsx(_components.li,{children:`What happens to an in-progress inline edit if the underlying record is deleted, or changed by\nsomeone else, while the reader is still typing.`}),`\n`,_jsxs(_components.li,{children:[`Whether a long-running commit needs its own loading treatment on the field, beyond what\n`,_jsx(_components.a,{href:`#behavior`,children:`Behavior`}),` currently describes.`]}),`\n`]}),`\n`,_jsx(Heading,{level:`2`,id:`why-it-works-this-way`,children:`Why it works this way`}),`\n`,_jsxs(_components.p,{children:[_jsxs(_components.strong,{children:[`Inline editing borrows `,_jsx(`kbd`,{children:`Enter`}),` and `,_jsx(`kbd`,{children:`Escape`}),` rather than inventing its own\ncommit gesture, because the convention already exists everywhere else text is edited.`]}),` A\nreader who has never seen this specific composition still knows what those two keys do the\nmoment the field becomes editable — this pattern is deliberately unoriginal here.`]}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`The failed-save behavior is argued from the same principle as a failed destructive action, not\nderived independently.`}),` Reverting silently on failure and closing a dialog on failure destroy\nthe same thing — the reader’s belief that their input is still there. Once one of those cases is\nsettled, the other isn’t a new decision; it’s the same decision applied to a different surface.`]}),`\n`,_jsx(Heading,{level:`2`,id:`related`,children:`Related`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.a,{href:`/invoca-design-system/components/forms/input`,children:`Input`}),` and\n`,_jsx(_components.a,{href:`/invoca-design-system/components/forms/select`,children:`Select`}),`, the fields this pattern swaps in during\nediting. `,_jsx(_components.a,{href:`/invoca-design-system/components/actions/button`,children:`Button`}),` and\n`,_jsx(_components.a,{href:`/invoca-design-system/components/containment/tooltip`,children:`Tooltip`}),`, for the icon-only commit, cancel,\nand edit affordances. `,_jsx(_components.a,{href:`/invoca-design-system/patterns/form-validation`,children:`Form validation`}),`, for a\nchange that has outgrown a single inline field.\n`,_jsx(_components.a,{href:`/invoca-design-system/patterns/destructive-confirmation#when-it-doesnt`,children:`Destructive confirmation`}),`,\nfor leaving a page with an uncommitted edit. `,_jsx(_components.a,{href:`/invoca-design-system/foundations/open-decisions#titan-gap-22`,children:`TITAN-GAP-22`}),`\nand `,_jsx(_components.a,{href:`/invoca-design-system/patterns/error-handling`,children:`Error handling`}),`, for the general treatment of\na failed action this page’s failed-save behavior works around.`]}),`\n`,_jsx(Heading,{level:`2`,id:`status`,children:`Status`}),`\n`,_jsxs(_components.p,{children:[_jsx(_components.strong,{children:`Proposal — first pass, not an audited Invoca screen.`}),` No Titan design library or shipped\nproduct surface was available while writing this page. What follows is built from general\ninteraction-design practice and from the constraints Foundations and Components already\nestablish — see `,_jsx(_components.a,{href:`/invoca-design-system/patterns/overview#status`,children:`Status`}),`.`]})]})}function MDXContent(props={}){const{wrapper:MDXLayout}={..._provideComponents(),...props.components};return MDXLayout?_jsx(MDXLayout,{...props,children:_jsx(_createMdxContent,{...props})}):_createMdxContent(props)}return{default:MDXContent};function _missingMdxReference(id,component){throw new Error(`Expected `+(component?`component`:`object`)+\" `\"+id+\"` to be defined: you likely forgot to import, pass, or provide it.\")}","frontmatter":{},"scope":{"config":{},"pageMetadata":{"title":"Inline editing","description":"Editing a value in place, and when that stops being the right idea.","icon":"pencil","keywords":["inline edit","edit in place","click to edit","quick edit","commit","cancel"],"href":"/invoca-design-system/patterns/inline-editing"}}},"mdxSourceWithNoJs":null},"mdxExtracts":{"tableOfContents":[{"title":"The problem","slug":"the-problem","depth":2,"children":[]},{"title":"Inline, or a dedicated view?","slug":"inline-or-a-dedicated-view","depth":2,"children":[]},{"title":"Vocabulary","slug":"vocabulary","depth":2,"children":[]},{"title":"When this applies","slug":"when-this-applies","depth":2,"children":[]},{"title":"When it doesn’t","slug":"when-it-doesn’t","depth":2,"children":[]},{"title":"Structure","slug":"structure","depth":2,"children":[]},{"title":"Behavior","slug":"behavior","depth":2,"children":[]},{"title":"Constraints","slug":"constraints","depth":2,"children":[]},{"title":"Content","slug":"content","depth":2,"children":[]},{"title":"Accessibility","slug":"accessibility","depth":2,"children":[]},{"title":"Variations","slug":"variations","depth":2,"children":[]},{"title":"Anti-patterns","slug":"anti-patterns","depth":2,"children":[]},{"title":"Known issues","slug":"known-issues","depth":2,"children":[]},{"title":"Gaps in the current rules","slug":"gaps-in-the-current-rules","depth":2,"children":[]},{"title":"Why it works this way","slug":"why-it-works-this-way","depth":2,"children":[]},{"title":"Related","slug":"related","depth":2,"children":[]},{"title":"Status","slug":"status","depth":2,"children":[]}],"codeExamples":{}},"jsLinks":[],"subdomain":"invoca-5bd45748","siteName":"Invoca","title":"Inline editing - Invoca","relatedPagesConfig":{"enabled":true,"limit":5,"mode":"automatic"},"contextualStarterQuestions":[{"_id":"6ab71c03fc74c9b83ba92d05","deploymentId":"6a10d9fbb2a094386ec2a44e","questionText":"What makes inline editing fail?","status":"active","generatedBy":"contextual","path":"invoca-design-system/patterns/inline-editing","createdAt":"2026-09-26T01:12:35.670Z","updatedAt":"2026-09-26T01:12:35.670Z","__v":0},{"_id":"6ab71c03fc74c9b83ba92d06","deploymentId":"6a10d9fbb2a094386ec2a44e","questionText":"How do I handle save errors?","status":"active","generatedBy":"contextual","path":"invoca-design-system/patterns/inline-editing","createdAt":"2026-09-26T01:12:35.670Z","updatedAt":"2026-09-26T01:12:35.670Z","__v":0},{"_id":"6ab71c03fc74c9b83ba92d04","deploymentId":"6a10d9fbb2a094386ec2a44e","questionText":"When should I use inline editing?","status":"active","generatedBy":"contextual","path":"invoca-design-system/patterns/inline-editing","createdAt":"2026-09-26T01:12:35.670Z","updatedAt":"2026-09-26T01:12:35.670Z","__v":0}],"navMaps":{"firstHrefInVersion":{},"firstHrefInLanguage":{}},"lastModified":"2026-09-17T19:58:06.955Z","gitLastModified":"2026-09-17T19:57:46.000Z","originalFileLocation":"invoca-design-system/patterns/inline-editing.mdx"},"headHtml":"\u003cscript data-mint-head>(function(){var d=document.documentElement,p=null;if(!p){try{p=localStorage.getItem(\"isDarkMode\")}catch(e){}if(p==='true')p='dark';else if(p==='false')p='light';if(p!=='light'\u0026\u0026p!=='dark'\u0026\u0026p!=='system')p=\"light\"}var r=p==='system'?(matchMedia(\"(prefers-color-scheme: dark)\").matches?'dark':'light'):p;d.classList.remove(r==='dark'?'light':'dark');d.classList.add(r);d.setAttribute(\"data-theme-preference\",p)})();\u003c/script>\u003cmeta data-mint-head name=\"description\" content=\"Editing a value in place, and when that stops being the right idea.\">\u003cmeta data-mint-head name=\"application-name\" content=\"Invoca\">\u003cmeta data-mint-head name=\"generator\" content=\"Mintlify\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/invoca-5bd45748/Q9AjSeskElHvFPzD/_generated/favicon/android-chrome-192x192.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"apple-touch-icon\" type=\"image/png\" sizes=\"180x180\" href=\"/mintlify-assets/_mintlify/favicons/invoca-5bd45748/Q9AjSeskElHvFPzD/_generated/favicon/apple-touch-icon.png\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/invoca-5bd45748/Q9AjSeskElHvFPzD/_generated/favicon/favicon-16x16.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/invoca-5bd45748/Q9AjSeskElHvFPzD/_generated/favicon/favicon-32x32.png\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/invoca-5bd45748/Q9AjSeskElHvFPzD/_generated/favicon/favicon.ico\" media=\"(prefers-color-scheme: light)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"192x192\" href=\"/mintlify-assets/_mintlify/favicons/invoca-5bd45748/Q9AjSeskElHvFPzD/_generated/favicon-dark/android-chrome-192x192.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/mintlify-assets/_mintlify/favicons/invoca-5bd45748/Q9AjSeskElHvFPzD/_generated/favicon-dark/favicon-16x16.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/mintlify-assets/_mintlify/favicons/invoca-5bd45748/Q9AjSeskElHvFPzD/_generated/favicon-dark/favicon-32x32.png\" media=\"(prefers-color-scheme: dark)\">\u003clink data-mint-head rel=\"shortcut icon\" type=\"image/x-icon\" href=\"/mintlify-assets/_mintlify/favicons/invoca-5bd45748/Q9AjSeskElHvFPzD/_generated/favicon-dark/favicon.ico\" media=\"(prefers-color-scheme: dark)\">\u003cmeta data-mint-head property=\"og:title\" content=\"Inline editing - Invoca\">\u003cmeta data-mint-head property=\"og:description\" content=\"Editing a value in place, and when that stops being the right idea.\">\u003cmeta data-mint-head property=\"og:site_name\" content=\"Invoca\">\u003cmeta data-mint-head property=\"og:url\" content=\"https://docs.invoca.com/invoca-design-system/patterns/inline-editing\">\u003cmeta data-mint-head property=\"og:type\" content=\"website\">\u003cmeta data-mint-head property=\"og:image\" content=\"https://invoca-5bd45748.mintlify.app/_mintlify/api/og/v2?division=Patterns\u0026amp;title=Inline+editing\u0026amp;description=Editing+a+value+in+place%2C+and+when+that+stops+being+the+right+idea.\u0026amp;theme=1c2016f9c5ca3323f39e5905\u0026amp;signature=1b43a82ef9ca48263155487eed01baa1be6d42852f0f3cf7b3c9eae2e87e2113\">\u003cmeta data-mint-head property=\"og:image:width\" content=\"1200\">\u003cmeta data-mint-head property=\"og:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"twitter:card\" content=\"summary_large_image\">\u003cmeta data-mint-head name=\"twitter:title\" content=\"Inline editing - Invoca\">\u003cmeta data-mint-head name=\"twitter:description\" content=\"Editing a value in place, and when that stops being the right idea.\">\u003cmeta data-mint-head name=\"twitter:image\" content=\"https://invoca-5bd45748.mintlify.app/_mintlify/api/og/v2?division=Patterns\u0026amp;title=Inline+editing\u0026amp;description=Editing+a+value+in+place%2C+and+when+that+stops+being+the+right+idea.\u0026amp;theme=1c2016f9c5ca3323f39e5905\u0026amp;signature=1b43a82ef9ca48263155487eed01baa1be6d42852f0f3cf7b3c9eae2e87e2113\">\u003cmeta data-mint-head name=\"twitter:image:width\" content=\"1200\">\u003cmeta data-mint-head name=\"twitter:image:height\" content=\"630\">\u003cmeta data-mint-head name=\"msapplication-config\" content=\"/mintlify-assets/_mintlify/favicons/invoca-5bd45748/Q9AjSeskElHvFPzD/_generated/favicon/browserconfig.xml\">\u003cmeta data-mint-head name=\"apple-mobile-web-app-title\" content=\"Invoca\">\u003cmeta data-mint-head name=\"msapplication-TileColor\" content=\"#00A87F\">\u003clink data-mint-head rel=\"canonical\" href=\"https://docs.invoca.com/invoca-design-system/patterns/inline-editing\">\u003clink data-mint-head rel=\"alternate\" type=\"application/xml\" href=\"/sitemap.xml\">\u003clink data-mint-head rel=\"alternate\" type=\"text/markdown\" href=\"/invoca-design-system/patterns/inline-editing.md\">\u003cscript data-mint-head type=\"application/ld+json\">{\"@context\":\"https://schema.org\",\"@graph\":[{\"@type\":\"Organization\",\"@id\":\"https://docs.invoca.com/#organization\",\"name\":\"Invoca\",\"url\":\"https://docs.invoca.com\",\"logo\":{\"@type\":\"ImageObject\",\"url\":\"https://mintcdn.com/invoca-5bd45748/9Md68DqArcq3WD3L/logo/light.svg?fit=max\u0026auto=format\u0026n=9Md68DqArcq3WD3L\u0026q=85\u0026s=3ed629c5be28d08bd1a6580e72b6dbfa\"}},{\"@type\":\"WebSite\",\"@id\":\"https://docs.invoca.com#website\",\"name\":\"Invoca\",\"url\":\"https://docs.invoca.com\",\"publisher\":{\"@id\":\"https://docs.invoca.com/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https://docs.invoca.com/invoca-design-system/patterns/inline-editing#webpage\",\"url\":\"https://docs.invoca.com/invoca-design-system/patterns/inline-editing\",\"name\":\"Inline editing\",\"description\":\"Editing a value in place, and when that stops being the right idea.\",\"dateModified\":\"2026-09-17T19:57:46.000Z\",\"isPartOf\":{\"@id\":\"https://docs.invoca.com#website\"},\"breadcrumb\":{\"@id\":\"https://docs.invoca.com/invoca-design-system/patterns/inline-editing#breadcrumb\"}},{\"@type\":\"BreadcrumbList\",\"@id\":\"https://docs.invoca.com/invoca-design-system/patterns/inline-editing#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Patterns\",\"item\":\"https://docs.invoca.com/invoca-design-system/patterns/overview\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Inline editing\",\"item\":\"https://docs.invoca.com/invoca-design-system/patterns/inline-editing\"}]},{\"@type\":[\"Article\",\"TechArticle\"],\"@id\":\"https://docs.invoca.com/invoca-design-system/patterns/inline-editing#article\",\"headline\":\"Inline editing\",\"name\":\"Inline editing\",\"description\":\"Editing a value in place, and when that stops being the right idea.\",\"url\":\"https://docs.invoca.com/invoca-design-system/patterns/inline-editing\",\"mainEntityOfPage\":{\"@id\":\"https://docs.invoca.com/invoca-design-system/patterns/inline-editing#webpage\"},\"dateModified\":\"2026-09-17T19:57:46.000Z\",\"keywords\":\"inline edit, edit in place, click to edit, quick edit, commit, cancel\",\"publisher\":{\"@id\":\"https://docs.invoca.com/#organization\"},\"isPartOf\":{\"@id\":\"https://docs.invoca.com#website\"}}]}\u003c/script>","siteName":"Invoca","nav":{"global":null,"products":[{"product":"Invoca Docs","pages":[{"title":"Invoca Docs","href":""}]},{"product":"Developer","pages":[{"title":"Developer","href":"/en/latest"}]},{"product":"Invoca Design System","icon":"palette","color":{"light":"#00b388","dark":"#00b388"},"description":"","tabs":[{"tab":"Get started","icon":"rocket","pages":[{"title":"Get started","href":"/invoca-design-system"}]},{"tab":"Foundations","icon":"palette","pages":[{"title":"Foundations","href":"/invoca-design-system/foundations/overview"}]},{"tab":"Content","icon":"font","pages":[{"title":"Content","href":"/invoca-design-system/content/voice-and-tone"}]},{"tab":"Components","icon":"shapes","pages":[{"title":"Components","href":"/invoca-design-system/components/overview"}]},{"tab":"Patterns","icon":"clipboard-list","groups":[{"group":"Patterns","root":{"title":"Patterns overview","icon":"layout-template","href":"/invoca-design-system/patterns/overview"},"directory":"card","pages":[{"title":"Destructive confirmation","icon":"trash-2","href":"/invoca-design-system/patterns/destructive-confirmation"},{"title":"Form validation","icon":"clipboard-check","href":"/invoca-design-system/patterns/form-validation"},{"title":"Empty \u0026 zero states","icon":"inbox","href":"/invoca-design-system/patterns/empty-and-zero-states"},{"title":"Loading \u0026 skeletons","icon":"loader","href":"/invoca-design-system/patterns/loading-and-skeletons"},{"title":"Error handling","icon":"octagon-alert","href":"/invoca-design-system/patterns/error-handling"},{"title":"Filtering and search","icon":"filter","href":"/invoca-design-system/patterns/filtering-and-search"},{"title":"Bulk selection","description":"Selecting many rows at once and acting on them as a single batch.","icon":"list-checks","href":"/invoca-design-system/patterns/bulk-selection"},{"title":"Inline editing","icon":"pencil","href":"/invoca-design-system/patterns/inline-editing"},{"title":"Progressive disclosure","description":"Keeping a screen simple without hiding what people actually need.","icon":"chevrons-down-up","href":"/invoca-design-system/patterns/progressive-disclosure"},{"title":"Notifications","icon":"bell","href":"/invoca-design-system/patterns/notifications"},{"title":"Footers","icon":"panel-bottom","href":"/invoca-design-system/patterns/footers"},{"group":"CRUD","root":{"title":"CRUD overview","icon":"database","href":"/invoca-design-system/patterns/crud/overview"},"pages":[{"title":"Create","icon":"circle-plus","href":"/invoca-design-system/patterns/crud/create"},{"title":"Read","icon":"eye","href":"/invoca-design-system/patterns/crud/read"},{"title":"Update","icon":"pencil-line","href":"/invoca-design-system/patterns/crud/update"}]}]}]},{"tab":"Views","icon":"table-columns","pages":[{"title":"Views","href":"/invoca-design-system/views/overview"}]},{"tab":"AI Experience","icon":"robot","pages":[{"title":"AI Experience","href":"/invoca-design-system/ai-experience/overview"}]},{"tab":"Contribute","icon":"code-pull-request","pages":[{"title":"Contribute","href":"/invoca-design-system/contribute/governance"}]},{"tab":"Open Issues","icon":"triangle-exclamation","pages":[{"title":"Open Issues","href":"/invoca-design-system/foundations/divergences"}]}]}]},"basePath":"","title":"Inline editing - Invoca","buildId":"dpl_36EDyZCDfRuoUD9af5CH4M1bqY2h","deploymentId":"dpl_36EDyZCDfRuoUD9af5CH4M1bqY2h","dynamics":[],"chunks":[]}