Textarea
Textarea that grows with its content instead of scrolling, down to three rows and up to a scrollable cap.
"use client";
import { useId } from "react";
import { Textarea } from "@/components/motion/textarea";
export function TextareaPreview() {
const id = useId();
return (
<label htmlFor={id} className="flex w-full max-w-xs flex-col gap-2 text-sm">
<span className="font-medium text-foreground">Message</span>
<Textarea id={id} placeholder="Type a message. It grows as you type." />
</label>
);
}
"use client";
// easeui.dev/components/motion/textarea
import { forwardRef, type TextareaHTMLAttributes } from "react";
import { cn } from "@/lib/utils";
export type TextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement>;
/**
* Textarea that grows with its content instead of scrolling, down to three
* rows and up to a scrollable cap. Shares Input's focus ring.
*/
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(function Textarea(
{ className, rows = 3, ...props },
ref,
) {
return (
<textarea
ref={ref}
rows={rows}
className={cn(
"block max-h-64 w-full resize-none rounded-xl bg-card px-3 py-2 text-base text-foreground shadow-[0_0_0_1px_var(--border-strong)] outline-none transition-shadow duration-150 ease-out sm:text-sm",
"[field-sizing:content]",
"placeholder:text-muted-foreground",
"focus:shadow-[0_0_0_2px_var(--accent)]",
"aria-invalid:shadow-[0_0_0_2px_var(--destructive)]",
"disabled:cursor-not-allowed disabled:opacity-50",
className,
)}
{...props}
/>
);
});
Installation
$ bunx --bun shadcn add @easeui/textarea
- 1
Set up the theme tokens
Do this once per project. Follow the theme setup or skip it if you already ran shadcn init.
- 2
Install the dependencies
terminal npm install clsx tailwind-merge - 3
Add the source files
components/motion/textarea.tsx "use client"; // easeui.dev/components/motion/textarea import { forwardRef, type TextareaHTMLAttributes } from "react"; import { cn } from "@/lib/utils"; export type TextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement>; /** * Textarea that grows with its content instead of scrolling, down to three * rows and up to a scrollable cap. Shares Input's focus ring. */ export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(function Textarea( { className, rows = 3, ...props }, ref, ) { return ( <textarea ref={ref} rows={rows} className={cn( "block max-h-64 w-full resize-none rounded-xl bg-card px-3 py-2 text-base text-foreground shadow-[0_0_0_1px_var(--border-strong)] outline-none transition-shadow duration-150 ease-out sm:text-sm", "[field-sizing:content]", "placeholder:text-muted-foreground", "focus:shadow-[0_0_0_2px_var(--accent)]", "aria-invalid:shadow-[0_0_0_2px_var(--destructive)]", "disabled:cursor-not-allowed disabled:opacity-50", className, )} {...props} /> ); });lib/utils.ts import { clsx, type ClassValue } from "clsx" import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) }
API reference
Updated