@mshafiqyajid/react-textarea
Headless textarea hook + styled component. Auto-resize (min/max rows), character counter (inside or outside), three sizes, four tones, hint and error messages, controlled or uncontrolled.
Playground #
import { TextareaStyled } from "@mshafiqyajid/react-textarea/styled";
import "@mshafiqyajid/react-textarea/styles.css";
<TextareaStyled
placeholder="Type something..."
/>Install #
npm install @mshafiqyajid/react-textarea Quick start #
import { TextareaStyled } from "@mshafiqyajid/react-textarea/styled";
import "@mshafiqyajid/react-textarea/styles.css";
<TextareaStyled
label="Message"
placeholder="Type your message..."
maxLength={500}
showCount
/> Auto-resize #
autoResize is enabled by default. The textarea grows from minRows (default: 2) up to maxRows (default: 10) as the user types. Set autoResize={false} to disable and use resize instead.
<TextareaStyled
label="Notes"
minRows={3}
maxRows={8}
autoResize
placeholder="The textarea grows as you type..."
/>
{/* Manual resize */}
<TextareaStyled
label="Bio"
autoResize={false}
resize="vertical"
rows={4}
/> Character count #
Enable showCount with an optional maxLength. The counter can be positioned inside or outside the textarea. It turns amber when at the limit and red when over.
{/* Count outside (default) */}
<TextareaStyled
label="Bio"
maxLength={160}
showCount
/>
{/* Count inside */}
<TextareaStyled
label="Comment"
maxLength={280}
showCount
countPosition="inside"
/> Validation states #
Pass an error string to flip to the danger tone and render the message below. Use hint for plain helper text.
<TextareaStyled
label="Description"
value={text}
onChange={(e) => setText(e.target.value)}
error={text.length < 20 ? "Must be at least 20 characters" : undefined}
hint="Describe your project in a few sentences"
/> Controlled #
const [value, setValue] = useState("");
<TextareaStyled
value={value}
onChange={(e) => setValue(e.target.value)}
onValueChange={(v) => console.log(v)}
label="Message"
maxLength={200}
showCount
/> Headless #
Use useTextarea for full control over markup and styling. The hook manages value state, ARIA attributes, and character counting.
import { useTextarea } from "@mshafiqyajid/react-textarea";
const { textareaProps, charCount, isAtLimit, isOverLimit } = useTextarea({
defaultValue: "",
maxLength: 200,
required: true,
});
return (
<div>
<label htmlFor={textareaProps.id}>Message</label>
<textarea {...textareaProps} placeholder="Type something..." />
<span style={{ color: isOverLimit ? "red" : "inherit" }}>
{charCount} / 200
</span>
</div>
); API — TextareaStyled #
| Prop | Type | Default | Description |
|---|---|---|---|
| value | string | — | Controlled value |
| defaultValue | string | "" | Uncontrolled initial value |
| onChange | (e: ChangeEvent) => void | — | Native change event handler |
| onValueChange | (value: string) => void | — | Called with the string value on every change |
| placeholder | string | — | Placeholder text |
| rows | number | 3 | Row count when autoResize is false |
| minRows | number | 2 | Minimum rows when auto-resizing |
| maxRows | number | 10 | Maximum rows when auto-resizing |
| autoResize | boolean | true | Automatically grow/shrink the textarea |
| maxLength | number | — | Maximum character count |
| showCount | boolean | false | Show character counter |
| countPosition | "inside" | "outside" | "outside" | Where to render the counter |
| resize | "none" | "vertical" | "horizontal" | "both" | "none" | CSS resize when autoResize is false |
| size | "sm" | "md" | "lg" | "md" | Size variant |
| tone | "neutral" | "primary" | "success" | "danger" | "neutral" | Colour tone |
| label | string | — | Field label rendered above |
| hint | string | — | Helper text rendered below |
| error | string | — | Error message — auto-applies danger tone |
| required | boolean | false | Mark field as required (adds asterisk) |
| disabled | boolean | false | Disable the textarea |
| readOnly | boolean | false | Make the textarea read-only |
| invalid | boolean | false | Force invalid state without inline error text |
| id | string | auto | HTML id for the textarea |
| name | string | — | Form field name |
| autoFocus | boolean | — | Auto-focus on mount |
| spellCheck | boolean | — | Browser spell-check |
| className | string | — | Extra class on the root element |
| style | CSSProperties | — | Inline style on the root element |
API — useTextarea hook #
| Option | Type | Default | Description |
|---|---|---|---|
| value | string | — | Controlled value |
| defaultValue | string | "" | Uncontrolled initial value |
| onChange | (e: ChangeEvent) => void | — | Native change event |
| onValueChange | (value: string) => void | — | String value change handler |
| maxLength | number | — | Character limit for isAtLimit / isOverLimit |
| disabled | boolean | — | Disabled state |
| readOnly | boolean | — | Read-only state |
| required | boolean | — | Required state |
| invalid | boolean | — | Invalid state |
| id | string | auto | HTML id |
| name | string | — | Form name |
Returns { textareaProps, charCount, isAtLimit, isOverLimit }. Spread textareaProps on a <textarea> element.