nili/ui
All components

TextArea

Overview
10 exports5 variant axes31 props4 files
import { TextArea, TextAreaResize } from '@nili/ui';

This is the library’s own overview page, rendered live. Switch the language or the theme in the header and it follows.

Label, hint and error

The presence of error is what marks the field invalid, so the border and the message can never disagree — there is no separate invalid flag to forget.

Markdown is supported.

import { TextArea } from "@nili/ui"

export function Example() {
  return (
    <>
      <TextArea
        label="Description"
        placeholder="Tell us a little about the project…"
        hint="Markdown is supported."
        rows={4}
      />

      <TextArea
        label="Description"
        defaultValue="Too short"
        error="Please write at least twenty characters."
        rows={4}
      />
    </>
  )
}

Character counter

showCount puts used / limit under the field. countLimit sets a soft limit without also setting maxLength — a hard maxLength silently swallows the end of a paste, which is worse than showing someone they are over.

0/120
0 of 200
import { TextArea } from "@nili/ui"

export function Example() {
  const [value, setValue] = useState("")

  return (
    <>
      {/* soft limit: typing past it is allowed, and shown */}
      <TextArea
        label="Bio"
        showCount
        countLimit={120}
        value={value}
        onChange={(e) => setValue(e.target.value)}
        rows={3}
      />

      {/* hard limit */}
      <TextArea label="Tweet" showCount maxLength={280} rows={3} />

      {/* custom format */}
      <TextArea
        label="Notes"
        showCount
        countLimit={200}
        formatCount={(used, limit) => `${used} of ${limit}`}
        rows={3}
      />
    </>
  )
}

Resize handles

Which handles the browser offers. vertical is the default; none for a field inside a tight layout that must not be dragged out of shape.

resize=vertical
resize=both
resize=none
import { TextArea } from "@nili/ui"

export function Example() {
  return (
    <>
      <TextArea label="Vertical (default)" resize="vertical" rows={3} />
      <TextArea label="Both axes" resize="both" rows={3} />
      <TextArea label="Fixed" resize="none" rows={3} />
    </>
  )
}

Auto resize

Grows with the content instead of scrolling, up to maxRows. Off by default: a field that resizes on every keystroke moves everything below it, which is unpleasant in a long form.

import { TextArea } from "@nili/ui"

export function Example() {
  return (
    <TextArea
      label="Message"
      autoResize
      maxRows={8}
      rows={2}
      placeholder="Type a few lines…"
    />
  )
}