Textarea
Multi-line text that grows with its content.
import { Button } from "@/components/ui/button";import { Textarea } from "@/components/ui/textarea";
export default function Demo() { return ( <div className="grid w-full max-w-sm gap-3"> <Textarea placeholder="Why this basket? Write the thesis in a few lines." /> <Textarea disabled placeholder="Mandate is locked after launch." /> <Textarea aria-invalid placeholder="Invalid" /> <div className="grid gap-2"> <Textarea placeholder="Message the agent..." /> <Button className="justify-self-end" size="sm"> Send </Button> </div> </div> );}<script setup lang="ts">import { Button } from "@/components/ui/button";import { Textarea } from "@/components/ui/textarea";</script>
<template> <div class="grid w-full max-w-sm gap-3"> <Textarea placeholder="Why this basket? Write the thesis in a few lines." /> <Textarea disabled placeholder="Mandate is locked after launch." /> <Textarea aria-invalid="true" placeholder="Invalid" /> <div class="grid gap-2"> <Textarea placeholder="Message the agent..." /> <Button class="justify-self-end" size="sm">Send</Button> </div> </div></template><script lang="ts"> import { Button } from "$lib/components/ui/button"; import { Textarea } from "$lib/components/ui/textarea";
let message = $state("");</script>
<div class="grid w-full max-w-sm gap-3"> <Textarea placeholder="Why this basket? Write the thesis in a few lines." /> <Textarea disabled placeholder="Mandate is locked after launch." /> <Textarea aria-invalid="true" placeholder="Invalid" /> <div class="grid gap-2"> <Textarea bind:value={message} placeholder="Message the agent..." /> <Button class="justify-self-end" size="sm" disabled={!message}>Send</Button> </div></div>Installation
Section titled “Installation”npx shadcn@latest add @edmi-ui/textareapnpm dlx shadcn@latest add @edmi-ui/textareayarn dlx shadcn@latest add @edmi-ui/textareabunx shadcn@latest add @edmi-ui/textareaPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-vue@latest add @edmi-ui/textareapnpm dlx shadcn-vue@latest add @edmi-ui/textareayarn dlx shadcn-vue@latest add @edmi-ui/textareabunx shadcn-vue@latest add @edmi-ui/textareaPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Install the dependencies:
Terminal window npm install @vueuse/coreTerminal window pnpm add @vueuse/coreTerminal window yarn add @vueuse/coreTerminal window bun add @vueuse/coreAdd the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/textarea.jsonpnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/textarea.jsonyarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/textarea.jsonbunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/textarea.jsonPick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.
Add the Edmi components this one builds on:
elevation.Copy the code from Component source into your project and update the import paths to match your aliases.
import { Button } from "@/components/ui/button";import { Textarea } from "@/components/ui/textarea";
<div className="grid w-full max-w-sm gap-3"> <Textarea placeholder="Why this basket? Write the thesis in a few lines." /> <Textarea disabled placeholder="Mandate is locked after launch." /> <Textarea aria-invalid placeholder="Invalid" /> <div className="grid gap-2"> <Textarea placeholder="Message the agent..." /> <Button className="justify-self-end" size="sm"> Send </Button> </div></div><script setup lang="ts">import { Button } from "@/components/ui/button";import { Textarea } from "@/components/ui/textarea";</script>
<template> <div class="grid w-full max-w-sm gap-3"> <Textarea placeholder="Why this basket? Write the thesis in a few lines." /> <Textarea disabled placeholder="Mandate is locked after launch." /> <Textarea aria-invalid="true" placeholder="Invalid" /> <div class="grid gap-2"> <Textarea placeholder="Message the agent..." /> <Button class="justify-self-end" size="sm">Send</Button> </div> </div></template><script lang="ts"> import { Button } from "$lib/components/ui/button"; import { Textarea } from "$lib/components/ui/textarea";</script>
<div class="grid w-full max-w-sm gap-3"> <Textarea placeholder="Why this basket? Write the thesis in a few lines." /> <Textarea disabled placeholder="Mandate is locked after launch." /> <Textarea aria-invalid="true" placeholder="Invalid" /> <div class="grid gap-2"> <Textarea placeholder="Message the agent..." /> <Button class="justify-self-end" size="sm">Send</Button> </div></div>Component source
Section titled “Component source”"use client";
import { cn } from "cn";import type * as React from "react";
import { type Elevation, useElevation } from "@/registry/edmi/ui/elevation";
// ✦ depth (v4): fields sink (-1) in layered mode; focus swaps the edge for the ringconst fieldElevation = { sunken: "border-sk-bd bg-sk-bg shadow-sunken focus-visible:bg-card", flat: "", raised: "border-transparent bg-[image:linear-gradient(var(--bv-face-b),var(--bv-face-b))] shadow-raised focus-visible:border-ring focus-visible:shadow-ring", floating: "border-transparent bg-[image:linear-gradient(var(--bv-face-b),var(--bv-face-b))] shadow-floating focus-visible:border-ring focus-visible:shadow-ring",};
function Textarea({ className, elevation, ...props}: React.ComponentProps<"textarea"> & { /** ✦ depth: sunken -1, flat 0, raised +1, floating +2. */ elevation?: Elevation;}) { const level = useElevation(elevation, "field"); return ( <textarea data-slot="textarea" className={cn( "flex field-sizing-content min-h-24 w-full min-w-0 rounded-md border border-input bg-card px-3 py-2.5 text-sm leading-relaxed text-foreground outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:shadow-ring disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-50 aria-invalid:border-destructive aria-invalid:shadow-ring-error", fieldElevation[level], className, )} {...props} /> );}
export { Textarea };<script setup lang="ts">import type { HTMLAttributes } from 'vue'import { useVModel } from '@vueuse/core'import { cn } from '@/registry/edmi/lib/utils'import { type Elevation, useElevation } from '@/registry/edmi/ui/elevation'
const props = withDefaults(defineProps<{ class?: HTMLAttributes['class'] defaultValue?: string | number modelValue?: string | number /** ✦ depth: sunken -1, flat 0, raised +1, floating +2 */ elevation?: Elevation}>(), { elevation: undefined })
const emits = defineEmits<{ (e: 'update:modelValue', payload: string | number): void}>()
const modelValue = useVModel(props, 'modelValue', emits, { passive: true, defaultValue: props.defaultValue,})
// ✦ depth (v4): fields sink (-1) in layered mode; focus swaps the edge for the ringconst fieldElevation = { sunken: 'border-sk-bd bg-sk-bg shadow-sunken focus-visible:bg-card', flat: '', raised: 'border-transparent bg-[image:linear-gradient(var(--bv-face-b),var(--bv-face-b))] shadow-raised focus-visible:border-ring focus-visible:shadow-ring', floating: 'border-transparent bg-[image:linear-gradient(var(--bv-face-b),var(--bv-face-b))] shadow-floating focus-visible:border-ring focus-visible:shadow-ring',}
const level = useElevation(() => props.elevation, 'field')</script>
<template> <textarea v-model="modelValue" data-slot="textarea" :class="cn('flex field-sizing-content min-h-24 w-full min-w-0 rounded-md border border-input bg-card px-3 py-2.5 text-sm leading-relaxed text-foreground outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:shadow-ring disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-50 aria-invalid:border-destructive aria-invalid:shadow-ring-error', fieldElevation[level], props.class)" /></template>export { default as Textarea } from "./Textarea.vue";<script lang="ts"> import { cn, type WithElementRef, type WithoutChildren } from "$lib/utils.js"; import { type Elevation, useElevation } from "$lib/registry/ui/elevation/index.js"; import type { HTMLTextareaAttributes } from "svelte/elements";
let { ref = $bindable(null), value = $bindable(), class: className, elevation = "auto", "data-slot": dataSlot = "textarea", ...restProps }: WithoutChildren<WithElementRef<HTMLTextareaAttributes>> & { /** ✦ depth: sunken -1, flat 0, raised +1, floating +2. */ elevation?: Elevation; } = $props(); // ✦ depth (v4): fields sink (-1) in layered mode; focus swaps the edge for the ring const fieldElevation = { sunken: "border-sk-bd bg-sk-bg shadow-sunken focus-visible:bg-card", flat: "", raised: "border-transparent bg-[image:linear-gradient(var(--bv-face-b),var(--bv-face-b))] shadow-raised focus-visible:border-ring focus-visible:shadow-ring", floating: "border-transparent bg-[image:linear-gradient(var(--bv-face-b),var(--bv-face-b))] shadow-floating focus-visible:border-ring focus-visible:shadow-ring", };
const level = useElevation(() => elevation, "field");</script>
<textarea bind:this={ref} data-slot={dataSlot} class={cn( "flex field-sizing-content min-h-24 w-full min-w-0 rounded-md border border-input bg-card px-3 py-2.5 text-sm leading-relaxed text-foreground outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:shadow-ring disabled:cursor-not-allowed disabled:bg-muted disabled:opacity-50 aria-invalid:border-destructive aria-invalid:shadow-ring-error", fieldElevation[level.current], className )} bind:value {...restProps}></textarea>import Root from "./textarea.svelte";
export { Root, // Root as Textarea,};Elevation ✦
Section titled “Elevation ✦”See the Elevation guide for the levels, roles and layered mode.
Same levels as Input: sunken (-1, the layered default), flat, raised (+1), floating (+2).
Sunken (-1)
Flat (0)
Raised (+1)
Floating (+2)
Sunken (-1)
Flat (0)
Raised (+1)
Floating (+2)
Sunken (-1)
Flat (0)
Raised (+1)
Floating (+2)
import { Textarea } from "@/components/ui/textarea";
const levels = [ { value: "sunken", label: "Sunken (-1)" }, { value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" },] as const;
export default function Demo() { return ( <div className="flex flex-col gap-5"> {levels.map(({ value, label }) => ( <div key={value} className="flex flex-col gap-2"> <p className="text-xs font-medium text-muted-foreground">{label}</p> <Textarea elevation={value} placeholder="Why this mandate?" className="max-w-sm" /> </div> ))} </div> );}<script setup lang="ts">import { Textarea } from "@/components/ui/textarea";
const levels = [{ value: "sunken", label: "Sunken (-1)" }, { value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" }];</script>
<template> <div class="flex flex-col gap-5"> <div v-for="level in levels" :key="level.value" class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{{ level.label }}</p> <Textarea :elevation="level.value" placeholder="Why this mandate?" class="max-w-sm" /> </div> </div></template><script lang="ts"> import { Textarea } from "$lib/components/ui/textarea";
const levels = [{ value: "sunken", label: "Sunken (-1)" }, { value: "flat", label: "Flat (0)" }, { value: "raised", label: "Raised (+1)" }, { value: "floating", label: "Floating (+2)" }] as const;</script>
<div class="flex flex-col gap-5"> {#each levels as level (level.value)} <div class="flex flex-col gap-2"> <p class="text-xs font-medium text-muted-foreground">{level.label}</p> <Textarea elevation={level.value} placeholder="Why this mandate?" class="max-w-sm" /> </div> {/each}</div>| Prop | Type | Default |
|---|---|---|
| Props | React.ComponentProps<"textarea"> |
none |
elevation |
"auto" | "sunken" | "flat" | "raised" | "floating" |
"auto" |
✦ Edmi additions
Section titled “✦ Edmi additions”- Same elevation levels, halo and invalid state as Input.