Overview
The Textarea component is built on top of the Input component, providing a multi-line text input interface. It inherits the Input component’s architecture while optimizing for textarea-specific use cases.
Installation
npm install @svelte-atoms/core
Basic Usage
<script>
import { Textarea } from '@svelte-atoms/core/components/textarea';
let value = $state('');
</script>
<Textarea.Root>
<Textarea.Input bind:value placeholder="Enter your message..." />
</Textarea.Root>
Subcomponents
- Textarea.Root - Container for the textarea (extends Input.Root with textarea preset)
- Textarea.Input - The actual textarea element
Examples
Basic Textarea
<script>
let message = $state('');
</script>
<Textarea.Root>
<Textarea.Input bind:value={message} />
</Textarea.Root>
Textarea with Label
<script>
import { Label } from '@svelte-atoms/core/components/label';
import { Textarea } from '@svelte-atoms/core/components/textarea';
let comment = $state('');
</script>
<Label for="comment">Comment</Label>
<Textarea.Root>
<Textarea.Input id="comment" bind:value={comment} />
</Textarea.Root>
Custom Rows and Cols
<Textarea.Root>
<Textarea.Input rows={10} cols={50} />
</Textarea.Root>
With Character Limit
<script>
let bio = $state('');
const maxLength = 500;
</script>
<Textarea.Root>
<Textarea.Input
bind:value={bio}
maxlength={maxLength}
placeholder="Tell us about yourself..."
/>
</Textarea.Root>
<div class="text-sm text-muted-foreground">
{bio.length}/{maxLength} characters
</div>
Disabled and Readonly
<!-- Disabled -->
<Textarea.Root>
<Textarea.Input disabled value="This textarea is disabled" />
</Textarea.Root>
<!-- Readonly -->
<Textarea.Root>
<Textarea.Input readonly value="This textarea is readonly" />
</Textarea.Root>
Textarea.Root Props
preset
string
default:"'textarea'"
The preset key used for styling. Defaults to ‘textarea’ which extends the input preset.
Additional CSS classes to apply to the root container. Default includes h-auto to allow vertical expansion.
Child components, typically Textarea.Input.
Textarea.Input Props
The current text value. Bindable for two-way data binding.
Placeholder text displayed when the textarea is empty.
When true, the textarea is disabled and cannot be edited.
When true, the textarea is read-only and cannot be edited.
The number of visible text rows.
The number of visible text columns.
Maximum number of characters allowed.
Minimum number of characters required.
When true, the textarea must have a value for form submission.
When true, the textarea receives focus on mount.
Controls browser autocomplete behavior.
Controls spell checking behavior.
Controls how text wrapping is handled.
Additional CSS classes for the textarea element.
Default Styling
Textarea.Root
Extends Input.Root styling with:
h-auto /* Allows vertical expansion */
Textarea.Input
border-border
w-full
p-2
outline-none
HTML Attributes
The Textarea.Input component accepts all standard HTML textarea attributes, including:
id - Element ID for label association
name - Form field name
form - Associates with a form element
oninput - Input event handler
onchange - Change event handler
onfocus - Focus event handler
onblur - Blur event handler
Event Handling
<script>
function handleInput(e) {
console.log('Input event:', e.target.value);
}
function handleChange(e) {
console.log('Change event:', e.target.value);
}
function handleBlur(e) {
console.log('Lost focus');
}
</script>
<Textarea.Root>
<Textarea.Input
oninput={handleInput}
onchange={handleChange}
onblur={handleBlur}
/>
</Textarea.Root>
TypeScript Support
interface TextareaRootProps<
E extends keyof HTMLElementTagNameMap = 'div',
B extends Base = Base
> extends HtmlAtomProps<E, B>, TextareaRootExtendProps {}
interface TextareaInputProps extends TextareaInputExtendProps {
value?: string;
placeholder?: string;
disabled?: boolean;
readonly?: boolean;
rows?: number;
cols?: number;
maxlength?: number;
minlength?: number;
required?: boolean;
autofocus?: boolean;
autocomplete?: string;
spellcheck?: boolean;
wrap?: 'soft' | 'hard' | 'off';
}
Extending Textarea Props
// In your types file
declare module '@svelte-atoms/core/components/textarea' {
interface TextareaRootExtendProps {
variant?: 'default' | 'large';
}
interface TextareaInputExtendProps {
autoResize?: boolean;
}
}
Integration with Forms
<script>
import { Form } from '@svelte-atoms/core/components/form';
import { Textarea } from '@svelte-atoms/core/components/textarea';
import { z } from 'zod';
const messageSchema = z.string().min(10).max(500);
</script>
<Form.Root>
<Form.Field name="message" schema={messageSchema}>
<Form.Field.Label>Message</Form.Field.Label>
<Textarea.Root>
<Form.Field.Control base={Textarea.Input} />
</Textarea.Root>
<Form.Field.Errors />
</Form.Field>
</Form.Root>
Architecture
The Textarea component is built on top of the Input component:
<!-- Textarea.Root -->
<Input.Root preset="textarea" class={['h-auto', klass]}>
{@render children?.()}
</Input.Root>
This architecture means:
- Textarea.Root extends Input.Root with textarea-specific defaults
- All Input.Root features are available (bond system, state management)
- Preset system allows customization via “textarea” preset key
Accessibility
- Uses semantic
<textarea> element
- Supports all ARIA attributes
- Works with form labels via
id attribute
- Keyboard accessible (Tab to focus, Shift+Tab to unfocus)
- Supports screen readers
Best Practices
- Always provide a label: Use the Label component and associate it with the textarea via
id
- Set appropriate rows: Default is browser-dependent, explicitly set
rows for consistency
- Use maxlength for character limits: Combine with a character counter for better UX
- Provide clear placeholders: Help users understand what content is expected
- Consider auto-resize: For better UX, implement auto-resize based on content
- Handle validation: Use with Form.Field for built-in validation support
Common Patterns
Auto-Resizing Textarea
<script>
let textarea;
let value = $state('');
$effect(() => {
if (textarea) {
textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + 'px';
}
});
</script>
<Textarea.Root>
<Textarea.Input bind:this={textarea} bind:value />
</Textarea.Root>
Character Counter with Validation
<script>
let value = $state('');
const minChars = 50;
const maxChars = 500;
const isValid = $derived(value.length >= minChars && value.length <= maxChars);
const remaining = $derived(maxChars - value.length);
</script>
<Textarea.Root>
<Textarea.Input
bind:value
maxlength={maxChars}
class={!isValid && value.length > 0 ? 'border-red-500' : ''}
/>
</Textarea.Root>
<div class="text-sm" class:text-red-500={!isValid}>
{#if value.length < minChars}
Minimum {minChars} characters ({minChars - value.length} more needed)
{:else}
{remaining} characters remaining
{/if}
</div>
Related Components
- Input - For single-line text input
- Form - For form management and validation
- Label - For textarea labels