npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

react-textarea-enhanced

v1.3.2

Published

A React textarea component with Twitter-like features including character limits, entity highlighting, and auto-expansion

Readme

React Textarea Enhanced

A powerful React textarea component with Twitter-like features including character limits, entity highlighting, and auto-expansion.

Features

  • 🎯 Character Limit: Configurable character limits with visual feedback
  • 🔗 Entity Highlighting: Automatic detection and highlighting of hashtags, mentions, URLs, and cashtags
  • 📏 Auto-expansion: Textarea automatically grows with content
  • 🎨 Customizable: Flexible styling and configuration options
  • 🎛️ Controlled/Uncontrolled: Support for both controlled and uncontrolled component patterns
  • 📱 Responsive: Works great on mobile and desktop
  • 🌙 Dark Mode: Built-in dark mode support
  • TypeScript: Full TypeScript support with type definitions
  • 🚀 Lightweight: Minimal dependencies

Installation

npm install react-textarea-enhanced

or

yarn add react-textarea-enhanced

Note: This package is compiled to JavaScript and includes TypeScript declarations, so it works with both JavaScript and TypeScript projects.

Quick Start

Uncontrolled Mode (Default)

import React, { useState } from 'react';
import { TextAreaBox, Detail } from 'react-textarea-enhanced';
import 'react-textarea-enhanced/dist/index.css';

function App() {
  const [details, setDetails] = useState(new Detail());

  return (
    <div>
      <TextAreaBox 
        charLimit={280}
        getDetails={setDetails}
      />
      <div>Characters left: {details.charsLeft}</div>
    </div>
  );
}

Controlled Mode

import React, { useState } from 'react';
import { TextAreaBox, Detail } from 'react-textarea-enhanced';
import 'react-textarea-enhanced/dist/index.css';

function App() {
  const [text, setText] = useState('');
  const [details, setDetails] = useState(new Detail());

  return (
    <div>
      <TextAreaBox 
        value={text}
        onChange={setText}
        charLimit={280}
        getDetails={setDetails}
      />
      <div>Characters left: {details.charsLeft}</div>
    </div>
  );
}

Props

TextAreaBox

| Prop | Type | Default | Description | |------|------|---------|-------------| | value | string | - | Controlled value for the textarea. If provided, component becomes controlled. | | onChange | (value: string) => void | - | Callback fired when textarea value changes. Required if using controlled mode. | | charLimit | number | 10000 | Maximum number of characters allowed | | height | number | - | Fixed height of the textarea in pixels | | minHeight | number | 30 | Minimum height of the textarea in pixels | | maxHeight | number | 450 | Maximum height of the textarea in pixels | | fontFamily | string \| React.CSSProperties['fontFamily'] | 'Courier New, Courier, monospace' | Font family for the textarea | | baseUrl | string | "" | Base URL for generated entity links | | getDetails | (details: Details) => void | - | Callback to receive textarea details | | wrapperId | string | generated | Optional id for wrapper div | | textareaId | string | generated | Optional id for textarea element | | highlightId | string | generated | Optional id for highlight layer | | wrapperClassName | string | - | Extra class for wrapper (in addition to internal classes) | | textareaClassName | string | - | Extra class for textarea | | highlightClassName | string | - | Extra class for highlight layer | | classNamePrefix | string | txb | Prefix for internal classes to avoid collisions | | legacyClassNames | boolean | false | Include legacy generic classes for backward compatibility | | highlightColor | string | '#1da1f2' | Color for entity highlights |

Details Interface

interface Details {
  charsLeft: number;  // Characters remaining
  text: string;      // Current text content
  highlightedText: React.ReactNode;
  /**
   * The extracted tags from the text
   */
  tags: {
    cash: string[],
    hash: string[],
    mention: string[],
  };
  /**
   * Urls in the text
   */
  urls: string[]
}

Controlled vs Uncontrolled Mode

The TextAreaBox component supports both controlled and uncontrolled patterns, similar to React's native input elements.

Uncontrolled Mode (Default)

When you don't provide the value prop, the component manages its own internal state:

<TextAreaBox 
  charLimit={280}
  getDetails={setDetails}
