Editable Time Label

( Input / Form / Badge / Label )

A time label that expands into a 24-hour format input on edit and collapses into a read-only text badge upon saving.

Live preview and source code for Editable Time Label

An inline time field that holds hours and minutes in two small inputs and collapses into a compact read-only label when you confirm it. Editing and display are the same control rather than a field that swaps for text, so the row it sits in never changes height. The width of each number animates to fit its own digits, which is what makes the collapsed state read as a label instead of an empty input.

Installation

Copy the source from the Code tab above into your project as EditableTimeLabel.tsx.

It imports framer-motion for the width and radius animations and lucide-react for the check and pencil icons:

Terminal
npm install framer-motion lucide-react

This one is styled with Tailwind utility classes, so the copied file needs Tailwind set up in your project and the file included in your content paths. Its greys are literal hex values in the class names (bg-[#F5F5f5], text-[#B1B1B1]), so nothing here reads from your theme: to match your palette, replace those values in the file.

Usage

It takes no props and manages its own state, so this is the whole of it:

Example.tsx
import EditableTimeLabel from "./components/EditableTimeLabel";

export default function Example() {
    return <EditableTimeLabel />;
}

It starts in the expanded, editable state. Nothing outside the component can read the hours and minutes, so putting it in a real form means lifting that state out of the copied file, which the "When not to" section below covers.

When to reach for it

A duration in a settings row: a reminder offset, a session length, a cooking timer. The Hr. and Min. labels are fixed, so it reads as an amount of time rather than a clock time.

A field in a dense list where a full date and time picker would dominate the row, and the only thing being edited is a number of hours and minutes.

A form where the saved value should still look editable. The pencil icon on the collapsed state is the affordance that tells someone the label is a control.

When not to

If you are picking a time of day, use <input type="time">. It gives you the platform picker, meridiem handling, locale formatting and validation for free, none of which is here.

If you need the value. The component owns its state and exposes no props and no change callback, so nothing outside it can read the hours and minutes. Using it in a real form means editing the copied file to lift the state or add an onChange. That is a deliberate starting point, not an oversight, but it is work you have to do.

If the value must be validated. Input is clamped as you type and never rejected, so there is no error state, no required handling and no message to show when something is wrong.

Accessibility

Both inputs are labelled: Hr. and Min. are real <label> elements tied to the inputs by htmlFor, so each field is announced with its unit.

The confirm button is icon-only and has no accessible name. A screen reader announces it as an unlabelled button, and the icon swap between the check and the pencil is not announced at all. Add an aria-label that reflects the current state, and consider aria-expanded, before shipping it.

The collapsed state renders the numbers as plain text in a <div>, so the value stays readable, but nothing tells assistive technology that the control changed mode.

Notes

The confirm button has no type attribute, so inside a <form> it defaults to type="submit" and will submit on click. Add type="button" when you drop it into a form.

Hours are clamped to a maximum of 24 and minutes to 59, and anything that is not a digit is stripped as you type. Both fields accept two characters, and 24 is reachable, so clamp to 23 in the copied file if you are treating the value as a clock time.

The two fields are not symmetrical in the source: hours is a text input with inputMode="numeric" and minutes is a number input with its spinners hidden. Both behave the same when typed into.

The collapsed width is computed in ch units with a small correction for the digit 1, which assumes the surrounding font is roughly monospaced in its digits. A font with unusually narrow or wide numerals will make the collapsed label sit slightly off.