designComponents

Search the design system…

Search the design system…

patterns

EditableTitle

Click-to-edit page heading. Drop into `<DSPageShell title={…}>` on pages where the user can rename the entity (campaigns, audiences, projects). Otherwise leave the static string title — this is opt-in per page.

Reads as a regular h1 until clicked. Selects the full text on focus so the user can either replace or append. Enter or blur commits; Esc reverts; an empty / unchanged value reverts silently. Visual matches the static `<DSPageShell>` title (text-2xl, semibold, -0.02em tracking) so swapping a page from static to editable doesn't shift the layout by a pixel.

Install

Pull this component (and its dependencies) straight into your app via the shadcn CLI:

npx shadcn@latest add https://design.oapps.io/r/editable-title.json

Or import from the workspace package:

import { EditableTitle, EditableTitleProps } from "@8maverik8/design";

Examples

Inside DSPageShell

The canonical use — slot it into the `title` prop. Header actions and footer behave exactly as with a static title.

Click the title to edit. Enter or click-away saves; Esc reverts.

14 campaigns · last updated 2m ago

const [title, setTitle] = useState("Spring 2026 launch");

<DSPageShell
  title={
    <EditableTitle
      value={title}
      onSave={async (next) => {
        await renameCampaign(id, next);
        setTitle(next);
      }}
    />
  }
  headerActions={<Button><Plus />New campaign</Button>}
  footer="14 campaigns · last updated 2m ago"
>
  …
</DSPageShell>

Standalone

Outside DSPageShell — works the same. The visual matches a `text-2xl` heading.

<EditableTitle
  value={name}
  onSave={async (next) => { await rename(next); }}
  placeholder="Project name"
/>

Anatomy

  • valuePersisted title from your store / server. Re-syncs the local draft when changed externally (only while not editing).
  • onSaveCalled with the trimmed new value after Enter / blur. Return a Promise to gate the read-mode swap on persistence — the input stays in committed state until it resolves.
  • placeholderShown muted when value is empty (read mode) or as input placeholder (edit mode). Default `"Untitled"`.
  • readOnlyRender as a plain `<h1>` with no edit affordance — for view-only roles.
  • maxLengthHard cap on the input length. Default 200.

Guidelines

  • Use this only when the title represents a *user-owned* entity name (campaign, audience, dashboard).Renaming "Settings" or "Billing" makes no sense — those are app sections, not data.
  • Persist the change in `onSave` (mutation / fetch / server action) before relying on the new value elsewhere.The component's own state is just an optimistic local draft. Source of truth lives in the caller.
  • Render two EditableTitle on the same page.Two editable heading levels on one screen is a sign you should split into separate pages or use a normal Input for the secondary one.
  • Set `readOnly` for roles without rename permission instead of hiding the title chrome.Keeps layout identical across roles — no shifting when permissions change.