emoteer

EmoteInput

Inputs and textareas that auto-convert :shortcode: to unicode

EmoteInput and EmoteTextArea are drop-in replacements for native form controls. The moment the user closes a shortcode — typing the second : in :star: — it is converted to ⭐ in place.

Controlled or uncontrolled

Both components behave exactly like native <input> / <textarea>:

  • Pass value + onChange for controlled mode — works with form libraries (react-hook-form, formik), programmatic resets, draft persistence, and server-synced state.
  • Pass defaultValue (or nothing) for uncontrolled mode — the component holds its own state.

In both modes, onChange(e) receives the already-converted text via e.target.value. The cursor position is preserved across conversions using a useLayoutEffect that restores the selection after React reconciles.

EmoteInput

Single-line input. Controlled example:

components/single-input.tsx
import { EmoteInput } from '@emoteer/react';
import { useState } from 'react';

export function Single() {
  const [value, setValue] = useState('');
  return (
    <EmoteInput
      value={value}
      onChange={(e) => setValue(e.target.value)}
      placeholder="Try typing :star:"
    />
  );
}

Uncontrolled variant:

<EmoteInput
  defaultValue=""
  onChange={(e) => console.log(e.target.value)}
  placeholder="Try typing :star:"
/>

EmoteTextArea

Multi-line textarea with the same conversion behavior and the same controlled / uncontrolled options.

components/multi-input.tsx
import { EmoteTextArea } from '@emoteer/react';
import { useState } from 'react';

export function Multi() {
  const [value, setValue] = useState('');
  return (
    <EmoteTextArea
      value={value}
      onChange={(e) => setValue(e.target.value)}
      placeholder="Try typing :grinning_face:"
    />
  );
}

Integrating with form libraries

Because both components expose the standard controlled API, they work out of the box with any form library. Example with react-hook-form:

components/form.tsx
import { EmoteInput } from '@emoteer/react';
import { Controller, useForm } from 'react-hook-form';

export function Form() {
  const { control, handleSubmit } = useForm({ defaultValues: { message: '' } });
  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <Controller
        control={control}
        name="message"
        render={({ field }) => <EmoteInput {...field} />}
      />
      <button type="submit">Send</button>
    </form>
  );
}

Props

Both components forward all native input / textarea attributes plus a ref.

EmoteInput

PropTypeDescription
valuestringControlled value. Pair with onChange.
defaultValuestringInitial value in uncontrolled mode.
onChange(e: ChangeEvent<HTMLInputElement>) => voidFires after every keystroke and after every conversion. e.target.value is the converted text.
refRef<HTMLInputElement>Forwarded to the <input> element.
classNamestringForwarded to the <input> element.
...native—All standard <input> attributes (placeholder, disabled, readOnly, maxLength, etc).

EmoteTextArea

PropTypeDescription
valuestringControlled value. Pair with onChange.
defaultValuestringInitial value in uncontrolled mode.
onChange(e: ChangeEvent<HTMLTextAreaElement>) => voidFires after every keystroke and after every conversion.
refRef<HTMLTextAreaElement>Forwarded to the <textarea> element.
classNamestringForwarded to the <textarea> element.
...native—All standard <textarea> attributes.

Notes

  • Conversion fires the moment a shortcode is closed — i.e. as soon as the second : in :name: is typed. No trailing space required.
  • Unmatched shortcodes (no emoji found for the name) stay as plain text.
  • Cursor position is preserved in both modes.
  • Requires an ancestor EmoteProvider — the components read the shortcode index from context.

On this page