Getting StartedComponentsDesign TokensThemingTheme SynthesizerFrameworksAccessibilityUtilitiesServer RenderingBrowser SupportContributingChangelog App ShellAspect GridAuth ShellCenterClusterContainerDashboard GridDockFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkSpeed DialStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCalloutCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSeparatorSkeletonSpinnerStackVideoVirtual List Activity HeatmapAnimated NumberBadgeChartClockComparisonCountdown TimerData GridData TableDescription ListDiffEvent CalendarGaugeJSON TreeKanbanKey ValueLevel MeterListMeterSparklineStatStepperTableTagTimelineUptimeValue CardWaveform BlockquoteCode BlockGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputOTP InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree Select AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuGuided TourHover CardInline MessageLoading OverlayModalNotification PanelPopoverProgressProgress ToastSheetSnackbarSpotlightToastTooltip
ARC UI ARC Radiant Components
v3.2 Docs Components Tokens Synthesizer
Getting StartedFrameworksServer Rendering Design TokensThemingTheme SynthesizerTypographyUtilities All ComponentsAccessibilityBrowser SupportChangelogContributingStats App ShellAspect GridAuth ShellCenterClusterContainerDashboard GridDockFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkMenubarNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkSpeed DialStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCalloutCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageImage CompareImage HotspotsInfinite ScrollLightboxMarqueeQR CodeScroll AreaSeparatorSkeletonSpinnerStackVideoVirtual List Activity HeatmapAnimated NumberBadgeChartClockComparisonCountdown TimerData GridData TableDescription ListDiffEvent CalendarGaugeJSON TreeKanbanKey ValueLevel MeterListMeterSparklineStatStepperTableTagTimelineUptimeValue CardWaveform BlockquoteCode BlockGradient TextHighlightKbdKeyboard MapMarkdownNumber FormatProseTerminalTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerDate Range PickerFieldsetFile UploadFormHotkeyIcon ButtonImage CropperInline EditInputInput GroupKnobLabelMasked InputMulti SelectNumber InputOTP InputPassword InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSignature PadSliderSortable ListSwitch GroupTag InputTextareaTheme ToggleTime PickerToggleTransfer ListTree Select AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuConversationDialogDropdown MenuGuided TourHover CardInline MessageLoading OverlayModalNotification PanelPopoverProgressProgress ToastSheetSnackbarSpotlightToastTooltip

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.

Components Inline Edit
input interactive
<arc-inline-edit>

Overview

Inline Edit is text that happens to be editable. In its resting state it renders the current value as plain text with no field chrome at all — just 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 — only changes 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. 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 deliberately 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()` methods
  • 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>
import { InlineEdit } from '@arclux/arc-ui-react';

export default function TrackList() {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', maxWidth: 440, gap: 2 }}>
      <InlineEdit
        value="Midnight Signal"
        label="Track 1 title"
        placeholder="Untitled track"
        onArcChange={(e) => console.log('renamed to', e.detail.value)}
      />
      <InlineEdit value="Glass Harbor" label="Track 2 title" placeholder="Untitled track" />
      <InlineEdit label="Track 3 title" placeholder="Untitled track" />
    </div>
  );
}
<script setup>
import { InlineEdit } from '@arclux/arc-ui-vue';

function onRename(e) {
  console.log('renamed to', e.detail.value);
}
</script>

<template>
  <div style="display:flex; flex-direction:column; max-width:440px; gap:2px;">
    <InlineEdit value="Midnight Signal" label="Track 1 title" placeholder="Untitled track" @arc-change="onRename" />
    <InlineEdit value="Glass Harbor" label="Track 2 title" placeholder="Untitled track" />
    <InlineEdit label="Track 3 title" placeholder="Untitled track" />
  </div>
</template>
<script>
  import { InlineEdit } from '@arclux/arc-ui-svelte';

  function onRename(e) {
    console.log('renamed to', e.detail.value);
  }
</script>

<div style="display:flex; flex-direction:column; max-width:440px; gap:2px;">
  <InlineEdit value="Midnight Signal" label="Track 1 title" placeholder="Untitled track" on:arc-change={onRename} />
  <InlineEdit value="Glass Harbor" label="Track 2 title" placeholder="Untitled track" />
  <InlineEdit label="Track 3 title" placeholder="Untitled track" />
</div>
import { Component } from '@angular/core';
import { InlineEdit } from '@arclux/arc-ui-angular';

@Component({
  imports: [InlineEdit],
  template: `
    <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-change)="onRename($event)"></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>
  `,
})
export class TrackListComponent {
  onRename(e: CustomEvent<{ value: string }>) {
    console.log('renamed to', e.detail.value);
  }
}
import { InlineEdit } from '@arclux/arc-ui-solid';

export default function TrackList() {
  return (
    <div style={{ display: 'flex', 'flex-direction': 'column', 'max-width': '440px', gap: '2px' }}>
      <InlineEdit
        value="Midnight Signal"
        label="Track 1 title"
        placeholder="Untitled track"
        on:arc-change={(e) => console.log('renamed to', e.detail.value)}
      />
      <InlineEdit value="Glass Harbor" label="Track 2 title" placeholder="Untitled track" />
      <InlineEdit label="Track 3 title" placeholder="Untitled track" />
    </div>
  );
}
import { InlineEdit } from '@arclux/arc-ui-preact';

export default function TrackList() {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', maxWidth: 440, gap: 2 }}>
      <InlineEdit
        value="Midnight Signal"
        label="Track 1 title"
        placeholder="Untitled track"
        onArcChange={(e) => console.log('renamed to', e.detail.value)}
      />
      <InlineEdit value="Glass Harbor" label="Track 2 title" placeholder="Untitled track" />
      <InlineEdit label="Track 3 title" placeholder="Untitled track" />
    </div>
  );
}
<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>

API

value string ''
The committed text. Updated only when an edit commits (Enter or blur); keystrokes accumulate in an internal draft until then.
label string ''
Accessible name for the control. The display state announces as "Edit {label}" and the edit field is labeled with it. Always provide one.
name string ''
The `name` attribute sent with form data on submission. The submitted value is the committed `value`, never an in-progress draft.
placeholder string 'Empty'
Text shown in muted italic when `value` is empty, and as the field placeholder while editing. Defaults to "Empty".
multiline boolean false
When true, editing uses a `<textarea>`: Enter inserts a newline and Cmd/Ctrl+Enter commits. Single-line commits on plain Enter.
disabled boolean false
Prevents activation and applies a muted treatment. The value is excluded from form submission while disabled.
required boolean false
Marks the field as required. An empty committed value is invalid — including in display state, which shows a subtle error tint.
readonly boolean false
Renders the display state only: the text remains focusable for reading order, but activation is inert and no pencil affordance appears.
formAssociated boolean true
properties object { required: { type: Boolean, reflect: true }, readonly: { type: Boolean, reflect: true }, }
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).
autoValidates boolean true
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

Events

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

See Also