Forms✨ Premium

Character Counter

Fixed-height text area with an "n / 150" counter tucked into its bottom-right corner, amber at 80% of the limit, red once it is reached.

JSCounterTextarea

Updated

This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Forms has 20 effects, including 2 free. Explore the category →

3 usage examples

Reminder text in a practice back office — the limit of one message — Character Counter example 1

① Reminder text in a practice back office — the limit of one message

WhenThe receptionist writes the reminder sent the day before the appointment; the counter out of 160 turns amber near the end of the text message.
WhyPast 160 characters a text message is billed twice: the counter inside the area recalls the constraint without taking your eyes off the text.
Settingsmaxlength="160" (amber at 128), light teal theme (--chct-field:#fff, --chct-border:#c5dcdc, --chct-focus:#0f766e, --chct-warning:#b45309), --chct-min-height:118px, --chct-width:100%. Typing driven by the scene.
Support tool — a reply template inserted with one click — Character Counter example 2

② Support tool — a reply template inserted with one click

WhenThe agent clicks the "Delivery time" template: the text lands in one go and the counter turns amber, 133 of 150.
WhyAfter an insertion you cannot tell how much room is left to personalise: the colour says so before you even start editing.
SettingsDark theme (--chct-field:#191c2b, --chct-border:#33384f, --chct-focus:#818cf8), shipped limit of 150, --chct-min-height:128px. The template button belongs to the scene: it writes the value then fires input, which the effect listens to.
Question to the speaker during a webinar — a small box in the side panel — Character Counter example 3

③ Question to the speaker during a webinar — a small box in the side panel

WhenA viewer taps the box and types a question; with a 45 limit, the counter turns amber from 36 characters.
WhyA low limit keeps questions short and readable for the host live; the corner counter takes no room in a narrow panel.
Settingsmaxlength="45", --chct-min-height:112px so the whole question fits above the counter, green accent (--chct-focus:#22c55e). The tap circle belongs to the scene.

How it works

The counter is a <span> positioned absolutely in the block's bottom-right corner, over the area's bottom padding (30 px kept free). On every input event, the script writes "length / maximum", the maximum coming from the maxlength attribute (150 shipped); changing that attribute is enough for a 160-character text message or a 45-character question.

At 80% of the maximum (120 of 150) the counter gets the is-warning class, amber; at the limit, is-limit, red. The area keeps its height (100 px minimum): the user can enlarge it vertically with the handle (resize: vertical), otherwise the text scrolls.

The <textarea> tag is empty in the shipped HTML: the placeholder shows and the counter starts at 0 (keep both tags touching if you edit it). No fixed ids: two areas on the same page count separately, and the field has a name sent with the form.

Accessibility

  • Label: "Message" in a visually hidden <label>, to rename for your use.
  • Linked counter: aria-describedby points to the counter, read when entering the area.
  • Measured announcements: an aria-live="polite" region speaks when entering amber ("30 characters left") then at the limit, never on every key.
  • Motion: no animation apart from the border colour change on focus.
  • Limit: if the text runs past the visible height, its last lines can slide under the counter. Pick a height (--chct-min-height) that holds the whole limit.

Browser compatibility

1.8 KB of JavaScript, CSS with variables. Measured on 08/11/2026 in Chromium, Firefox and WebKit (Playwright): empty starting value, placeholder shown, "1 / 150" after one key, 150 / 150 in red after an over-long paste, two independent copies, touch at 390 px, no errors. Floor set by NodeList.forEach (Chrome 51, Firefox 50, Safari 10, according to @mdn/browser-compat-data).

Chrome 51+✓ Full (measured)
Firefox 50+✓ Full (measured)
Safari 10+✓ Full (measured in WebKit)
Edge 79+✓ Full (Chromium engine)
iOS Safari 10+✓ Not checked on a real device; there is no resize handle on iOS
Android Chrome 51+✓ Full (Chromium engine)

Without JavaScript, the counter stays on "0 / 150" but the browser still enforces maxlength.

The code

HTML structure to paste into your page (the CSS + JS come with your purchase):

index.html — structure
<div class="fx-0299-root">
  <div class="chct-group">
    <label class="chct-field">
      <span class="chct-sr">Message</span>
      <textarea class="chct-input" name="message" placeholder="Type your message..." maxlength="150"></textarea>
    </label>
    <span class="chct-count">0 / 150</span>
    <span class="chct-sr chct-status" aria-live="polite"></span>
    <span class="chct-words" hidden>
      <span data-msg="warning">{n} characters left</span>
      <span data-msg="limit">{max}-character limit reached</span>
    </span>
  </div>
</div>

One-time payment, no subscription — the code is yours forever

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Limit (HTML — maxlength on the area) 150 The counter text and thresholds follow; also put the right value in the initial text of .chct-count for display without JavaScript.
Amber threshold (JS — max * 0.8) 80% 0.9 to warn only in the last 10%.
Counter colours (CSS — --chct-muted, --chct-warning, --chct-limit on .fx-0299-root) #a1a1aa · #f59e0b · #ef4444 On a light background: #6b7280, #b45309, #dc2626.
Height (CSS — --chct-min-height) 100px Pick a height that holds the whole limit so the text never slides under the counter.
Field (CSS — --chct-field, --chct-border, --chct-focus, --chct-text) transparent · 30% indigo · #6366f1 · #fff Background, border at rest and on focus, text colour.
Width and frame (CSS — --chct-width, --chct-bg, --chct-root-min-height) 280px · #0a0a0f · 300px On a page: --chct-width:100%; --chct-bg:transparent; --chct-root-min-height:0.
Announced messages (HTML — .chct-words) "{n} characters left" · "{max}-character limit reached" {n} = characters left, {max} = limit.

FAQ

Here the area keeps its height and the counter lives inside, in the corner: handy when space is tight (panel, card) and the layout must not move. The other area pushes content down as you write, with its counter underneath.
After a form reset, yes, automatically. After a value set by a script, fire textarea.dispatchEvent(new Event('input', {bubbles: true})): that is what the template button does in the support example.
The counter reads value.length, like maxlength: a common emoji counts as 2, some as more. Counter and limit therefore agree with each other, but not necessarily with a text-message carrier's count.