Black Weeks. Full Access for 50% OFF. Use code lex50 at checkout.

You'll get every theme available plus future additions. That's 40 themes total.Unlimited projects. Lifetime updates. One payment.

Get full access

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

Give 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-model keeps the textarea reactive while a simple text.length drive both the counter and the red ring.
  • Tailwind’s ring utilities 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: 50 or any number that matches your product requirements.
  • Keep text empty 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 maxlength if 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-text spans so the numbers update automatically.
  • If you want to show remaining characters, compute threshold - text.length instead.

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.dispatchEvent or x-on hook when the user hits the threshold to show additional warnings or disable submit buttons.
  • Tie threshold to a select dropdown if you support variable limits per field.

/Michael Andreuzza

Did you like this post? Please share it with your friends!