/>
  • Component manages its own state internally
  • Use getDetails callback to access the current value and details
  • Simpler for basic use cases
  • Good for forms where you only need the value on submit

Controlled Mode

When you provide the value prop, the component becomes controlled:

const [text, setText] = useState('');

<TextAreaBox 
  value={text}
  onChange={setText}
  charLimit={280}
  getDetails={setDetails}
/>
  • You manage the state externally
  • onChange callback is called with the new value whenever it changes
  • More control over the component's value
  • Better for complex state management, validation, or when you need to programmatically set the value
  • Required if you want to clear the textarea programmatically or sync with other components

Note: You can still use getDetails in controlled mode to access entity information (hashtags, mentions, URLs, etc.) and other details.

Examples

Basic Usage

import React, { useState } from 'react';
import { TextAreaBox, Detail } from 'react-textarea-enhanced';
import 'react-textarea-enhanced/dist/index.css';

function TweetComposer() {
  const [details, setDetails] = useState(new Detail());

  const handleSubmit = () => {
    if (details.text.length > 0 && details.charsLeft >= 0) {
      console.log('Posting:', details.text);
      // Handle post submission
    }
  };

  return (
    <div className="compose-box">
      <TextAreaBox 
        charLimit={280}
        getDetails={setDetails}
      />
      <button 
        onClick={handleSubmit}
        disabled={details.text.length === 0 || details.charsLeft < 0}
        className="tweet-button"
      >
        Tweet
      </button>
      <div className={`char-counter ${details.charsLeft <= 20 ? 'warning' : ''} ${details.charsLeft <= 0 ? 'error' : ''}`}>
        {details.charsLeft}
      </div>
    </div>
  );
}

Custom Styling

import React from 'react';
import { TextAreaBox } from 'react-textarea-enhanced';
import 'react-textarea-enhanced/dist/index.css';

function CustomTextarea() {
  return (
    <div style={{ maxWidth: '600px', margin: '0 auto' }}>
      <TextAreaBox 
        charLimit={500}
        height={300}
        minHeight={100}
        getDetails={(details) => console.log(details)}
      />
    </div>
  );
}

Controlled Mode

import React, { useState } from 'react';
import { TextAreaBox, Detail } from 'react-textarea-enhanced';
import 'react-textarea-enhanced/dist/index.css';

function ControlledTextarea() {
  const [text, setText] = useState('');
  const [details, setDetails] = useState(new Detail());

  const handleSubmit = () => {
    if (text.length > 0 && details.charsLeft >= 0) {
      console.log('Posting:', text);
      // Handle submission
      setText(''); // Clear after submission
    }
  };

  return (
    <div className="compose-box">
      <TextAreaBox 
        value={text}
        onChange={setText}
        charLimit={280}
        getDetails={setDetails}
      />
      <button 
        onClick={handleSubmit}
        disabled={text.length === 0 || details.charsLeft < 0}
        className="tweet-button"
      >
        Tweet
      </button>
      <div className={`char-counter ${details.charsLeft <= 20 ? 'warning' : ''} ${details.charsLeft <= 0 ? 'error' : ''}`}>
        {details.charsLeft}
      </div>
    </div>
  );
}

With Form Integration

import React, { useState } from 'react';
import { TextAreaBox, Detail } from 'react-textarea-enhanced';
import 'react-textarea-enhanced/dist/index.css';

function ContactForm() {
  const [text, setText] = useState('');
  const [details, setDetails] = useState(new Detail());
  const [name, setName] = useState('');

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (text.length > 0) {
      console.log('Form submitted:', { name, message: text });
      // Reset form
      setText('');
      setName('');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input 
        type="text" 
        value={name}
        onChange={(e) => setName(e.target.value)}
        placeholder="Your name"
        required
      />
      <TextAreaBox 
        value={text}
        onChange={setText}
        charLimit={1000}
        getDetails={setDetails}
      />
      <button type="submit" disabled={text.length === 0}>
        Send Message
      </button>
    </form>
  );
}

Styling

The component comes with built-in CSS that you can import:

import 'react-textarea-enhanced/dist/index.css';

The stylesheet defines CSS variables you can override at the app level:

