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.jsonOr 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.
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.