react-textarea-enhanced
v1.3.2
Published
A React textarea component with Twitter-like features including character limits, entity highlighting, and auto-expansion
Maintainers
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-enhancedor
yarn add react-textarea-enhancedNote: 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
getDetailscallback 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
onChangecallback 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
- Wrapper:
- A
data-textarea-boxattribute is set on the wrapper for additional scoping in CSS. - Set
legacyClassNames={false}to drop generic classes like.textarea-wrapperand.highlight-layerif 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') andlegacyClassNames(default: true) to avoid stylesheet collisions while keeping backward compatibility. - Internal accessibility updated: dynamic IDs generated with React
useId;aria-labelledbyreferences wrapper and textarea IDs. - Old minimal types (Details with
charsLeftandtext, and basic TextAreaBoxProps) replaced by richertypes.d.ts.
- New optional props for IDs/classNames:
- 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 vialegacyClassNames={false}. - Deduplicated and consolidated CSS rules in
src/style/index.css; added CSS variables for font family/size and highlight color.
- Introduced scoped, prefixed classes (
- 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
valueandonChangeprops to support controlled component pattern - Component now supports both controlled and uncontrolled modes
- If
valueprop is provided, component becomes controlled and uses external state - If
valueprop is not provided, component manages its own internal state (backward compatible) onChangecallback receives the new string value directly:onChange?: (value: string) => void
- Added
- Improved State Management:
clearText()function inDetailsnow 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
valueprop is not provided
- TypeScript:
- Updated type definitions to include
valueandonChangeprops - Full type safety for both controlled and uncontrolled usage patterns
- Updated type definitions to include
