Textarea
A versatile multiline text input component with support for labels, validation, character counting, and auto-resize. Perfect for comments, descriptions, and long-form content. Built for Pulse Framework with full reactivity support.
Import
import { Textarea, Pulse } from '@odyssee/components';Basic Usage
With Label and Hint
Add labels and helper text for better UX.
Sizes
Five size options control padding and text size: xs, sm, md, lg, and xl.
Rows
Control the initial height with the rows prop.
Required Field
Mark fields as required with visual indicators.
With Error
Display validation errors.
Character Count
Show character count with maximum length.
Auto-Resize
Automatically grow/shrink based on content.
const autoResizeTextarea = (
<Textarea
label="Description"
autoResize={true}
minRows={2}
maxRows={8}
placeholder="Type to see auto-resize in action..."
/>
);Disabled State
Disable the textarea.
Readonly State
Make the textarea read-only.
Reactive Value
Control textarea value with Pulse signals.
const message = Pulse.signal('');
const reactiveTextarea = (
<div>
<Textarea
label="Message"
value={message}
onChange={(val) => message(val)}
placeholder="Type something..."
/>
<div class="mt-4">
<p class="text-sm text-gray-600">You typed:</p>
<p class="text-sm font-mono">{message() || '(nothing yet)'}</p>
</div>
</div>
);With Focus Events
Handle focus and blur events.
const textareaWithEvents = (
<Textarea
label="Message"
placeholder="Focus on me..."
onFocus={() => console.log('Focused!')}
onBlur={() => console.log('Blurred!')}
/>
);Form Validation
Integrate with form validation.
const FeedbackForm = () => {
const feedback = Pulse.signal('');
const error = Pulse.signal('');
const validate = () => {
const value = feedback();
if (value.length === 0) {
error('Feedback is required');
return false;
}
if (value.length < 10) {
error('Feedback must be at least 10 characters');
return false;
}
error('');
return true;
};
const handleSubmit = (e: Event) => {
e.preventDefault();
if (validate()) {
console.log('Submitted:', feedback());
}
};
return (
<form onsubmit={handleSubmit} class="space-y-4">
<Textarea
label="Your Feedback"
value={feedback}
onChange={(val) => {
feedback(val);
validate();
}}
error={error()}
placeholder="Tell us what you think..."
rows={4}
required
/>
<Button type="submit">Submit Feedback</Button>
</form>
);
};Complete Example
Here's a comprehensive comment form with all features:
import { Textarea, Button, Alert, Pulse } from '@odyssee/components';
const CommentForm = () => {
const comment = Pulse.signal('');
const isSubmitting = Pulse.signal(false);
const error = Pulse.signal('');
const success = Pulse.signal(false);
const maxLength = 500;
const minLength = 10;
const isValid = Pulse.computed(() => {
const len = comment().length;
return len >= minLength && len <= maxLength;
});
const handleSubmit = async (e: Event) => {
e.preventDefault();
if (!isValid()) {
error(`Comment must be between ${minLength} and ${maxLength} characters`);
return;
}
isSubmitting(true);
error('');
try {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1500));
console.log('Comment submitted:', comment());
success(true);
comment('');
// Hide success message after 3 seconds
setTimeout(() => success(false), 3000);
} catch (err) {
error('Failed to submit comment. Please try again.');
} finally {
isSubmitting(false);
}
};
return (
<div class="max-w-2xl">
{success() && (
<Alert
color="success"
dismissible={true}
onDismiss={() => success(false)}
className="mb-4"
>
Comment submitted successfully!
</Alert>
)}
<form onsubmit={handleSubmit} class="space-y-4">
<Textarea
label="Add a Comment"
value={comment}
onChange={(val) => {
comment(val);
error('');
}}
error={error()}
placeholder="Share your thoughts..."
maxLength={maxLength}
showCount={true}
autoResize={true}
minRows={3}
maxRows={10}
disabled={isSubmitting()}
required
/>
<div class="flex items-center justify-between">
<p class="text-xs text-gray-500">
{comment().length >= minLength ? (
<span class="text-green-600">✓ Minimum length reached</span>
) : (
<span>At least {minLength} characters required</span>
)}
</p>
<div class="flex gap-2">
<Button
variant="outline"
onClick={() => comment('')}
disabled={isSubmitting() || comment().length === 0}
>
Clear
</Button>
<Button
type="submit"
color="primary"
loading={isSubmitting()}
disabled={!isValid()}
>
Post Comment
</Button>
</div>
</div>
</form>
</div>
);
};Props
| Prop | Type | Default | Description |
|---|---|---|---|
value | string | Signal<string> | - | Textarea value |
placeholder | string | "" | Placeholder text |
label | string | - | Label text |
hint | string | - | Helper text below input |
error | string | - | Error message |
size | "xs" | "sm" | "md" | "lg" | "xl" | "md" | Textarea size |
rows | number | 3 | Initial number of rows |
maxLength | number | - | Maximum character count |
showCount | boolean | false | Show character counter |
autoResize | boolean | false | Auto-resize based on content |
minRows | number | 2 | Minimum rows (auto-resize) |
maxRows | number | 10 | Maximum rows (auto-resize) |
disabled | boolean | false | Disable textarea |
readonly | boolean | false | Make read-only |
required | boolean | false | Mark as required |
onChange | (value: string) => void | - | Change event handler |
onFocus | (event: FocusEvent) => void | - | Focus event handler |
onBlur | (event: FocusEvent) => void | - | Blur event handler |
className | string | - | Additional CSS classes |
id | string | Auto-generated | HTML id attribute |
Accessibility
The Textarea component follows accessibility best practices:
- ✅ Proper label association with
forattribute - ✅ ARIA attributes for errors and descriptions
- ✅ Required field indicators
- ✅ Keyboard navigation support
- ✅ Focus management
- ✅ Screen reader friendly error messages
ARIA Attributes
const accessibleTextarea = (
<Textarea
label="Feedback"
aria-label="User feedback"
aria-describedby="feedback-hint"
aria-invalid={hasError}
/>
);Best Practices
✅ Do
- Provide clear labels and hints
- Show character limits when applicable
- Use appropriate row heights for expected content
- Validate input and show helpful error messages
- Use auto-resize for dynamic content
- Disable during submission
// Good: Clear label, validation, and limits
const goodTextarea = (
<Textarea
label="Product Review"
hint="Share your experience with this product"
maxLength={500}
showCount={true}
placeholder="Write your review..."
required
/>
);❌ Don't
- Don't use textarea for single-line input (use Input)
- Don't forget to handle long text gracefully
- Don't hide character limits
- Don't make textareas too small for expected content
- Don't forget to validate on blur
// Bad: Too small for expected content
const badTextarea = (
<Textarea
label="Essay (500 words)"
rows={2}
/>
);
// Better: Appropriate size with auto-resize
const betterTextarea = (
<Textarea
label="Essay (500 words)"
autoResize={true}
minRows={5}
maxRows={20}
maxLength={3000}
showCount={true}
/>
);Use Cases
Comment Section
const CommentBox = () => (
<Textarea
label="Leave a comment"
placeholder="What are your thoughts?"
autoResize={true}
minRows={2}
maxRows={8}
/>
);Feedback Form
const FeedbackBox = () => (
<Textarea
label="How can we improve?"
hint="Your feedback helps us build better products"
maxLength={1000}
showCount={true}
rows={5}
/>
);Bio Editor
const BioEditor = () => (
<Textarea
label="About Me"
hint="Tell others about yourself"
maxLength={200}
showCount={true}
autoResize={true}
minRows={3}
/>
);Message Composer
const MessageComposer = () => (
<Textarea
label="Message"
placeholder="Type your message..."
autoResize={true}
minRows={1}
maxRows={10}
/>
);Styling & Theming
All textarea styles use Tailwind CSS classes and support dark mode automatically.
Custom Styling
const customTextarea = (
<Textarea
className="font-mono bg-gray-50 dark:bg-gray-800"
placeholder="Custom styled textarea"
/>
);TypeScript
Full TypeScript support with complete type definitions:
import type { TextareaProps } from '@odyssee/components';
const props: TextareaProps = {
label: 'Message',
placeholder: 'Type here...',
maxLength: 500,
showCount: true,
autoResize: true,
onChange: (value: string) => {
console.log('Value:', value);
}
};
const textarea = <Textarea {...props} />;Related Components
- Input - For single-line text input
- Form Group - Group form elements
- Button - For form actions
Version: 1.0.0
Last Updated: January 2025