Skip to content

Fields

Character Count

A live, field-linked count of how much text remains before a native length limit.

When to use it: Use it when a text limit is important enough that people need to plan what they write.

On this page

Example

Loading example…
Character Count.tsxtsx
import { CharacterCount, Label, TextArea, TextField } from "@comp0/react";

export function Example() {
  return (
    <TextField defaultValue="" className="block w-full max-w-md text-base sm:text-sm">
      <Label className="mb-1 block font-medium text-zinc-950 dark:text-white">
        Short biography
      </Label>
      <TextArea
        name="biography"
        maxLength={160}
        rows={5}
        className="w-full rounded-lg border border-zinc-950/15 bg-white px-3 py-2 outline-teal-600 focus-visible:outline-2 dark:border-white/15 dark:bg-zinc-900 dark:outline-teal-400"
      />
      <CharacterCount
        maxLength={160}
        className="mt-1 block text-zinc-600 data-limit-reached:font-semibold data-limit-reached:text-amber-700 dark:text-zinc-400 dark:data-limit-reached:text-amber-300"
      />
    </TextField>
  );
}

Anatomy

Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.

A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.

  1. TextField

    Wrapper-free value provider shared by the native control and count. Does not add a DOM element.

  2. Label

    Visible name for the text control. Owns a DOM element.

  3. TextArea

    Native text control that enforces maxLength. Owns a DOM element.

  4. CharacterCount

    Polite output associated with the field through aria-describedby and for. Owns a DOM element.

Step by step

  1. 1

    Add the main part

    Give TextField an initial or controlled value so it can share the current text with CharacterCount.

  2. 2

    Add the supporting parts

    Put the same maxLength on TextArea and CharacterCount; the native control enforces the limit.

  3. 3

    Make the behavior clear

    Keep the output visible and concise; it is automatically associated with the field and announced politely.

    Exampletsx
    <TextField defaultValue="">
      <TextArea maxLength={160} />
      <CharacterCount maxLength={160} />
    </TextField>;

Keyboard

Moves to and from the native text control.

Forms and accessibility

TextArea submits its native value; CharacterCount submits nothing.

Accessibility checklist

  • Put the same non-negative maxLength on the native control and CharacterCount so the message matches the enforced limit.
  • Keep the count visible and associated with the control; do not communicate the limit only after it is reached.
  • Give TextField value, defaultValue, or onChange so CharacterCount receives the current text.

API reference

Importtsx
import { CharacterCount, Label, TextArea, TextField } from "@comp0/react";

TextField

Context only

Wrapper-free value provider shared by the native control and count.

PropTypeDescription
valuestringControlled text value.
defaultValuestringInitial uncontrolled text value.
onChange(value: string) => voidReceives the next text value.

Label

DOM element

Visible name for the text control.

TextArea

DOM element

Native text control that enforces maxLength.

PropTypeDescription
maxLengthnumberNative maximum accepted character count.

CharacterCount

DOM element

Polite output associated with the field through aria-describedby and for.

PropTypeDescription
maxLengthnumberNon-negative integer used to calculate remaining text.
childrenReactNode | (state: CharacterCountState) => ReactNodeCustom output receiving count, maximum, remaining, and limitReached.

Style hooks

Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.

CharacterCount

Style hookMeaning
[data-empty]The field has no text.
[data-limit-reached]No characters remain.
[data-count]Current character count.
[data-remaining]Characters remaining before the limit.

Keep exploring