shadcn.io is not affiliated with official shadcn/ui
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
Vinicius Vicentini
@vinihvc
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.jsonThis component depends on Button. Install it first if you haven't already.
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
├── EditableLabel
├── EditableInput
├── EditableInput
├── EditableEditTrigger
├── EditableSubmitTrigger
├── EditableCancelTrigger
└── EditableControlUsage
import { CheckIcon, XIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardHeader,
} from "@/components/ui/card";
import {
Editable,
EditableArea,
EditableCancelTrigger,
EditableControl,
EditableInput,
EditablePreview,
EditableSubmitTrigger,
} from "@/components/ui/editable";
import {
Field,
FieldGroup,
FieldLabel,
} from "@/components/ui/field";
import { Input } from "@/components/ui/input";<Card className="w-full max-w-sm">
<CardHeader
description="Click in the field or edit button to start editing"
title="Edit user"
/>
<CardContent>
<FieldGroup>
<Field>
<FieldLabel>Name</FieldLabel>
<Editable defaultValue="Vinicius Vicentini">
<EditableArea>
<EditableInput asChild>
<Input className="w-full" />
</EditableInput>
<EditablePreview />
</EditableArea>
<EditableControl>
<EditableCancelTrigger asChild>
<Button size="icon-md" variant="outline">
<XIcon />
</Button>
</EditableCancelTrigger>
<EditableSubmitTrigger asChild>
<Button size="icon-md" variant="outline">
<CheckIcon />
</Button>
</EditableSubmitTrigger>
</EditableControl>
</Editable>
</Field>
<Field>
<FieldLabel>Username</FieldLabel>
<Editable defaultValue="@vinihvc">
<EditableArea>
<EditableInput asChild>
<Input />
</EditableInput>
<EditablePreview />
</EditableArea>
<EditableControl>
<EditableCancelTrigger asChild>
<Button size="icon-md" variant="outline">
<XIcon />
</Button>
</EditableCancelTrigger>
<EditableSubmitTrigger asChild>
<Button size="icon-md" variant="outline">
<CheckIcon />
</Button>
</EditableSubmitTrigger>
</EditableControl>
</Editable>
</Field>
</FieldGroup>
</CardContent>
</Card>Accessibility
| Key | Description |
|---|---|
Escape | Cancel. |
Enter | Activate the focused item. |
Space | Activate the focused control. |
Demos
Activation Click
Activation Focus
Activation None
Controlled
Dblclick
Orientation Horizontal
Orientation Vertical
Size Lg
Size Md
Size Sm
With Textarea
Without Controls
API Reference
Editable
div
PropType
AttributeType
EditableArea
div
PropType
AttributeType
EditableInput
input
PropType
AttributeType
EditableEditTrigger
button
PropType
AttributeType
EditableSubmitTrigger
button
PropType
AttributeType
EditableCancelTrigger
button
PropType
AttributeType
EditableControl
div
PropType
AttributeType
EditablePreview
span
PropType
AttributeType
useEditable
PropType