ContextInput

A rich text input component with context-aware suggestions, mentions, and inline editing capabilities

Installation

Terminal
pnpm add @choice-ui/context-input

Import

Component.tsx
import { ContextInput, InsertMentionsButton, CopyButton, useContextInput, useMentions, useContextInputEditor, shouldInsertSpaceBefore, shouldInsertSpaceAfter, insertSpaceBeforeIfNeeded, insertSpaceAfterIfNeeded, insertWithSmartSpacing, convertSlateToText, convertTextToSlate, convertTextToSlateWithResolver, extractTextWithMentions, parseTextWithMentions, extractMentionContext, type ContextInputProps, type ContextInputValue, type ContextMentionElement, type ContextInputRef, type ContextMentionProps, type ContextMentionItemProps, type ContextMentionTrigger } from "@choice-ui/context-input"

Basic

Basic: Simple context input with user mentions using @ trigger.
- Type @ to search and mention users - Arrow keys navigation and Enter/Tab to select - Returns structured data with text and mention metadata - Real-time search filtering
```tsx const [value, setValue] = useState({ text: "", mentions: [] }) <ContextInput value={value} placeholder="Type @ to mention someone..." triggers={[{ char: "@", onSearch: async (query) => { return users.filter(user => user.label.toLowerCase().includes(query.toLowerCase()) ) } }]} onChange={setValue} onMentionSelect={(mention, trigger) => { console.log("Selected:", mention, trigger) }} /> ```

{
  "text": "",
  "mentions": []
}

Disabled

Disabled: Shows context input in disabled state.
- All interactions are prevented - Visual styling indicates disabled state - Useful for read-only scenarios or conditional editing
```tsx <ContextInput disabled value={value} placeholder="This input is disabled..." triggers={triggers} onChange={setValue} /> ```
Hello @alice and @bob!
{
  "text": "Hello @alice and @bob!",
  "mentions": [
    {
      "item": {
        "id": "1",
        "label": "alice",
        "type": "user"
      },
      "startIndex": 6,
      "endIndex": 12,
      "text": "alice"
    },
    {
      "item": {
        "id": "2",
        "label": "bob",
        "type": "user"
      },
      "startIndex": 17,
      "endIndex": 21,
      "text": "bob"
    }
  ]
}

Read Only

ReadOnly: Demonstrates the ContextInput component in readOnly mode.
- Prevents value changes while allowing focus and selection - Maintains normal visual appearance (unlike disabled) - Useful for displaying non-editable context input information
Hello @alice and @bob!
{
  "text": "Hello @alice and @bob!",
  "mentions": [
    {
      "item": {
        "id": "1",
        "label": "alice",
        "type": "user"
      },
      "startIndex": 6,
      "endIndex": 12,
      "text": "alice"
    },
    {
      "item": {
        "id": "2",
        "label": "bob",
        "type": "user"
      },
      "startIndex": 17,
      "endIndex": 21,
      "text": "bob"
    }
  ]
}

Variants

Variants: Demonstrates different visual variants of the context input component.
- default: Follows the page theme dynamically (light/dark mode) - light: Fixed light appearance regardless of theme - dark: Fixed dark appearance regardless of theme - reset: Removes variant styling, no Variant settings applied
```tsx <ContextInput variant="default" // Adapts to current theme value={value} placeholder="Type @ to mention..." triggers={triggers} onChange={setValue} /> ```




Large Size

Large: Context input with large size variant.
- Increased padding and font size - Better for prominent input areas - More comfortable for extended typing
```tsx <ContextInput size="large" value={value} placeholder="Large size input..." triggers={triggers} onChange={setValue} /> ```

{
  "text": "",
  "mentions": []
}

Min Height

MinHeight: Context input with custom minimum height.
- Set minHeight prop to control initial height - Useful for ensuring consistent layout - Input will grow beyond minHeight if needed
```tsx <ContextInput minHeight={128} size="large" value={value} placeholder="Min height 128px..." triggers={triggers} onChange={setValue} /> ```

{
  "text": "",
  "mentions": []
}

With Header

WithHeader: Context input with custom header section.
- Header section automatically inherits size prop - Useful for titles, actions, or additional controls - Header content can include buttons and text
```tsx <ContextInput value={value} onChange={setValue}> <ContextInput.Header> <h3>Header Title</h3> <IconButton> <ExpandSmall /> </IconButton> </ContextInput.Header> </ContextInput> ```

Header

