Getting StartedComponentsDesign TokensThemingTheme SynthesizerFrameworksAccessibilityUtilitiesServer RenderingBrowser SupportContributingChangelogApp ShellAspect GridAuth ShellCenterContainerDashboard GridFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbarAnchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkStepper NavTabsTop BarTree ViewAccordionAspect RatioAvatarAvatar GroupCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSkeletonSpinnerStackVideoVirtual ListActivity HeatmapAnimated NumberBadgeBar ListChartClockComparisonCountdown TimerData GridDescription ListDiffGaugeJSON TreeKanbanLevel MeterListMeterSparklineStatStepperTagTimelineUptimeValue CardWaveformBlockquoteCode BlockCode GroupGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriterButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerField ListFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree SelectAlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuHover CardLoading OverlayNotification PanelPopoverProgressSheetToastTooltip
ARC UIARC Radiant Components
v4.9DocsComponentsTokensSynthesizer
Getting StartedFrameworksServer RenderingDesign TokensThemingTheme SynthesizerTypographyUtilitiesAll ComponentsAccessibilityBrowser SupportChangelogContributingStatsApp ShellAspect GridAuth ShellCenterContainerDashboard GridFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbarAnchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkStepper NavTabsTop BarTree ViewAccordionAspect RatioAvatarAvatar GroupCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSkeletonSpinnerStackVideoVirtual ListActivity HeatmapAnimated NumberBadgeBar ListChartClockComparisonCountdown TimerData GridDescription ListDiffGaugeJSON TreeKanbanLevel MeterListMeterSparklineStatStepperTagTimelineUptimeValue CardWaveformBlockquoteCode BlockCode GroupGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriterButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerField ListFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree SelectAlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuHover CardLoading OverlayNotification PanelPopoverProgressSheetToastTooltip

Inline Edit

Click-to-edit text that renders as plain content until activated, then swaps to a pre-filled field. Enter or blur commits, Escape cancels. Built for track renames, titles, and other fields where a permanent input box would be visual noise.

ComponentsInline Edit
inputinteractive
<arc-inline-edit>

Overview

Inline Edit is editable text. In its resting state it renders the current value as plain text with no field chrome at all, only a faint pencil affordance on hover or focus. Clicking it, or pressing Enter or F2 while it is focused, swaps in a field pre-filled with the current value, text selected and ready to overtype. Enter or clicking away commits; Escape throws the edit away. The display text inherits the surrounding typography, and the edit field matches it, so the swap never changes the text's size or position. An Inline Edit inside a heading edits at heading size; one in a table cell edits at cell size. This is the component's whole reason to exist: rename flows and title fields where a visible input box would say "form" when the page is saying "document". While the user types, keystrokes accumulate in an internal draft and stream out as `arc-input` events. The `value` prop and the value a surrounding form submits change only when the edit commits, which fires a single `arc-change`. Committing an unchanged value fires nothing, so listeners never see a rename that didn't happen. Cancelling fires `arc-cancel` and restores the previous text. **The same three transitions are available from script.** `edit()` enters edit mode with the field focused and its text selected. Use it for a "Rename" item in a context menu, or to focus a freshly added row so the user can name it without hunting for it. `commit()` and `cancel()` leave edit mode the two ways the keyboard does, firing the same `arc-change` (or nothing, if the value is unchanged) and `arc-cancel`. Reach for `commit()` when something outside the component ends the edit (a toolbar Save, a route change), and `cancel()` when a save fails and the previous text should come back. `edit()` is a no-op when the component is disabled, readonly, or already editing, so it is safe to call without checking first. Inline Edit participates in forms through the same ElementInternals machinery as Input: give it a `name` and the committed value is submitted, `required` makes an empty committed value invalid (shown as a quiet error tint even in display state), and `form.reset()` restores the initial text. The `multiline` prop swaps the edit field to a textarea, where Enter inserts a newline and Cmd/Ctrl+Enter commits.

Guidelines

When to use

  • Use Inline Edit where the text is content first and a field second: titles, track names, table cells, sidebar labels
  • Always provide a `label`. It becomes the accessible name ("Edit Track title") for the display button and the field
  • Listen for `arc-change` to persist a rename; it fires once per commit and only when the value actually changed
  • Use `multiline` for short notes and descriptions that may wrap, and tell users that Cmd/Ctrl+Enter saves
  • Set a domain-specific `placeholder` ("Untitled track") so an empty value still reads as something clickable
  • Use `readonly` when a value is temporarily locked. The text stays in the reading order without inviting an edit

When not to use

  • Do not use Inline Edit in a conventional form layout. A labeled Input communicates "fill me in"; Inline Edit hides that invitation
  • Do not use it for values needing heavy validation or structured entry (emails, dates, numbers). Use Input, DatePicker, or NumberInput
  • Do not treat `arc-input` as a save signal; it carries the in-progress draft, which Escape may still throw away
  • Do not hide the only editing path behind hover alone on touch-heavy interfaces. The affordance also appears on focus, so keep the control reachable by keyboard

