Skip to content

substrate-system/character-counter

Repository files navigation

Character Counter

tests types module install size gzip size semantic versioning Common Changelog license

A visual character counter web component with a circular progress indicator, inspired by Bluesky's post composer.

See a live demo

Featuring

Contents

Install

npm i -S @substrate-system/character-counter

Use

Basic Example

This calls the global function customElements.define. Just import, then use the tag in your HTML.

Note

The tag name is exposed at CharacterCounter.TAG

import { CharacterCounter } from '@substrate-system/character-counter'

document.querySelector(CharacterCounter.TAG)  // 'character-counter' tag

Import JavaScript

import '@substrate-system/character-counter'

Import CSS

import '@substrate-system/character-counter/css'

Or minified:

import '@substrate-system/character-counter/min/css'

Use in HTML

<character-counter max="300" count="50"></character-counter>

Dynamic Updates

Update the count attribute as the user types:

const textarea = document.querySelector('textarea')
const counter = document.querySelector('character-counter')

textarea.addEventListener('input', () => {
    counter.setAttribute('count', textarea.value.length)
})

Or use the setter:

counter.count = textarea.value.length

Attributes

Attribute Type Default Description
max number 300 Maximum character count
count number 0 Current character count
hide-count boolean false When present, never shows the remaining count text
warn boolean | number false When present as boolean, shows count when within 20 of limit; when set to a number, shows count when that many characters remain (ignored if hide-count is present)

Display Behavior

The component's text display behavior depends on these attributes:

  • Default (no attributes): Always shows the remaining count
  • With warn (boolean): Only shows count when 20 or fewer characters remain
  • With warn="N" (number): Only shows count when N or fewer characters remain
  • With hide-count: Never shows the count (takes precedence over warn)
<!-- Always show count -->
<character-counter max="300" count="50"></character-counter>

<!-- Only show count when near limit (20 characters) -->
<character-counter max="300" count="285" warn></character-counter>

<!-- Only show count when 50 or fewer characters remain -->
<character-counter max="300" count="200" warn="50"></character-counter>

<!-- Never show count -->
<character-counter max="300" count="50" hide-count></character-counter>

Data Attributes

Sets data attributes based on state:

Attribute Description
data-over-limit Present when count exceeds max
data-hide-count Present when count text should be hidden (uses visibility: hidden to prevent layout shift)

CSS Custom Properties

Customize the appearance using CSS variables:

Property Default Description
--counter-diameter 2rem Circle diameter (supports any CSS unit)
--counter-stroke-width 3 Circle stroke width in pixels
--counter-track-color #e0e0e0 Background ring color
--counter-normal-color #1d9bf0 Progress color when under limit
--counter-warning-color #f4212e Progress color when over limit
--counter-text-color #536471 Remaining count text color

Change size globally

character-counter {
    --counter-diameter: 3rem;
    --counter-stroke-width: 5;
}

Change colors

character-counter {
    --counter-normal-color: #059669;
    --counter-warning-color: #dc2626;
    --counter-track-color: #f3f4f6;
}

Per-instance customization

<character-counter
    max="280"
    count="0"
    style="--counter-diameter: 32px; --counter-normal-color: purple"
>
</character-counter>

Behavior

  • Progress indicator: The circular ring fills as count approaches max
  • Number display: The remaining count appears to the left of the circle (controlled by hide-count and warn attributes)
  • Over-limit state: When count > max, the component turns red and shows a negative number (if count display is enabled)
  • Accessibility: Announces character count to screen readers via ARIA live regions

API

This exposes ESM and common JS via package.json exports field.

ESM

import '@substrate-system/character-counter'
// Named import
import { CharacterCounter } from '@substrate-system/character-counter'

Common JS

require('@substrate-system/character-counter')

TypeScript

The component includes TypeScript definitions and extends the global HTMLElementTagNameMap.

// Type-safe querySelector
const counter = document.querySelector('character-counter')
// counter is typed as CharacterCounter | null

// Set count via property
counter.count = 42

// Get computed values
console.log(counter.remaining)      // number
console.log(counter.isOverLimit)    // boolean
console.log(counter.hideCount)      // boolean
console.log(counter.warn)           // boolean | number
console.log(counter.shouldShowCount) // boolean

Pre-Built Files

This package exposes minified JS and CSS files. Copy them to a location accessible to your web server, then link to them in HTML.

Copy Files

cp ./node_modules/@substrate-system/character-counter/dist/index.min.js ./public/character-counter.min.js
cp ./node_modules/@substrate-system/character-counter/dist/style.min.css ./public/character-counter.css

Use in HTML

<head>
    <link rel="stylesheet" href="./character-counter.css">
</head>
<body>
    <character-counter max="300" count="0"></character-counter>

    <script type="module" src="./character-counter.min.js"></script>
</body>