Skip to content
Lenso UI

TextArea

Primitive multiline text input component that accepts standard HTML attributes

Usage

import { TextArea } from '@lenso/ui';
"use client";
// Adapted from HeroUI v3.2.6 (e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e), Apache-2.0.import { TextArea } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";
const styles = stylex.create({ field: { width: 384, height: 128 } });export function Basic() {  return (    <TextArea      aria-label="Quick project update"      xstyle={styles.field}      placeholder="Share a quick project update..."    />  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

For validation, labels, and error messages, see TextField.

Examples

Variants

The TextArea component supports two visual variants:

  • primary (default) - Standard styling with shadow, suitable for most use cases
  • secondary - Lower emphasis variant without shadow, suitable for use in Surface components
"use client";// Adapted from HeroUI v3.2.6 (e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e), Apache-2.0.import { TextArea } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  root: { display: "flex", width: 280, flexDirection: "column", gap: 8 },});export function Variants() {  return (    <div {...stylex.props(styles.root)}>      <TextArea fullWidth placeholder="Primary textarea" variant="primary" />      <TextArea fullWidth placeholder="Secondary textarea" variant="secondary" />    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

In Surface

When used inside a Surface component, use variant="secondary" to apply the lower emphasis variant suitable for surface backgrounds.

"use client";// Adapted from HeroUI v3.2.6 (e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e), Apache-2.0.import { Surface, TextArea } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  root: { width: "100%", borderRadius: 24, padding: 24 },  field: { width: "100%", minWidth: 280 },});export function OnSurface() {  return (    <Surface xstyle={styles.root}>      <TextArea xstyle={styles.field} placeholder="Describe your product" variant="secondary" />    </Surface>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

Full Width

"use client";// Adapted from HeroUI v3.2.6 (e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e), Apache-2.0.import { TextArea } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({ root: { width: 400 } });export function FullWidth() {  return (    <div {...stylex.props(styles.root)}>      <TextArea fullWidth placeholder="Full width textarea" />    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

Controlled

"use client";// Adapted from HeroUI v3.2.6 (e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e), Apache-2.0.import { Description, TextArea } from "@lenso/ui";import * as React from "react";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  root: { display: "flex", width: 384, flexDirection: "column", gap: 8 },});export function Controlled() {  const [value, setValue] = React.useState("");  return (    <div {...stylex.props(styles.root)}>      <TextArea        aria-describedby="textarea-controlled-description"        aria-label="Announcement"        placeholder="Compose an announcement..."        value={value}        onValueChange={setValue}      />      <Description id="textarea-controlled-description">        Characters: {value.length} / 280      </Description>    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

Rows and Resizing

"use client";// Adapted from HeroUI v3.2.6 (e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e), Apache-2.0.import { Label, TextArea } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  root: { display: "flex", width: 384, flexDirection: "column", gap: 16 },  field: { display: "flex", flexDirection: "column", gap: 8 },  resize: { resize: "vertical" },});export function Rows() {  return (    <div {...stylex.props(styles.root)}>      <div {...stylex.props(styles.field)}>        <Label htmlFor="textarea-rows-3">Short feedback</Label>        <TextArea          aria-label="Short feedback"          id="textarea-rows-3"          placeholder="This week's highlights..."          rows={3}        />      </div>      <div {...stylex.props(styles.field)}>        <Label htmlFor="textarea-rows-6">Detailed notes</Label>        <TextArea          aria-label="Detailed notes"          id="textarea-rows-6"          placeholder="Write out the full meeting notes..."          rows={6}          xstyle={styles.resize}        />      </div>    </div>  );}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

Customization

Tailwind CSS

"use client";// Adapted from HeroUI v3.2.6 (e385ac202b2cdb94b1bf6fa76d32c31c8259cc5e), Apache-2.0.import { TextArea } from "@lenso/ui";import * as stylex from "@stylexjs/stylex";const styles = stylex.create({  field: {    height: 112,    width: "100%",    maxWidth: 320,    fontSize: 14,    borderRadius: 12,    borderWidth: 1,    borderStyle: "solid",    borderColor: "color-mix(in oklab, var(--border) 80%, transparent)",    backgroundColor: "var(--surface)",    color: { default: "oklch(26.9% 0 0)", ':is(.dark *, [data-theme="dark"] *)': "oklch(97% 0 0)" },    boxShadow: {      default: "0 1px 2px 0 rgb(0 0 0 / .05), 0 0 0 1px rgb(0 0 0 / .05)",      ":focus-visible": "0 1px 2px 0 rgb(0 0 0 / .05), 0 0 0 2px oklch(70.8% 0 0 / .25)",      ':is(.dark *, [data-theme="dark"] *)':        "0 1px 2px 0 rgb(0 0 0 / .05), 0 0 0 1px rgb(255 255 255 / .1)",      ':is(.dark *, [data-theme="dark"] *):focus-visible':        "0 1px 2px 0 rgb(0 0 0 / .05), 0 0 0 2px oklch(55.6% 0 0 / .3)",    },    transitionProperty: "box-shadow, border-color",    transitionDuration: "150ms",    "::placeholder": {      color: {        default: "oklch(70.8% 0 0)",        ':is(.dark *, [data-theme="dark"] *)': "oklch(55.6% 0 0)",      },    },  },});export function CustomStyles() {  return <TextArea aria-label="Notes" xstyle={styles.field} placeholder="Add a note..." />;}

Local adaptation source above. Derived from HeroUI v3.2.6 source.

Global CSS

Override the shared .textarea class once with Tailwind's @layer components.

@layer components {  .textarea {    @apply rounded-xl border border-border bg-surface px-4 py-3 text-sm leading-6 shadow-sm;
    &:hover,    &[data-hovered="true"] {      @apply bg-surface-secondary border-border/80;    }
    &:focus-visible,    &[data-focus-visible="true"] {      @apply border-accent ring-2 ring-accent/20;    }
    &[data-invalid="true"] {      @apply border-danger bg-danger-soft text-danger;    }  }}

Styling Reference

HeroUI follows the BEM methodology to ensure component variants and states are reusable and easy to customize.

CSS Classes

Base Classes [!toc]

  • .textarea – Underlying <textarea> element styling

Interactive States

  • Hover: :hover or [data-hovered="true"]
  • Focus Visible: :focus-visible or [data-focus-visible="true"]
  • Invalid: [data-invalid="true"]
  • Disabled: :disabled or [aria-disabled="true"]

API Reference

TextArea

TextArea accepts all standard HTML <textarea> attributes plus the following:

PropTypeDefaultDescription
classNamestring-Tailwind classes merged with the base styles.
rowsnumber3Number of visible text lines.
colsnumber-Visible width of the text control.
valuestring-Controlled value for the textarea.
defaultValuestring-Initial uncontrolled value.
onChange(event: React.ChangeEvent<HTMLTextAreaElement>) => void-Change handler.
placeholderstring-Placeholder text.
disabledbooleanfalseDisables the textarea.
readOnlybooleanfalseMakes the textarea read-only.
requiredbooleanfalseMarks the textarea as required.
namestring-Name for form submission.
autoCompletestring-Autocomplete hint for the browser.
maxLengthnumber-Maximum number of characters.
minLengthnumber-Minimum number of characters.
wrap'soft' | 'hard'-How text wraps when submitted.
fullWidthbooleanfalseWhether the textarea should take full width of its container
variant"primary" | "secondary""primary"Visual variant of the component. primary is the default style with shadow. secondary is a lower emphasis variant without shadow, suitable for use in surfaces.

For validation props like isInvalid, isRequired, and error handling, use TextField with TextArea as a child component.

See upstream TextArea showcases. Product showcases are not part of the local component runtime.