Features

  • Renders as plain text until activated. No field chrome in the resting state
  • Display text inherits surrounding typography, and the edit field matches it, so the swap never reflows
  • Activation by click, or Enter, Space, or F2 while focused; the field opens pre-filled with the text selected
  • Enter or blur commits and fires a single `arc-change`; Escape reverts and fires `arc-cancel`
  • Committing an unchanged value fires no event at all
  • `arc-input` streams the draft on every keystroke while editing
  • Multiline mode edits in a textarea: Enter inserts a newline, Cmd/Ctrl+Enter commits
  • Full form participation: named submission of the committed value, `required` validation, reset support
  • Programmatic control through `edit()`, `commit()`, and `cancel()`. The same three transitions the keyboard drives, firing the same events
  • Pencil affordance and hover tint follow the design tokens; the swap animates subtly and honors reduced motion

Preview

1 3:42
2 4:17
3 2:58

Usage

This component requires JavaScript. No pure HTML/CSS version is available. Use the Web Component directly or a framework wrapper.

<!-- A renameable track list: text until clicked, field until committed -->
<div style="display:flex; flex-direction:column; max-width:440px; gap:2px;">
  <arc-inline-edit value="Midnight Signal" label="Track 1 title" placeholder="Untitled track"></arc-inline-edit>
  <arc-inline-edit value="Glass Harbor" label="Track 2 title" placeholder="Untitled track"></arc-inline-edit>
  <arc-inline-edit label="Track 3 title" placeholder="Untitled track"></arc-inline-edit>
</div>

<script>
  document.querySelectorAll('arc-inline-edit').forEach((el) => {
    el.addEventListener('arc-change', (e) => {
      console.log('renamed to', e.detail.value);
    });
  });
</script>

<!-- Multiline notes: Enter newlines, Cmd/Ctrl+Enter commits -->
<arc-inline-edit multiline label="Session notes" placeholder="Add a note"></arc-inline-edit>

API

valuestring''
The committed text. Updated only when an edit commits (Enter or blur); keystrokes accumulate in an internal draft until then.
labelstring''
Accessible name for the control. The display state announces as "Edit {label}" and the edit field is labeled with it. Always provide one.
namestring''
The name attribute sent with form data on submission. The submitted value is the committed value, never an in-progress draft.
placeholderstring'Empty'
Text shown in muted italic when value is empty, and as the field placeholder while editing. Defaults to "Empty".
disabledbooleanfalse
Prevents activation and applies a muted treatment. The value is excluded from form submission while disabled.
multilinebooleanfalse
When true, editing uses a <textarea>: Enter inserts a newline and Cmd/Ctrl+Enter commits. Single-line commits on plain Enter.
requiredbooleanfalse
Marks the field as required. An empty committed value is invalid, including in display state, which shows a subtle error tint.
readonlybooleanfalse
Renders the display state only: the text remains focusable for reading order, but activation is inert and no pencil affordance appears.
formAssociatedbooleantrue
propertiesobject{ // flag(), unlike `disabled`. The exclusion in props.js is specifically // about form-associated *platform* semantics: a `disabled` content // attribute that is merely present makes the element actually disabled // per the HTML spec, and formDisabledCallback assigns the property back, // so no converter can win. Neither of these is platform-mapped: // `required` is enforced by _computeValidity() below and `readonly` by // each component's own interaction handlers, so the stock converter buys // nothing here and costs the usual bug: `required="false"` read as true, // blocking submission of a form the author meant to leave optional. // Finding #48's shape, across all 26 form controls at once. required: flag(false), readonly: flag(false), }
Lit merges static properties up the prototype chain, so every consumer gets these without declaring them. required participates in constraint validation below; readonly reflects for styling and is enforced by each component's interaction handlers (the mixin can't know which gestures mutate state).
autoValidatesbooleantrue
Components that run their own constraint-validation logic (pattern checks, range checks) opt out of the automatic required sync by overriding this to false, and own the whole validity flag set instead.
form
validity
validationMessage

Methods

focus(options?)options?: FocusOptions
Move focus to whichever element is currently the control: the field while editing, the display row otherwise. The host sets no delegatesFocus and everything focusable lives in the shadow root, so el.focus() used to do nothing at all, silently, which cost a consumer a "Rename" menu item that appeared dead. edit() is the way *into* editing and stays that way; this is only the obvious call landing where a caller expects it.
edit()
Enter edit mode: focus the field and select its text. No-op when disabled, readonly, or already editing.
commit()
Commit the current draft and leave edit mode. Fires arc-change only if the value changed.
cancel()
Discard the draft, keep the previous value, and leave edit mode. Fires arc-cancel.
checkValidity()boolean
Whether the control currently satisfies its constraints, per the native constraint-validation API. Fires invalid on the element when it does not, and reports nothing to the user.
reportValidity()boolean
As checkValidity(), but also shows the browser's validation message against the control when it fails.

Events

arc-changedetail:{ value: string }
Fired once on commit when the value actually changed, with the new value in detail.value.
arc-canceldetail:{ value: string }
Fired when an edit is canceled (Escape or cancel()), with the retained value in detail.value. No arc-change accompanies it.
arc-inputdetail:{ value: string }
Fired on each keystroke while editing, with the draft text in detail.value.

See Also