Micro-interactions

Inline Edit

Text that becomes its own input with nothing moving — one padded box, two contents, sized in CSS.

Install

bun add motion clsx tailwind-merge

Then copy 2 files into your project:

  • components/registry/inline-edit/inline-edit.tsx
  • lib/utils.ts

Props

PropTypeDefaultDescription
commitOn"blur" | "explicit""blur"Blur commits when focus leaves. Explicit grows a confirm and a cancel outside the box and treats losing focus as a cancel. Both are defensible, which is why it is a prop rather than a decision.
stiffnessnumber460Spring tension on the border, wash and ring arriving. This is the only thing that animates — everything else is already exactly where it will be.
dampingnumber32Spring friction on the same. Low overshoots the colour, which on a border reads as a flash rather than as bounce.
underline"dotted" | "solid" | "none""dotted"How the display state says it is editable. A text decoration rather than a border, because a border would have to be reserved in the editing state too — and none is the honest option for a row that already announces itself some other way.
padXnumber8Horizontal padding. Shared by both states by construction: it lives on the shell, which never unmounts, so the two contents cannot disagree about it.
padYnumber5Vertical padding. The one that shifts every row below it when an inline editor gets this wrong.
radiusnumber6Corner radius of the shell.
selectOnEditbooleantrueSelect the whole value on entering edit, so typing replaces it. Off puts the caret where the click landed, which is the right behaviour for a long value being amended rather than renamed.
allowEmptybooleanfalseAccept an empty commit. Off puts the previous value back, on the grounds that a blank field is a mis-click far more often than an intention.
showPencilbooleantruePencil on hover and keyboard focus. Absolutely positioned outside the shell — anything rendered inside it changes the width of the box the component exists to hold still.
multilinebooleanfalseA textarea that grows by line instead of a single-line field. The growth is the same CSS trick either way: an invisible copy of the draft sized in the same grid cell, so nothing is measured in JavaScript.
accentColorstring (hex)"#a855f7"The border, wash and ring of the editing state, and the underline of the display state. Expected as a 6-digit hex: the transparent variants are built by suffix so the fade runs on one interpolation rather than between two colours.

What It Demonstrates

Morph
One shape smoothly turns into another shape, e.g. Dynamic Island.
Continuity transition
A change that keeps the user oriented by visually connecting before and after. For example, making the same rectangle bigger and smaller.
Spatial consistency
Animating so an element keeps its identity and position across states, so users never lose track of where things went.

Related Components

Appears In