Skip to content
edmi-ui

Textarea

Multi-line text that grows with its content.

StackBlitz ↗StackBlitz ↗StackBlitz ↗
Terminal window
npx shadcn@latest add @edmi-ui/textarea
Terminal window
pnpm dlx shadcn@latest add @edmi-ui/textarea
Terminal window
yarn dlx shadcn@latest add @edmi-ui/textarea
Terminal window
bunx shadcn@latest add @edmi-ui/textarea

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-vue@latest add @edmi-ui/textarea
Terminal window
pnpm dlx shadcn-vue@latest add @edmi-ui/textarea
Terminal window
yarn dlx shadcn-vue@latest add @edmi-ui/textarea
Terminal window
bunx shadcn-vue@latest add @edmi-ui/textarea

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

Terminal window
npx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/textarea.json
Terminal window
pnpm dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/textarea.json
Terminal window
yarn dlx shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/textarea.json
Terminal window
bunx --bun shadcn-svelte@latest add https://viandwi24.github.io/edmi-ui/r/svelte/textarea.json

Pick your package manager above; the choice is remembered. Add the @edmi-ui registry first, see Getting started.

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>
registry/ui/textarea.tsx
"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 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",
};
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 };
registry/ui/textarea/Textarea.vue
<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 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(() => 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>
registry/ui/textarea/index.ts
export { default as Textarea } from "./Textarea.vue";
src/lib/registry/ui/textarea/textarea.svelte
<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>
src/lib/registry/ui/textarea/index.ts
import Root from "./textarea.svelte";
export {
Root,
//
Root as Textarea,
};

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).

StackBlitz ↗StackBlitz ↗StackBlitz ↗

Sunken (-1)

Flat (0)

Raised (+1)

Floating (+2)

Sunken (-1)

Flat (0)

Raised (+1)

Floating (+2)

Sunken (-1)

Flat (0)

Raised (+1)

Floating (+2)

Prop Type Default
Props React.ComponentProps<"textarea"> none
elevation "auto" | "sunken" | "flat" | "raised" | "floating" "auto"
  • Same elevation levels, halo and invalid state as Input.