Nasaq

InlineEdit

Text that turns into an input in place with Save and Cancel, async save, validation by type, and keyboard and focus handling.

PreviewOpen ↗

Code

import { useState } from "react";import { InlineEdit } from "@fadymondy/nasaq/web";export function ProjectTitle() {  const [title, setTitle] = useState("Website redesign");  return (    <InlineEdit      label="title"      value={title}      required      maxLength={80}      displayClassName="text-h2"      onSave={async (next) => {        await api.rename(next);        setTitle(next);      }}    />  );}

Forms · beta

Live examples and controls: InlineEdit in the lab.

Install

npx shadcn@latest add https://docs.nasaqui.com/r/inline-edit.json

A value that reads as plain text until you click it. Then it becomes an input (or a textarea) in the same place, with Save and Cancel buttons. Use it for titles, names, descriptions and other single fields where opening a dialog or a form page would be too heavy.

When to use

  • Renaming a document, project or list item.
  • A profile or settings value edited one field at a time.
  • Short text or one paragraph that changes now and then.

When not to use

  • Several related fields saved together: use a form (profile-form, field).
  • Editing cells of a table: use DataTable's column edit and onCellEdit.
  • Long or formatted content: use rich-text-editor.

Import

import { InlineEdit } from "@fadymondy/nasaq/web";
// inside this monorepo: "@nasaq/web"

Quick start

import { useState } from "react";
import { InlineEdit } from "@fadymondy/nasaq/web";

export function ProjectTitle() {
  const [title, setTitle] = useState("Website redesign");
  return (
    <InlineEdit
      label="title"
      value={title}
      required
      maxLength={80}
      displayClassName="text-h2"
      onSave={async (next) => {
        await api.rename(next);
        setTitle(next);
      }}
    />
  );
}

Anatomy

InlineEdit           data-slot="inline-edit"  data-state="display" | "editing"
├─ display           <button> with the text and a pencil (shown on hover, focus and touch)
└─ editing           role="group"
    ├─ Input | Textarea
    ├─ Save (check) and Cancel (x) buttons
    └─ message       role="alert" for errors, or the multi-line key hint

API

PropTypeDefaultDescription
valuestringrequiredThe saved value.
onSave(value) => void | { error?: string } | Promise<...>requiredSave the new value. Return { error } or throw to keep editing. Busy while pending.
labelstringrequiredNames the field ("Edit title") and the empty text ("Add title").
type"text" | "number" | "url" | "email""text"Validates on save. number accepts Arabic-Indic digits. url accepts only http and https.
multiline, rowsboolean, numberfalse, 3A textarea. Enter adds a line, Ctrl or Cmd+Enter saves.
placeholderstringnoneShown for an empty value and in the input.
requiredbooleanfalseAn empty value is refused.
maxLengthnumbernoneCounted in characters.
validate(value) => string | undefinednoneReturn a message to refuse the value. Runs after the built-in checks.
onBlurAction"save" | "cancel" | "none""save"What leaving the open editor does.
renderValue(value) => ReactNodenoneCustom display, such as a link.
displayClassName, inputClassNamestringnoneMake the display match the heading it replaces.
ltrbooleantrue for number, email, urlForce left-to-right entry for codes and links inside Arabic pages.
disabled, readOnlybooleanfalseNo edit affordance.
editing, onEditingChangeboolean, (boolean) => voiduncontrolledControl the state, for example to start editing from a menu.
labelsInlineEditLabelsEnglish or ArabicOverrides for the strings.

Examples

<InlineEdit label="website" type="url" value={site} onSave={saveSite} />
<InlineEdit label="description" multiline rows={4} value={about} onSave={saveAbout} onBlurAction="none" />
<InlineEdit
  label="handle"
  value={handle}
  validate={(v) => (/^[a-z0-9_]+$/.test(v) ? undefined : "Use lowercase letters, digits and underscores.")}
  onSave={async (v) => ((await api.isTaken(v)) ? { error: "That handle is taken." } : void (await api.save(v)))}
/>

Arabic copy:

<InlineEdit label="العنوان" value="إعادة تصميم الموقع" onSave={save} labels={{ edit: "تعديل {label}" }} />

Accessibility

KeyAction
Enter, SpaceOn the text: start editing. In a single-line field: save.
Ctrl or Cmd + EnterSave a multi-line field.
EscapeCancel and give focus back to the text.
TabMove between the field, Save and Cancel.
  • The display is a real button named "Edit title". The editor is a group with the same name; the input has aria-invalid and aria-describedby pointing at the error.
  • Focus returns to the text after saving or cancelling, so keyboard users keep their place.
  • Enter during IME composition (Arabic or CJK input methods) is left to the IME.
  • Localise label, and labels where wording matters.

RTL & i18n

  • The text and input use dir="auto", so Arabic and English values each align naturally.
  • number, email and url fields are left-to-right so codes and links keep their order.
  • Numbers typed with Arabic-Indic digits are converted before validating.

Styling & tokens

Uses --nq-hover, --nq-focus and the control tokens. Target data-slot="inline-edit", data-state and data-pending. Pass displayClassName to match the surrounding type role.

Do / Don't

  • Do show what will happen: Save and Cancel are always visible while editing.
  • Do keep the display looking like the text it replaces.
  • Don't use it where a mistake is costly and needs review. Use a form with a confirmation.
  • Don't rely on hover for discovery. The pencil is always shown on touch and focus.

Lab

https://docs.nasaqui.com/?path=/docs/components-forms-inline-edit--docs

On this page