Let Customers Tell You More
Sometimes a dropdown isn’t enough. Customers might have specific instructions: “Enter through the side gate,” “Allergic to certain fertilizers,” or “Please call before arriving.” A notes textarea gives them space to share details.
What You’ll Learn
- Bind textarea values
- Add character limits
- Show remaining characters
- Make textareas auto-resize
Basic Textarea Binding
Textareas bind just like inputs:
<script>
let notes = $state('');
</script>
<label for="notes">Additional Notes (Optional)</label>
<textarea
id="notes"
bind:value={notes}
placeholder="Any special instructions or requests..."
></textarea>
<p>Notes: {notes || 'None provided'}</p> The bind:value keeps the textarea content synchronized with state.
Add a Character Limit
Prevent excessively long notes:
<script>
let notes = $state('');
const maxLength = 500;
let remaining = $derived(maxLength - notes.length);
let isOverLimit = $derived(notes.length > maxLength);
</script>
<label for="notes">Additional Notes (Optional)</label>
<textarea
id="notes"
bind:value={notes}
placeholder="Any special instructions or requests..."
maxlength={maxLength}
></textarea>
<p class="char-count" class:warning={remaining < 50} class:error={isOverLimit}>
{remaining} characters remaining
</p>
<style>
.char-count {
font-size: 0.875rem;
color: #666;
margin-top: 0.25rem;
}
.char-count.warning {
color: #f59e0b;
}
.char-count.error {
color: #ef4444;
}
</style> The maxlength attribute prevents typing beyond the limit. The character counter provides feedback.
Enforce Limit Without Attribute
If you want custom handling instead of the native maxlength:
<script>
let notes = $state('');
const maxLength = 500;
function handleInput(event) {
const value = event.target.value;
if (value.length <= maxLength) {
notes = value;
} else {
// Trim to max length
notes = value.slice(0, maxLength);
// Force the textarea to show trimmed value
event.target.value = notes;
}
}
</script>
<textarea
value={notes}
oninput={handleInput}
placeholder="Any special instructions..."
></textarea> This approach lets you add custom behavior when the limit is reached.
Auto-Resizing Textarea
Make the textarea grow as the user types:
<script>
let notes = $state('');
let textarea;
function autoResize() {
if (textarea) {
textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + 'px';
}
}
$effect(() => {
// Resize whenever notes change
notes;
autoResize();
});
</script>
<textarea
bind:this={textarea}
bind:value={notes}
oninput={autoResize}
placeholder="Any special instructions..."
rows="3"
></textarea>
<style>
textarea {
resize: none;
overflow: hidden;
min-height: 80px;
}
</style> bind:this gives us a reference to the DOM element, allowing us to manipulate its height.
Add to the Booking Form
Here’s the complete notes section:
<!-- filename: src/lib/components/BookingForm.svelte (partial) -->
<script>
let notes = $state('');
const maxLength = 500;
let remaining = $derived(maxLength - notes.length);
</script>
<fieldset>
<legend>Additional Information</legend>
<div class="field">
<label for="notes">
Special Instructions (Optional)
</label>
<textarea
id="notes"
name="notes"
bind:value={notes}
placeholder="Examples:
• Gate code: 1234
• Please text before arriving
• Dog is friendly but loud
• Specific areas to focus on"
maxlength={maxLength}
rows="4"
></textarea>
<p class="char-count" class:warning={remaining < 50}>
{remaining} characters remaining
</p>
</div>
</fieldset>
<style>
textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1rem;
font-family: inherit;
line-height: 1.5;
resize: vertical;
min-height: 100px;
}
textarea:focus {
outline: none;
border-color: #007bff;
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
}
textarea::placeholder {
color: #999;
}
.char-count {
font-size: 0.875rem;
color: #666;
margin: 0.25rem 0 0 0;
text-align: right;
}
.char-count.warning {
color: #f59e0b;
}
</style> The Complete Booking Form
With all fields in place, here’s the full form structure:
<!-- filename: src/lib/components/BookingForm.svelte -->
<script>
// Contact information
let customerName = $state('');
let customerEmail = $state('');
let customerPhone = $state('');
// Appointment details
let bookingDate = $state('');
let selectedTime = $state('');
// Service selection
let selectedService = $state('');
let selectedAddOns = $state([]);
// Additional info
let notes = $state('');
let agreeToTerms = $state(false);
// Form state
let submitted = $state(false);
function handleSubmit(event) {
event.preventDefault();
submitted = true;
// In a later module, we'll send this to the server
}
function resetForm() {
customerName = '';
customerEmail = '';
customerPhone = '';
bookingDate = '';
selectedTime = '';
selectedService = '';
selectedAddOns = [];
notes = '';
agreeToTerms = false;
submitted = false;
}
</script>
{#if submitted}
<div class="confirmation">
<h2>✓ Booking Request Received!</h2>
<p>Thank you, {customerName}! We'll confirm your booking shortly.</p>
<button onclick={resetForm}>Book Another Service</button>
</div>
{:else}
<form onsubmit={handleSubmit}>
<fieldset>
<legend>Contact Information</legend>
<!-- Name, email, phone fields -->
</fieldset>
<fieldset>
<legend>Appointment Details</legend>
<!-- Date and time fields -->
</fieldset>
<fieldset>
<legend>Select a Service</legend>
<!-- Service radio buttons -->
</fieldset>
<fieldset>
<legend>Add-Ons (Optional)</legend>
<!-- Add-on checkboxes -->
</fieldset>
<fieldset>
<legend>Additional Information</legend>
<!-- Notes textarea -->
</fieldset>
<label class="terms">
<input type="checkbox" bind:checked={agreeToTerms} required />
I agree to the terms and conditions
</label>
<button type="submit" disabled={!agreeToTerms}>
Request Booking
</button>
</form>
{/if} Common Mistakes
Using value Attribute Instead of Binding
<!-- ❌ Content won't update with state -->
<textarea value={notes}></textarea>
<!-- ✅ Use bind:value for two-way sync -->
<textarea bind:value={notes}></textarea> Forgetting font-family
/* ❌ Textarea has different default font */
textarea {
font-size: 1rem;
}
/* ✅ Match the rest of your form */
textarea {
font-size: 1rem;
font-family: inherit;
} Not Setting min-height
/* ❌ Textarea can be squished too small */
textarea {
resize: vertical;
}
/* ✅ Ensure minimum usable height */
textarea {
resize: vertical;
min-height: 100px;
} Module Complete! 🎉
You’ve finished Module 6: Data Binding. The booking form now has:
- Text inputs for name and email
- Date picker for appointment date
- Time slot dropdown
- Service selection with radio buttons
- Add-on checkboxes
- Notes textarea
- Terms checkbox
What you’ve learned:
bind:valuefor text, email, date inputsbind:valuefor select dropdownsbind:groupfor radio buttons and checkbox groupsbind:checkedfor single checkboxes- Character limits and counters
Summary
Textareas bind with bind:value just like other inputs. Add character limits for better UX, and consider auto-resize for longer content. The booking form is now feature-complete on the client side.
Key takeaways:
bind:valueworks with textarea- Use
maxlengthand character counters - Set
font-family: inheritfor consistent styling - Auto-resize improves UX for longer content
Next Steps
The form works, but it’s all in one file. Continue with Module 7: Component Communication to break it into reusable components.