Hello world, this is a sample message with header.
{
  "text": "Hello world, this is a sample message with header.",
  "mentions": []
}
Hello world, this is a sample message with header.

Max Length

MaxLength: Context input with character limit enforcement.
- Set maxLength prop to limit input characters - Prevents typing/pasting beyond the limit - Character count display shows current/max length - Visual indicator when approaching limit
```tsx <ContextInput maxLength={100} value={value} onChange={setValue} > <ContextInput.Footer> <span className={value.text.length === 100 ? "text-red-500" : ""}> {value.text.length}/100 </span> </ContextInput.Footer> </ContextInput> ```

0/100

Max Suggestions

MaxSuggestions: Context input with limited suggestion count.
- Set maxSuggestions prop to limit displayed options - Useful for keeping suggestion lists manageable - Search results are truncated to the specified limit
```tsx <ContextInput maxSuggestions={3} value={value} placeholder="Max 3 suggestions" triggers={triggers} onChange={setValue} /> ```


With Paste Button

WithPasteButton: Context input with copy button functionality.
- Copy button extracts text including mention placeholders - Demonstrates format conversion for external use - Integration with AlertDialog for confirmation - Shows mention data transformation
```tsx <ContextInput value={value} onChange={setValue}> <ContextInput.Header> <h3>Input content</h3> <ContextInput.CopyButton onClick={(copiedText) => { console.log("Copied:", copiedText) }} /> </ContextInput.Header> </ContextInput> ```

Input content

Lorem @John Doeolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.

Multiple Triggers

MultipleTriggers: Context input with multiple mention triggers.
- Use @ to mention users and / to mention channels - Different search functions for each trigger type - Demonstrates versatile mention system - Use arrow keys to navigate suggestions - Press Enter or Tab to select
```tsx <ContextInput value={value} placeholder="Try @ for users, / for channels..." triggers={[ { char: "@", onSearch: async (query) => searchUsers(query) }, { char: "/", onSearch: async (query) => searchChannels(query) } ]} onChange={setValue} /> ```


Custom Mention Prefix

CustomMentionPrefix: Demonstrates customizable mention prefix.
- Use # instead of @ for mentions display - Configurable via mentionPrefix prop - Supports any string as prefix (e.g., "+", "~", "#", etc.) - Useful for different contexts like hashtags or custom notations - Mentions will appear as `#JohnDoe` instead of `@JohnDoe`
```tsx <ContextInput mentionPrefix="#" placeholder="Type @ to mention with # prefix..." triggers={[ { char: "@", onSearch: async (query) => searchUsers(query) } ]} /> ```

{
  "text": "",
  "mentions": []
}

Clear Function Test

ClearFunctionTest: Test the clear function of the context input.
- Click the clear button to clear the input - The input should be cleared and the value should be an empty object - Demonstrates programmatic clearing of content and mentions
```tsx const handleClear = () => { setValue({ text: "", mentions: [] }) } ```


Controlled Value

ControlledValue: Context input with external value control.
- Demonstrates controlled value state management - External buttons can modify input content and mentions - Shows programmatic content insertion and clearing - Auto-focuses editor when value changes externally (when autoFocus is enabled) - Cursor always moves to end position after value changes - Useful for form integration and external state management - Real-time character and mention counts - Preset content examples for quick testing - Full bidirectional data binding
```tsx const [value, setValue] = useState<ContextInputValue>({ text: "", mentions: [] }) <ContextInput value={value} autoFocus onChange={setValue} triggers={triggers} /> <Button onClick={() => setValue({ text: "Hello world!", mentions: [] })}> Set Content </Button> ```

Value Control Buttons

Dynamic Actions


Characters: 0Mentions: 0
{
  "text": "",
  "mentions": []
}

Custom Mention Component

CustomMentionComponent: Example showing how to use customMentionComponent for customization.
- Create custom Mention component with different styling - Custom component has gradient background and emoji decoration - Passed via `customMentionComponent` prop - Receives same props as default Mention component - Useful for branding or visual differentiation
```tsx const CustomMention = (props) => { const { attributes, children, element, mentionPrefix } = props return ( <span {...attributes} contentEditable={false} className="custom-styles"> {children} {mentionPrefix}{element.mentionLabel} </span> ) } <ContextInput customMentionComponent={CustomMention} /> ```


Imperative Focus

