How to build a character limit textarea with Tailwind CSS and Alpine.js
Guide writers with a live character counter and automatic ring warning when they exceed your threshold, built with Tailwind CSS and Alpine.js.
Published on November 26, 2025 by Michael Andreuzza · 3 min readGive writers immediate feedback by showing how many characters they’ve typed and highlighting the textarea when they cross a limit. This component uses Alpine.js to track text length and Tailwind CSS to flip the focus ring to red once the threshold is exceeded.
Why it helps
- Copywriters see their remaining budget without modal popups or alerts.
x-modelkeeps the textarea reactive while a simpletext.lengthdrive both the counter and the red ring.- Tailwind’s
ringutilities create the warning state without extra CSS.
1. Alpine state and threshold
Store the message text plus a configurable limit. You can expose threshold as a prop if you reuse this component elsewhere.
<div class="w-full space-y-1" x-data="{ text: '', threshold: 50 }">
<!-- textarea + counter -->
</div>
- Start with
threshold: 50or any number that matches your product requirements. - Keep
textempty so the counter begins at zero.
2. Textarea binding + warning ring
Use x-model for the text and a dynamic class binding to change the ring when the user passes the limit.
<textarea
id="highlight-textarea"
x-model="text"
rows="4"
placeholder="Type your message"
:class="{ 'ring-red-500 focus:ring-red-500': text.length > threshold }"
class="block w-full px-4 py-2 text-sm text-blue-700 bg-white border border-transparent rounded-lg appearance-none duration-300 ring-1 ring-zinc-200 placeholder-zinc-400 focus:border-zinc-300 focus:bg-transparent focus:outline-none focus:ring-blue-500 focus:ring-offset-2 focus:ring-2"
></textarea>
- Keep the base ring styles in the static
class, then override with the conditional red ring. - You can add
maxlengthif you want to block further typing entirely, but the visual warning is often enough.
3. Counter display
A text row underneath shows the current count and the limit using Alpine’s templating.
<div class="mt-2 text-sm text-zinc-500">
Characters: <span x-text="text.length"></span> (Limit: <span x-text="threshold"></span>)
</div>
- Combine plain text with
x-textspans so the numbers update automatically. - If you want to show remaining characters, compute
threshold - text.lengthinstead.
4. Copy-and-paste snippet
Use the full markup below in any Alpine-enabled page.
<div class="w-full space-y-1" x-data="{ text: '', threshold: 50 }">
<label class="text-sm font-medium text-zinc-500">
Highlight excess characters
</label>
<textarea
id="highlight-textarea"
x-model="text"
rows="4"
placeholder="Type your message"
class="block w-full px-4 py-2 text-sm text-blue-700 bg-white border border-transparent rounded-lg appearance-none duration-300 ring-1 ring-zinc-200 placeholder-zinc-400 focus:border-zinc-300 focus:bg-transparent focus:outline-none focus:ring-blue-500 focus:ring-offset-2 focus:ring-2 sm:text-sm"
:class="{ 'ring-red-500 focus:ring-red-500': text.length > threshold }"
></textarea>
<div class="mt-2 text-sm text-zinc-500">
Characters: <span x-text="text.length"></span> (Limit:
<span x-text="threshold"></span>)
</div>
</div>
Finishing touches
- Surface the remaining count instead of total characters if that language resonates better (e.g., “12 characters left”).
- Trigger a
window.dispatchEventorx-onhook when the user hits the threshold to show additional warnings or disable submit buttons. - Tie
thresholdto a select dropdown if you support variable limits per field.
/Michael Andreuzza