ATOM
Components (registry)Forms

Textarea

A multi-line box that grows with longer answers. Bios, messages, and free-form notes.

Preview

Editorial

<!-- F12c editorial — non-derivable only. Review: Karen. -->

## Ejemplos

Bio with validation:

```tsx import { Textarea } from '@/components/atoms/Textarea'; import { Field } from '@/components/atoms/Field';

<Field label="Bio" htmlFor="bio" error={err} description="Max 280 characters"> <Textarea id="bio" error={!!err} rows={4} value={bio} onChange={(e) => setBio(e.target.value)} /> </Field> ```

## Accesibilidad

- Always pair with `Field` (or an explicit `<label htmlFor>`). - `error` is visual on the control — put the message string on `Field` for `role="alert"`.

### Correcto

- aria-invalid='true' se agrega automaticamente con `error={true}` - disabled remueve del tab order y desactiva resize - Focus ring visible para navegacion por teclado - Usar dentro de Field para label + mensaje de error asociados via htmlFor/id - rows controla la altura inicial visible — mejor UX que min-height CSS

### Evitar

- No usar placeholder como label — desaparece al escribir - No desactivar resize globalmente — los usuarios necesitan ajustar altura para textos largos - No usar Textarea para input de una linea — usa Input en su lugar

## Cuándo no usar

- Single-line values (email, name) → `Input`. - Structured choices → `Select` / `Combobox`, not a free-text box.

## Criterio de uso

- Define `rows` según la respuesta esperada; un área demasiado pequeña obliga a editar dentro de un viewport incómodo, especialmente en móvil. - Para notas largas, comunica límite o tamaño esperado con `Field` y valida sin borrar texto durante la edición. - Si el valor tiene estructura conocida, usa un control especializado en lugar de pedir formato libre.

## Gotchas

- `error` en `Textarea` cambia la superficie visual; el mensaje humano debe vivir en `Field` para que el error sea anunciado. - Conserva el label visible aunque el placeholder incluya un ejemplo. - **Nota**: La diferencia clave con Input: line-height 1.5 (multi-linea), min-height 80px en vez de height fijo 40px, padding en los 4 lados en vez de solo horizontal, y resize: vertical habilitado. - **Nota**: CSS autocontenido. Mismos colores y focus rings que Input standalone.

Uso

import { Textarea } from '@/components/atoms/Textarea';

<Textarea rows={4} placeholder="Notes" />

Props

  • error: boolean

Gotchas

  • a11y

    Pair with Field for label/helper/error; set rows thoughtfully for mobile keyboards.

Anatomía CSS

<div class="textarea">

</div>
ClasePropósito
textarearoot

Tokens resueltos

Valores finales tras seguir la cadena de tokens. Derivados del source: si un token cambia, esta tabla cambia sola.

VariantePropValorToken
allbg#fafafabackground
allfg#525252placeholder
allborder#f84131destructive
allfocused-border#f84131destructive
alldisabled-bg#f5f5f5muted
alldisabled-fg#525252muted.foreground
alldisabled-border#f5f5f5muted

Animaciones

PropiedadDuraciónEasing
border-colorvar(--duration-150)var(--easing-out)
box-shadowvar(--duration-150)var(--easing-out)

CSS mínimo funcional

Autocontenido: sin imports ni tokens. Para previews y prototipos — en producción se consume el CSS del DS (@atom-uikit/css/components.css</code>, <code>@atom-uikit/tokens/tokens.css).

/* -------------------------------------------------------------------------
   Textarea

   Multi-line text input. Shares color system with Input.
   Min height 80px, resizable vertically by default.
   Colors matched to Figma atom-text-field.

   States: default, focus, disabled, error
   ------------------------------------------------------------------------- */

.textarea {
  display: flex;
  width: 100%;
  min-height: 5rem;
  padding: 8px;
  border: 1.5px solid #d4d4d4;
  border-radius: 8px;
  background-color: #fafafa;
  color: #0a0a0a;
  font-family: 'inter tight', -apple-system, blinkmacsystemfont, 'segoe ui', roboto, helvetica, arial, sans-serif, ui-sans-serif, system-ui, sans-serif;
  font-size: 12.8px;
  line-height: 1.5;
  resize: vertical;
  outline: none;
  transition:
    border-color 150ms cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 150ms cubic-bezier(0.22, 1, 0.36, 1);
}

.textarea::placeholder {
  color: #525252;
}

.textarea:focus {
  border-color: #171717;
  box-shadow: 0 0 0 2px var(--focus-ring-color);
}

.textarea:disabled {
  background-color: #f5f5f5;
  border-color: #f5f5f5;
  color: #525252;
  cursor: not-allowed;
  resize: none;
  opacity: 1;
}

.textarea[aria-invalid="true"] {
  border-color: #f84131;
  box-shadow: 0 0 2px 0 var(--focus-ring-error-color);
}

.textarea[aria-invalid="true"]:focus {
  border-color: #f84131;
  box-shadow: 0 0 0 2px var(--focus-ring-error-color);
}

Codigo fuente

components-react
components/atoms/Textarea.tsx
import { forwardRef, type TextareaHTMLAttributes } from 'react';

export type TextareaProps = {
  error?: boolean;
  className?: string;
} & Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'className'>;

function cn(...classes: (string | false | undefined | null)[]) {
  return classes.filter(Boolean).join(' ');
}

export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
  ({ error = false, className, ...props }, ref) => {
    return (
      <textarea
        ref={ref}
        className={cn('textarea', className)}
        aria-invalid={error || undefined}
        {...props}
      />
    );
  },
);

Textarea.displayName = 'Textarea';

Componentes relacionados

On this page

Detalles

Publicado14 de mayo de 2026
Categoriaforms
Lectura...
Visitas...
Ayuda?Slack

Componente

Textarea

Source

components-react / cssDisponible via MCP: atom_uikit_source("textarea")
Abrir en Storybook