ImperativeFocus: Demonstrates how to programmatically focus the input using ref.
- Use ref to get access to the focus method - Call ref.current.focus() to focus the input from outside - Useful for keyboard shortcuts or external button triggers
```tsx const inputRef = useRef<ContextInputRef>(null) <ContextInput ref={inputRef} /> <Button onClick={() => inputRef.current?.focus()}> Focus Input </Button> ```


API reference

ContextInputHeaderTypeDefault
className
string
|
undefined
-
handleClick
(() => void)
|
undefined
-
size
undefined
|
"default"
|
"large"
-
ref
((instance: HTMLDivElement
|
null) => void)
|
RefObject<HTMLDivElement>
|
null
|
undefined
-
ContextInputFooterTypeDefault
className
string
|
undefined
-
handleClick
(() => void)
|
undefined
-
size
undefined
|
"default"
|
"large"
-
ref
((instance: HTMLDivElement
|
null) => void)
|
RefObject<HTMLDivElement>
|
null
|
undefined
-
CopyButtonTypeDefault
className
string
|
undefined
-
size
undefined
|
"default"
|
"reset"
|
"large"
-
disabled
boolean
|
undefined
false
onClick
((copiedText: string) => void)
|
undefined
-
as
ElementType<any, keyof IntrinsicElements>
|
undefined
-
readOnly
boolean
|
undefined
-
active
boolean
|
undefined
-
asChild
boolean
|
undefined
-
focused
boolean
|
undefined
-
loading
boolean
|
undefined
-
tooltip
TooltipProps
|
undefined
-
variant
undefined
|
"default"
|
"dark"
|
"reset"
|
"secondary"
|
"solid"
|
"highlight"
|
"ghost"
|
"submit"
-
successDuration
number
|
undefined
2000
ref
((instance: HTMLElement
|
null) => void)
|
RefObject<HTMLElement>
|
null
|
undefined
-
MentionMenuTypeDefault
disabled
boolean
|
undefined
-
onSelect
(mention: ContextMentionItemProps, index: number) => void -
loading
boolean -
isOpen
boolean -
onClose
() => void -
position
MentionMenuPosition
|
null
-
renderSuggestion
((item: ContextMentionItemProps, isSelected: boolean) => ReactNode)
|
undefined
-
root
HTMLElement
|
null
|
undefined
-
suggestions
ContextMentionItemProps[] -
ref
((instance: MentionMenuRef
|
null) => void)
|
RefObject<MentionMenuRef>
|
null
|
undefined
-
SlateEditorTypeDefault
className
string
|
undefined
-
size
"default"
|
"large"
-
disabled
boolean
|
undefined
false
maxLength
number
|
undefined
-
placeholder
string
|
undefined
Type someone...
readOnly
boolean
|
undefined
false
autoFocus
boolean
|
undefined
false
onCompositionEnd
((event: CompositionEvent<Element>) => void)
|
undefined
-
onCompositionStart
((event: CompositionEvent<Element>) => void)
|
undefined
-
onFocus
(() => void)
|
undefined
-
onBlur
(() => void)
|
undefined
-
onChange
(value: Descendant[]) => void -
onKeyDown
(event: KeyboardEvent<Element>) => void -
variant
undefined
|
"default"
|
"light"
|
"dark"
|
"reset"
default
renderMention
((mention: MentionMatch) => ReactNode)
|
undefined
-
mentionPrefix
string
|
undefined
-
customMentionComponent
ComponentType<ContextMentionProps>
|
undefined
-
editor
ReactEditor -
footer
ReactNode -
hasFooter
boolean -
hasHeader
boolean -
minHeight
number
80
slateValue
Descendant[] -
ref
((instance: HTMLDivElement
|
null) => void)
|
RefObject<HTMLDivElement>
|
null
|
undefined
-
ContextMentionElementTypeDefault
mentionPrefix
string
|
undefined
-
renderMention
((mention: MentionMatch) => ReactNode)
|
undefined
-
variant
undefined
|
"default"
|
"light"
|
"dark"
|
"reset"
-
useMentionsTypeDefault
editor
ContextEditor -
maxSuggestions
number
|
undefined
10
mentionPrefix
string
|
undefined
@
onMentionSelect
((mention: ContextMentionItemProps, trigger: string) => void)
|
undefined
-
onSearchClose
(() => void)
|
undefined
-
triggers
ContextMentionTrigger[] -
useContextInputTypeDefault
autoFocus
boolean
|
undefined
-
editor
ContextEditor
|
undefined
-
onChange
((value: ContextInputValue) => void)
|
undefined
-
value
ContextInputValue
|
undefined
-