Show Remaining Characters in a Filament Text Input
At work I’ve recently brought up the idea of showing a “remaining characters indicator” to our Filament form components. A small circle that appears when you soon reach the maximum character limit and that turns red once you’re over that limit.
Here is a quick video on how this feature would look like:
After internal discussions, we’ve decided to scrap the idea again, but I thought why not share my hacky solution with the rest of the world?
Adding the Indicator #
I’m in a Laravel 12 and Filament 5 project here and abused the suffix and extraAttributes methods on a TextInput-component to make this work.
This is how my form field declaration looks like.
TextInput::make('name')
->label('Name')
->required()
->maxLength(50)
->suffix(new HtmlString(Blade::render('<x-remaining-characters />')))
->extraAttributes([
'class' => 'relative',
'x-data' => "{
maxChars: 50,
get charCount() { return \$state ? \$state.length : 0 },
get remaining() { return this.maxChars - this.charCount },
get percentage() { return (this.charCount / this.maxChars) * 100 },
get isOverLimit() { return this.charCount > this.maxChars },
get showCount() { return this.remaining <= 40 || this.isOverLimit },
get strokeColor() {
if (this.charCount === 0) return '#e5e7eb';
if (this.isOverLimit) return '#ef4444';
if (this.remaining <= 40) return '#f59e0b';
return '#3b82f6';
},
get circumference() { return 2 * Math.PI * 10 },
get strokeDashoffset() {
const progress = Math.min(this.percentage, 100);
return this.circumference - (progress / 100) * this.circumference;
}
}",
]),
The important pieces here are the values passed to maxLength and maxChars which dictate the maximum allowed length of the input. Then there is a condition to check, if the user is near the maximum length (this.remaining <= 40).
The rest of the Alpine.js code is there to calculate the right text color and stroke offset of the little circle we display in our Blade component.
And the code below is the content of the <x-remaining-characters /> Blade component located in resources/components/remaining-characters.blade.php.
<div data-class="absolute bottom-1 right-1 select-none">
<div class="relative w-6 h-6 flex items-center justify-center">
<!-- Character count in center -->
<span
x-show="showCount"
x-cloak
class="text-[10px] font-bold absolute z-10 leading-none"
:class="{
'text-red-600': isOverLimit,
'text-amber-600': !isOverLimit && remaining <= 40,
'text-blue-600': !isOverLimit && remaining > 40
}"
x-text="remaining"
></span>
<!-- SVG Circle (24px) -->
<svg
class="transform -rotate-90 absolute"
width="24"
height="24"
viewBox="0 0 24 24"
>
<circle
cx="12"
cy="12"
r="10"
fill="none"
stroke="#e5e7eb"
stroke-width="2"
/>
<circle
x-show="charCount > 0"
cx="12"
cy="12"
r="10"
fill="none"
:stroke="strokeColor"
stroke-width="2"
:stroke-dasharray="circumference"
:stroke-dashoffset="strokeDashoffset"
class="transition-all duration-200 ease-out"
stroke-linecap="round"
/>
</svg>
</div>
</div>
As you can see, we use the values from our Alpine.js component here to change the text color, to show the remaining allowed characters as well as the offset of a SVG circle.
This approach worked well for a proof of concept. As mentioned, we’ve collectively decided that we don’t even want to have such indicators in our app, so we scrapped the idea again.
If this would have landed in production, I definitely would have turned the code into a more reusable system, by either creating a macro or creating a dedicated TextInputWithRemainingCharacterCount-component for this.