Shadcn Editable for React and Tailwind
Allows user to edit text in-place.
Edit user
Click in the field or edit button to start editing
Installation
bunx --bun shadcn@latest add https://kit.dev/r/editable.jsonpnpm dlx shadcn@latest add https://kit.dev/r/editable.jsonnpx shadcn@latest add https://kit.dev/r/editable.jsonyarn shadcn@latest add https://kit.dev/r/editable.json<Step>This component depends on Button. Install it first if you haven't already.</Step>
Install the following dependencies:
bun add @ark-ui/reactpnpm add @ark-ui/reactnpm install @ark-ui/reactyarn add @ark-ui/reactCopy and paste the following code into your project.
"use client";
import {
Editable as ArkEditable,
useEditableContext,
} from "@ark-ui/react/editable";
import { ark } from "@ark-ui/react/factory";
import { useFieldContext } from "@ark-ui/react/field";
import type React from "react";
import { cn } from "@/lib/utils";
import {
type ButtonProps,
buttonVariants,
} from "@/components/ui/button";
export const useEditable = useEditableContext;
const withoutHidden = <T extends Record<string, unknown>>(
props: T,
className?: string
) => {
const {
hidden,
className: fromApi,
...rest
} = props as T & {
className?: string;
hidden?: boolean;
};
return {
...rest,
className: cn(hidden && "hidden", fromApi, className),
};
};
export interface EditableProps
extends React.ComponentProps<typeof ArkEditable.Root> {
/**
* The orientation of the editable
*/
orientation?: "horizontal" | "vertical";
}
export const Editable = (props: EditableProps) => {
const { orientation = "horizontal", className, ...rest } = props;
return (
<ArkEditable.Root
className={cn(
"group/editable",
"relative",
"w-full",
"data-[orientation=vertical]:items-end",
"flex items-center gap-2",
className
)}
data-orientation={orientation}
data-slot="editable"
{...rest}
/>
);
};
export const EditableArea = (
props: React.ComponentProps<typeof ArkEditable.Area>
) => {
const { className, ...rest } = props;
return (
<ArkEditable.Area
className={cn("w-full", className)}
data-slot="editable-area"
{...rest}
/>
);
};
export interface EditableInputProps
extends Omit<React.ComponentProps<typeof ArkEditable.Input>, "size"> {}
export const EditableInput = (props: EditableInputProps) => {
const { className, ...rest } = props;
const field = useFieldContext();
const inputProps = withoutHidden(
useEditable().getInputProps() as Record<string, unknown>,
className
);
return (
<ark.input
aria-describedby={field?.ariaDescribedby}
data-slot="editable-input"
{...inputProps}
{...rest}
/>
);
};
interface EditablePreviewProps
extends React.ComponentProps<typeof ArkEditable.Preview> {
/**
* The size of the preview
*
* @default "md"
*/
size?: ButtonProps["size"];
/**
* The variant of the preview
*
* @default "outline"
*/
variant?: ButtonProps["variant"];
}
export const EditablePreview = (props: EditablePreviewProps) => {
const {
variant = "outline",
size = "md",
className,
children,
...rest
} = props;
const { children: valueText, ...safePreview } = withoutHidden(
useEditable().getPreviewProps() as Record<string, unknown>,
cn(
buttonVariants({ variant, size, clickEffect: false }),
"w-full justify-start",
"px-3",
"whitespace-pre-wrap font-normal text-base sm:text-sm",
"dark:hover:bg-input/32",
"data-placeholder-shown:text-muted-foreground",
"in-[[data-slot=editable-area]:has(textarea)]:items-start",
className
)
);
return (
<ark.span data-slot="editable-preview" {...safePreview} {...rest}>
{children ?? (valueText as React.ReactNode)}
</ark.span>
);
};
export const EditableControl = (
props: React.ComponentProps<typeof ArkEditable.Control>
) => {
const { className, ...rest } = props;
return (
<ArkEditable.Control
className={cn(
"group-data-[orientation=vertical]/editable:flex-col",
"inline-flex items-center gap-2",
className
)}
data-slot="editable-control"
{...rest}
/>
);
};
export const EditableEditTrigger = (
props: React.ComponentProps<typeof ArkEditable.EditTrigger>
) => <ArkEditable.EditTrigger data-slot="editable-edit-trigger" {...props} />;
export const EditableCancelTrigger = (
props: React.ComponentProps<typeof ArkEditable.CancelTrigger>
) => (
<ArkEditable.CancelTrigger data-slot="editable-cancel-trigger" {...props} />
);
export const EditableSubmitTrigger = (
props: React.ComponentProps<typeof ArkEditable.SubmitTrigger>
) => (
<ArkEditable.SubmitTrigger data-slot="editable-submit-trigger" {...props} />
);Update the import paths to match your project setup.
Anatomy
Editable
├── EditableArea
│ ├── EditablePreview
│ └── EditableInput
└── EditableControl
├── EditableEditTrigger
├── EditableCancelTrigger
└── EditableSubmitTriggerUsage
import {
Editable,
EditableArea,
EditableInput,
EditablePreview,
EditableControl,
EditableCancelTrigger,
EditableSubmitTrigger,
EditableEditTrigger,
} from "@/components/ui/editable";<Editable>
<EditableArea>
<EditableInput />
<EditablePreview />
</EditableArea>
<EditableControl>
<EditableCancelTrigger/>
<EditableSubmitTrigger/>
<EditableEditTrigger/>
</EditableControl>
</Editable>Controlled
Make the entire form editable at once by using the edit prop.
The value and onValueChange props control the editable component programmatically.
Modes
The activationMode prop controls how editing is triggered.
Focus
Editing starts when the field receives focus.
Click
Editing starts with a single click on the preview.
Double-Click
Editing starts with a double-click on the preview.
None
No automatic activation. Use EditableEditTrigger to manually start editing.
Sizes
The size prop on EditablePreview controls the preview dimensions.
Small
Large
Orientation
The orientation prop controls the layout of the editable component and its controls.
Horizontal
Vertical
Examples
With Textarea
Without Controls
Edit without control buttons. Press Enter to save, Escape to cancel.
API Reference
Editable
Root component. Manages edit mode and value.
| Prop | Type | Default |
|---|---|---|
activationMode | focus | dblclick | click | none | 'focus' |
orientation | "horizontal" | "vertical" | "horizontal" |
submitMode | both | enter | blur | none | 'both' |
asChild | boolean | - |
className | string | - |
EditableArea
Wraps the input and preview views.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | - |
className | string | - |
EditableInput
Input shown when editing. Use asChild for custom input elements.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | - |
className | string | - |
EditablePreview
Shows the value when not editing. Click to enter edit mode.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | - |
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" |
className | string | - |
EditableControl
Holds edit/cancel/submit control buttons.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | - |
className | string | - |
EditableEditTrigger
Enters edit mode on click.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | - |
className | string | - |
EditableCancelTrigger
Cancels editing and reverts to previous value.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | - |
className | string | - |
EditableSubmitTrigger
Submits the edited value and exits edit mode.
| Prop | Type | Default |
|---|---|---|
asChild | boolean | - |
className | string | - |
For a complete list of props, see the Ark UI documentation.