Component
Textarea
Native multi-line textarea styled to the DevSnips input visual language — the reference for the whole family.
- Component
- React
- TSX
- Tailwind CSS
- MIT
Preview
Live component
9:41 100%
devsnips.dev/library/react/components/textareas/textarea/ fluid · no fixed width 100%
Install
Add to your project
npx devsnips add React/Components/Textareas/textarea React/Components/Textareas/textarea import type { ReactNode, TextareaHTMLAttributes } from "react";
import { useId } from "react";
function cx(...parts: Array<string | false | null | undefined>): string {
return parts.filter(Boolean).join(" ");
}
/**
* Shared textarea visual language (DevSnips input/textarea tokens):
* full width, 80px minimum height (~3 rows of body-md), vertical-only
* resize, radius-sm, 1px color.border, bg color.input, body-md text,
* muted placeholder, hover/focus surface shift, focus-visible ring from
* color.focus-ring, muted disabled surface, subtle readonly surface,
* 150ms color transitions (off under prefers-reduced-motion).
*/
const TEXTAREA_BASE =
"w-full min-h-[80px] resize-y rounded-[var(--ds-radius-sm)] border bg-[var(--ds-color-input)] px-3 py-2 text-sm leading-5 text-[var(--ds-color-foreground)] shadow-none transition-colors duration-150 ease-out placeholder:text-[var(--ds-color-muted-foreground)] hover:bg-[var(--ds-color-input-hover,var(--ds-color-input))] focus:bg-[var(--ds-color-input-focus,var(--ds-color-input))] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ds-color-focus-ring)] disabled:pointer-events-none disabled:bg-[var(--ds-color-muted)] disabled:text-[var(--ds-color-muted-foreground)] disabled:opacity-60 read-only:bg-[var(--ds-color-surface-subtle)] read-only:text-[var(--ds-color-muted-foreground)] motion-reduce:transition-none";
const TEXTAREA_BORDER =
"border-[var(--ds-color-border)] focus:border-[var(--ds-color-border-strong)]";
export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
/** Visible label; omit and pass `aria-label` for a bare control. */
label?: ReactNode;
}
/**
* The reference textarea. A real native `<textarea>` — form submission,
* keyboard interaction, selection, copy/paste, `name`/`value`, `required`,
* `disabled`, `readOnly`, `minLength`/`maxLength` all keep their native
* behavior. Vertical resize stays enabled (the intentional DevSnips resize
* behavior); `rows` and `min-h-[80px]` give a sensible default height.
* Controlled (`value`/`onChange`) and uncontrolled (`defaultValue`) usage
* work natively — no duplicated state.
*/
export function Textarea({ label, id, className, rows = 3, ...props }: TextareaProps) {
const generatedId = useId();
const textareaId = id ?? `textarea-${generatedId}`;
const control = (
<textarea
id={textareaId}
rows={rows}
className={cx(TEXTAREA_BASE, TEXTAREA_BORDER, className)}
{...props}
/>
);
if (label === undefined || label === null) {
return control;
}
return (
<div className="w-full">
<label
htmlFor={textareaId}
className="mb-2 block text-[13px] font-medium leading-5 text-[var(--ds-color-foreground)]"
>
{label}
</label>
{control}
</div>
);
}
export default Textarea; /* DevSnips React — JavaScript parity build.
* Same API, behavior, and classes as code.tsx; TypeScript types removed.
* Regenerated from code.tsx — edit code.tsx and re-run the generator.
*/
import { useId } from "react";
function cx(...parts) {
return parts.filter(Boolean).join(" ");
}
const TEXTAREA_BASE = "w-full min-h-[80px] resize-y rounded-[var(--ds-radius-sm)] border bg-[var(--ds-color-input)] px-3 py-2 text-sm leading-5 text-[var(--ds-color-foreground)] shadow-none transition-colors duration-150 ease-out placeholder:text-[var(--ds-color-muted-foreground)] hover:bg-[var(--ds-color-input-hover,var(--ds-color-input))] focus:bg-[var(--ds-color-input-focus,var(--ds-color-input))] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--ds-color-focus-ring)] disabled:pointer-events-none disabled:bg-[var(--ds-color-muted)] disabled:text-[var(--ds-color-muted-foreground)] disabled:opacity-60 read-only:bg-[var(--ds-color-surface-subtle)] read-only:text-[var(--ds-color-muted-foreground)] motion-reduce:transition-none";
const TEXTAREA_BORDER = "border-[var(--ds-color-border)] focus:border-[var(--ds-color-border-strong)]";
export function Textarea({ label, id, className, rows = 3, ...props }) {
const generatedId = useId();
const textareaId = id ?? `textarea-${generatedId}`;
const control = <textarea
id={textareaId}
rows={rows}
className={cx(TEXTAREA_BASE, TEXTAREA_BORDER, className)}
{...props}
/>;
if (label === undefined || label === null) {
return control;
}
return <div className="w-full">
<label
htmlFor={textareaId}
className="mb-2 block text-[13px] font-medium leading-5 text-[var(--ds-color-foreground)]"
>
{label}
</label>
{control}
</div>;
}
export default Textarea; # Textarea
Native multi-line textarea styled to the DevSnips input visual language — the reference for the whole family.
## Usage
```tsx
<Textarea label="Project description" placeholder="What does this project do?" rows={4} />
```
## JavaScript
A `code.jsx` build is provided for projects that ship plain JSX. It exposes the same API and behavior as `code.tsx` — only the TypeScript types are removed.
```jsx
<Textarea label="Project description" placeholder="What does this project do?" rows={4} />
```
## Props
| Name | Type | Default | Description |
|---|---|---:|---|
| `label` | `ReactNode` | — | Visible label (omit and pass `aria-label` for a bare control). |
| `value` / `defaultValue` | `string` | — | Controlled / uncontrolled value. |
| `onChange` | `(event) => void` | — | Native change callback. |
| `rows` | `number` | `3` | Visible rows — the natural height floor (with `min-h-[80px]`). |
| `placeholder` | `string` | — | Muted placeholder (never critical information). |
| `disabled` | `boolean` | — | Native disabled — not focusable, not submitted. |
| `readOnly` | `boolean` | — | Native read-only — focusable, selectable, submitted. |
| `required` / `name` / `id` | `boolean` / `string` | — | Native form semantics (`id` also the label `htmlFor`). |
| `minLength` / `maxLength` | `number` | — | Native length constraints. |
| `className` | `string` | — | Extra Tailwind classes merged onto the control. |
| other native props / `aria-*` | — | — | Passed through to the `<textarea>`. |
## States
A real native `<textarea>` styled with Tailwind + the `--ds-*` semantic tokens. Default / hover / focus / focus-visible / filled / disabled / read-only states all come from native pseudo-classes; dark mode follows the token theme. Vertical resize stays enabled (the intentional DevSnips resize behavior — horizontal resize is off so layouts never break). Controlled (`value`/`onChange`) and uncontrolled (`defaultValue`) usage work natively with no duplicated state.
## Accessibility
Real `<textarea>` element — full native keyboard interaction, selection, copy/paste, and form semantics are preserved. Pass `label` for a visible `<label htmlFor>` or `aria-label`/`aria-labelledby` for a bare control. Supporting text uses `aria-describedby`. Visible `focus-visible` ring from `--ds-color-focus-ring`. Disabled uses the real `disabled` attribute; read-only uses real `readOnly`.
## Styling
Built with React, Tailwind CSS, and DevSnips design tokens. The component consumes the `--ds-*` semantic tokens via arbitrary values (for example `bg-[var(--ds-color-input)]`). Define the tokens once in your theme — no component-specific CSS file is required.
## Design Tokens
See [React/DESIGN_TOKENS.md](../../../DESIGN_TOKENS.md) for the authoritative token specification. This textarea uses the semantic color, radius, spacing, and motion tokens.
## Notes
This is the reference implementation for the Textareas family — it establishes the shared full-width layout, `min-h-[80px]` floor, `px-3 py-2` padding, `text-sm` typography, `radius-sm`, 1px `color.border`, `color.input` background, muted placeholder, focus ring, `resize-y` behavior, disabled/read-only treatments, and dark-mode behavior that every other textarea extends. 66 lines UTF-8 · LF · Spaces: 2
Continue browsing