:root {
  --font-size: 15px;
  --font-family: 'Courier New', Courier, monospace;
  --highlight-color: #1da1f2;
}

Scoped class names and collision-avoidance

  • Internal elements use prefixed classes (default prefix txb):
    • Wrapper: txb-wrapper
    • Textarea: txb-textarea
    • Highlight layer: txb-highlight
  • A data-textarea-box attribute is set on the wrapper for additional scoping in CSS.
  • Set legacyClassNames={false} to drop generic classes like .textarea-wrapper and .highlight-layer if you want to fully isolate styles from client stylesheets.

Custom CSS Classes

The component uses the following CSS classes that you can customize:

  • .compose-box - Main container
  • .textarea-wrapper (legacy, optional) - Textarea wrapper
  • .highlight-layer (legacy, optional) - Entity highlighting layer
  • .txb-wrapper - Prefixed wrapper (scoped)
  • .txb-textarea - Prefixed textarea (scoped)
  • .txb-highlight - Prefixed highlight layer (scoped)
  • .highlight.hashtag - Hashtag styling
  • .highlight.mention - Mention styling
  • .highlight.url - URL styling
  • .highlight.cashtag - Cashtag styling
  • .char-counter - Character counter
  • .char-counter.warning - Warning state
  • .char-counter.error - Error state
  • .limit-content - Content beyond limit

Keyboard Shortcuts

  • Ctrl + Enter: Submit the textarea content (triggers console.log by default)

Dependencies

  • React 16.8+ (hooks support)
  • twitter-text: For robust entity parsing

Browser Support

  • Chrome 60+
  • Firefox 60+
  • Safari 12+
  • Edge 79+

TypeScript

Full TypeScript support is included with comprehensive type definitions:

import { TextAreaBox, TextAreaBoxProps, Details } from 'react-textarea-enhanced';

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT © Ojutalayo Ayomide Josiah

Changelog

1.0.0

  • Initial release
  • TextAreaBox component with character limits
  • Entity highlighting (hashtags, mentions, URLs, cashtags)
  • Auto-expansion functionality
  • TypeScript support
  • Dark mode support

v1.2.0

  • Updated type definitions (types.d.ts):
    • New optional props for IDs/classNames: wrapperId, textareaId, highlightId, wrapperClassName, textareaClassName, highlightClassName.
    • Added classNamePrefix (default: 'txb') and legacyClassNames (default: true) to avoid stylesheet collisions while keeping backward compatibility.
    • Internal accessibility updated: dynamic IDs generated with React useId; aria-labelledby references wrapper and textarea IDs.
    • Old minimal types (Details with charsLeft and text, and basic TextAreaBoxProps) replaced by richer types.d.ts.
  • Styling changes:
    • Introduced scoped, prefixed classes (txb-wrapper, txb-textarea, txb-highlight) under [data-textarea-box] to prevent client stylesheet interference.
    • Kept legacy classes (textarea-wrapper, highlight-layer) enabled by default; can be disabled via legacyClassNames={false}.
    • Deduplicated and consolidated CSS rules in src/style/index.css; added CSS variables for font family/size and highlight color.
  • Entity highlighting:
    • Entity anchor tags now also include the prefixed highlight class for easier scoping.
  • No breaking changes expected; legacy classes remain by default. Set legacyClassNames={false} to fully isolate styles.

v1.3.1

  • Controlled Component Support:
    • Added value and onChange props to support controlled component pattern
    • Component now supports both controlled and uncontrolled modes
    • If value prop is provided, component becomes controlled and uses external state
    • If value prop is not provided, component manages its own internal state (backward compatible)
    • onChange callback receives the new string value directly: onChange?: (value: string) => void
  • Improved State Management:
    • clearText() function in Details now works correctly in both controlled and uncontrolled modes
    • Character limit enforcement works seamlessly in both modes
    • All existing features (entity highlighting, auto-expansion, etc.) work in both modes
  • Backward Compatibility:
    • No breaking changes - existing code continues to work without modifications
    • Uncontrolled mode remains the default when value prop is not provided
  • TypeScript:
    • Updated type definitions to include value and onChange props
    • Full type safety for both controlled and uncontrolled usage patterns