Emoji Picker Rails Component
Add emoji selection functionality to forms and inputs with a beautiful, accessible emoji picker. Perfect for reactions, comments, and social features.
Installation
1. Stimulus Controller Setup
Start by adding the following controller to your project:
import { Controller } from "@hotwired/stimulus";
import "emoji-mart"; // Use this if you installed emoji-mart with importmap
// import { Picker } from "emoji-mart"; // Use this if you installed emoji-mart with npm or yarn
import data from "@emoji-mart/data";
export default class extends Controller {
static targets = ["input", "pickerContainer", "button"]; // Targets for the input field, picker container, and button
static values = {
autoSubmit: { type: Boolean, default: false }, // Whether to automatically submit the form when an emoji is selected
insertMode: { type: Boolean, default: false }, // Whether to insert emoji at cursor position instead of replacing entire value
targetSelector: { type: String, default: "" }, // CSS selector for a specific input/textarea to insert emoji into
};
currentTheme = null; // Store the current theme
lastCursorPosition = { start: 0, end: 0 }; // Store cursor position
lastFocusedField = null; // Store the last focused field
connect() {
// Prevent duplicate initialization
if (this.element.dataset.emojiPickerInitialized === "true") {
return;
}
// Mark as initialized
this.element.dataset.emojiPickerInitialized = "true";
// Use setTimeout to ensure DOM is fully rendered after Turbo navigation
setTimeout(() => {
this.initializePicker();
this.setupEventListeners();
this.findForm();
this.setupThemeObserver();
}, 0);
}
findForm() {
// Find the closest form element
this.form = this.element.closest("form") || this.element.querySelector("form");
}
setupEventListeners() {
// Prevent duplicate event listeners
if (this.eventListenersSetup) {
return;
}
this.eventListenersSetup = true;
// Add document click event listener to handle outside clicks
this.outsideClickHandler = this.handleOutsideClick.bind(this);
document.addEventListener("click", this.outsideClickHandler);
// Add keyboard event listener for escape key and navigation
this.keydownHandler = this.handleKeydown.bind(this);
document.addEventListener("keydown", this.keydownHandler);
// Add keydown listener to button for backspace/delete functionality
if (this.hasButtonTarget) {
this.buttonKeydownHandler = this.handleButtonKeydown.bind(this);
this.buttonTarget.addEventListener("keydown", this.buttonKeydownHandler);
// Make button focusable
this.buttonTarget.setAttribute("tabindex", "0");
// Add tooltip hint for backspace functionality only when not in insert mode
if (!this.insertModeValue) {
this.buttonTarget.setAttribute("title", "Press Backspace or Delete to remove emoji");
}
}
// Track focus on target fields
this.setupFieldTracking();
}
setupFieldTracking() {
const targetField = this.getTargetField();
if (targetField) {
// Track focus events
this.focusHandler = () => {
this.lastFocusedField = targetField;
};
// Track selection changes
this.selectionHandler = () => {
if (targetField === document.activeElement) {
this.lastCursorPosition = {
start: targetField.selectionStart,
end: targetField.selectionEnd,
};
}
};
targetField.addEventListener("focus", this.focusHandler);
targetField.addEventListener("click", this.selectionHandler);
targetField.addEventListener("keyup", this.selectionHandler);
targetField.addEventListener("select", this.selectionHandler);
}
}
handleButtonKeydown(event) {
// Delete emoji if backspace or delete is pressed (only when not in insert mode)
if ((event.key === "Backspace" || event.key === "Delete") && !this.insertModeValue) {
event.preventDefault();
this.clearEmoji();
}
}
clearEmoji() {
if (this.hasInputTarget) {
// Clear the input value
this.inputTarget.value = "";
// Update button HTML to show default icon
if (this.hasButtonTarget) {
this.updateButtonToDefault();
}
// Submit the form to save the change only if auto-submit is enabled
if (this.autoSubmitValue && this.form) {
this.form.requestSubmit();
}
}
}
updateButtonToDefault() {
// Default emoji face icon
const iconHtml = `<svg xmlns="http://www.w3.org/2000/svg" class="size-5" width="18" height="18" viewBox="0 0 18 18"><g fill="currentColor"><path d="M9,1C4.589,1,1,4.589,1,9s3.589,8,8,8,8-3.589,8-8S13.411,1,9,1Zm-4,7c0-.552,.448-1,1-1s1,.448,1,1-.448,1-1,1-1-.448-1-1Zm4,6c-1.531,0-2.859-1.14-3.089-2.651-.034-.221,.039-.444,.193-.598,.151-.15,.358-.217,.572-.185,1.526,.24,3.106,.24,4.638,.001h0c.217-.032,.428,.036,.583,.189,.153,.153,.225,.373,.192,.589-.229,1.513-1.557,2.654-3.089,2.654Zm3-5c-.552,0-1-.448-1-1s.448-1,1-1,1,.448,1,1-.448,1-1,1Z"></path></g></svg>`;
this.buttonTarget.innerHTML = iconHtml;
}
initializePicker() {
try {
// Check if we have the pickerContainer target
if (!this.hasPickerContainerTarget) {
return;
}
// Prevent duplicate picker initialization
if (this.picker) {
return;
}
// Detect the current theme from the document
const isDarkMode = document.documentElement.classList.contains("dark");
this.currentTheme = isDarkMode ? "dark" : "light";
// Use imported emoji data instead of CDN to avoid network issues
this.picker = new EmojiMart.Picker({
data: data,
onEmojiSelect: this.onEmojiSelect.bind(this),
emojiButtonColors: "#FF9E66",
emojiVersion: 15,
previewPosition: "none",
dynamicWidth: true,
theme: this.currentTheme,
});
this.pickerContainerTarget.innerHTML = "";
this.pickerContainerTarget.appendChild(this.picker);
} catch (error) {
console.error("Error initializing emoji picker:", error);
}
}
toggle(event) {
event.preventDefault();
event.stopPropagation(); // Prevent this click from being caught by the document listener
try {
// Check if we have the pickerContainer target
if (!this.hasPickerContainerTarget) {
return;
}
// Store cursor position before opening picker (for insert mode)
if (this.insertModeValue) {
const targetField = this.getTargetField();
if (targetField) {
// If the field was never focused, don't update cursor position
if (targetField === document.activeElement || this.lastFocusedField === targetField) {
this.lastFocusedField = targetField;
this.lastCursorPosition = {
start: targetField.selectionStart || 0,
end: targetField.selectionEnd || 0,
};
}
}
}
this.pickerContainerTarget.classList.toggle("hidden");
} catch (error) {
console.error("Error toggling emoji picker:", error);
}
}
handleOutsideClick(event) {
// If picker is visible and click is outside picker and outside the toggle button
if (
this.hasPickerContainerTarget &&
!this.pickerContainerTarget.classList.contains("hidden") &&
!this.pickerContainerTarget.contains(event.target) &&
(!this.hasButtonTarget || !this.buttonTarget.contains(event.target))
) {
this.pickerContainerTarget.classList.add("hidden");
}
}
handleKeydown(event) {
// Close picker when Escape key is pressed
if (
event.key === "Escape" &&
this.hasPickerContainerTarget &&
!this.pickerContainerTarget.classList.contains("hidden")
) {
this.pickerContainerTarget.classList.add("hidden");
return;
}
// Focus on search input when picker is open and navigation/typing occurs
if (
this.hasPickerContainerTarget &&
!this.pickerContainerTarget.classList.contains("hidden") &&
this.shouldFocusSearch(event)
) {
this.focusSearchInput();
}
}
shouldFocusSearch(event) {
// Check for arrow keys
if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(event.key)) {
return true;
}
// Check for typing (alphanumeric characters, space, etc.)
if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
return true;
}
return false;
}
focusSearchInput() {
try {
// The emoji picker uses shadow DOM, so we need to access the search input through it
if (this.picker && this.picker.shadowRoot) {
const searchInput = this.picker.shadowRoot.querySelector('input[type="search"]');
if (searchInput) {
searchInput.focus();
}
}
} catch (error) {
console.error("Error focusing search input:", error);
}
}
onEmojiSelect(emoji) {
try {
const targetField = this.getTargetField();
if (!targetField && !this.hasInputTarget) {
return;
}
// Insert or replace based on mode
if (this.insertModeValue && targetField) {
// Insert emoji at cursor position
this.insertAtCursor(targetField, emoji.native);
} else if (this.hasInputTarget) {
// Replace entire value (original behavior)
this.inputTarget.value = emoji.native;
}
// Update the button with the selected emoji
if (this.hasButtonTarget && !this.insertModeValue) {
this.buttonTarget.innerHTML = `<span class="size-6 text-xl shrink-0 flex items-center justify-center">${emoji.native}</span>`;
}
if (this.hasPickerContainerTarget) {
this.pickerContainerTarget.classList.add("hidden");
}
// Submit the form if it exists and auto-submit is enabled
if (this.autoSubmitValue && this.form) {
this.form.requestSubmit();
}
} catch (error) {
console.error("Error selecting emoji:", error);
}
}
disconnect() {
// Clean up initialization flag
if (this.element) {
delete this.element.dataset.emojiPickerInitialized;
}
// Clean up event listeners when controller disconnects
if (this.outsideClickHandler) {
document.removeEventListener("click", this.outsideClickHandler);
this.outsideClickHandler = null;
}
if (this.keydownHandler) {
document.removeEventListener("keydown", this.keydownHandler);
this.keydownHandler = null;
}
// Remove button keydown listener if it exists
if (this.hasButtonTarget && this.buttonKeydownHandler) {
this.buttonTarget.removeEventListener("keydown", this.buttonKeydownHandler);
this.buttonKeydownHandler = null;
}
// Clean up field tracking listeners
const targetField = this.getTargetField();
if (targetField) {
if (this.focusHandler) {
targetField.removeEventListener("focus", this.focusHandler);
}
if (this.selectionHandler) {
targetField.removeEventListener("click", this.selectionHandler);
targetField.removeEventListener("keyup", this.selectionHandler);
targetField.removeEventListener("select", this.selectionHandler);
}
}
// Clean up picker
if (this.picker && this.hasPickerContainerTarget && this.pickerContainerTarget.contains(this.picker)) {
this.pickerContainerTarget.removeChild(this.picker);
this.picker = null;
}
// Reset flags
this.eventListenersSetup = false;
this.currentTheme = null;
this.lastCursorPosition = { start: 0, end: 0 };
this.lastFocusedField = null;
// Clean up mutation observer
if (this.themeObserver) {
this.themeObserver.disconnect();
}
}
setupThemeObserver() {
// Create a mutation observer to watch for theme changes
this.themeObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === "attributes" && mutation.attributeName === "class") {
const newTheme = document.documentElement.classList.contains("dark") ? "dark" : "light";
// If theme changed, re-initialize the picker
if (newTheme !== this.currentTheme) {
this.reinitializePicker();
}
}
});
});
// Start observing the document element for class changes
this.themeObserver.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
});
}
reinitializePicker() {
// Clean up existing picker
if (this.picker) {
this.picker = null;
if (this.hasPickerContainerTarget) {
this.pickerContainerTarget.innerHTML = "";
}
}
// Re-initialize with new theme
this.initializePicker();
}
// Get the actual input/textarea element to insert emoji into
getTargetField() {
// If a custom selector is provided, use that
if (this.targetSelectorValue) {
const customTarget = document.querySelector(this.targetSelectorValue);
if (customTarget && (customTarget.tagName === "INPUT" || customTarget.tagName === "TEXTAREA")) {
return customTarget;
}
}
// Otherwise use the input target
return this.hasInputTarget ? this.inputTarget : null;
}
// Insert text at cursor position in input/textarea
insertAtCursor(field, text) {
if (!field) return;
// Save current scroll position
const scrollPos = field.scrollTop;
// If field was never focused/selected, append at the end
let startPos, endPos;
if (this.lastFocusedField !== field && this.lastCursorPosition.start === 0 && this.lastCursorPosition.end === 0) {
// Field was never selected, append at end
startPos = field.value.length;
endPos = field.value.length;
} else {
// Use stored cursor position if available, otherwise use current
startPos = this.lastCursorPosition.start || field.selectionStart || 0;
endPos = this.lastCursorPosition.end || field.selectionEnd || 0;
}
// Insert the text
const beforeText = field.value.substring(0, startPos);
const afterText = field.value.substring(endPos);
field.value = beforeText + text + afterText;
// Set cursor position after the inserted text
const newCursorPos = startPos + text.length;
field.setSelectionRange(newCursorPos, newCursorPos);
// Update stored position
this.lastCursorPosition = {
start: newCursorPos,
end: newCursorPos,
};
// Restore scroll position
field.scrollTop = scrollPos;
// Focus the field
field.focus();
// Trigger input event for any listeners (like autogrow)
field.dispatchEvent(new Event("input", { bubbles: true }));
}
}
2. Emoji Mart Installation
The emoji picker component relies on Emoji Mart for the emoji picker interface. Choose your preferred installation method:
pin "emoji-mart", to: "https://cdn.jsdelivr.net/npm/emoji-mart@latest/dist/browser.js"
npm install emoji-mart
yarn add emoji-mart
3. Custom Styling
Add custom CSS to style the emoji picker & match your design system:
/* Emoji Picker Styles */
em-emoji-picker {
--color-border-over: rgba(0, 0, 0, 0.1);
--color-border: rgba(0, 0, 0, 0.05);
--font-family: "Inter", sans-serif;
--rgb-accent: 155, 155, 155;
position: absolute;
z-index: 1000;
max-width: 400px;
min-width: 300px;
resize: horizontal;
overflow: auto;
}
@media (max-width: 768px) {
em-emoji-picker {
max-width: 80vw;
}
}
Examples
Basic emoji picker
A simple emoji picker with a button trigger an a hidden input field for storing the selected emoji.
<div data-controller="emoji-picker">
<div class="flex items-center gap-2">
<button type="button" class="outline-hidden size-8 text-xl shrink-0 flex items-center justify-center rounded-md text-neutral-700 hover:bg-neutral-100 hover:text-neutral-800 focus:outline-hidden disabled:pointer-events-none disabled:opacity-50 dark:text-neutral-300 dark:hover:bg-neutral-800 dark:hover:text-neutral-200" data-action="click->emoji-picker#toggle" data-emoji-picker-target="button">
<svg xmlns="http://www.w3.org/2000/svg" class="size-5" width="18" height="18" viewBox="0 0 18 18"><g fill="currentColor"><path d="M9,1C4.589,1,1,4.589,1,9s3.589,8,8,8,8-3.589,8-8S13.411,1,9,1Zm-4,7c0-.552,.448-1,1-1s1,.448,1,1-.448,1-1,1-1-.448-1-1Zm4,6c-1.531,0-2.859-1.14-3.089-2.651-.034-.221,.039-.444,.193-.598,.151-.15,.358-.217,.572-.185,1.526,.24,3.106,.24,4.638,.001h0c.217-.032,.428,.036,.583,.189,.153,.153,.225,.373,.192,.589-.229,1.513-1.557,2.654-3.089,2.654Zm3-5c-.552,0-1-.448-1-1s.448-1,1-1,1,.448,1,1-.448,1-1,1Z"></path></g></svg>
</button>
<input type="text" name="emoji" class="hidden" placeholder="Select an emoji..." data-emoji-picker-target="input">
</div>
<div data-emoji-picker-target="pickerContainer" class="hidden absolute z-50 mt-2 flex justify-center inset-x-0"></div>
</div>
Auto-submit control
Compare auto-submit enabled (default) vs disabled behavior. When disabled, forms require manual submission.
Auto-submit enabled (default)
Auto-submit disabled
<div class="space-y-6">
<!-- Auto-submit enabled (default behavior) -->
<div class="space-y-2">
<h4 class="text-sm font-medium text-neutral-700 dark:text-neutral-300">Auto-submit enabled (default)</h4>
<form class="p-4 border border-neutral-200 dark:border-neutral-700 rounded-lg bg-neutral-50 dark:bg-neutral-800">
<div class="relative gap-y-1.5" data-controller="emoji-picker" data-emoji-picker-auto-submit-value="true">
<label for="reaction_1" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">Reaction (auto-submits)</label>
<div class="flex items-center gap-2">
<button type="button" class="outline-hidden size-8 text-xl shrink-0 flex items-center justify-center rounded-md text-neutral-700 hover:bg-neutral-100 hover:text-neutral-800 focus:outline-hidden disabled:pointer-events-none disabled:opacity-50 dark:text-neutral-300 dark:hover:bg-neutral-800 dark:hover:text-neutral-200" data-action="click->emoji-picker#toggle" data-emoji-picker-target="button">
<svg xmlns="http://www.w3.org/2000/svg" class="size-6" width="18" height="18" viewBox="0 0 18 18"><g fill="currentColor"><path d="M9,1C4.589,1,1,4.589,1,9s3.589,8,8,8,8-3.589,8-8S13.411,1,9,1Zm-4,7c0-.552,.448-1,1-1s1,.448,1,1-.448,1-1,1-1-.448-1-1Zm4,6c-1.531,0-2.859-1.14-3.089-2.651-.034-.221,.039-.444,.193-.598,.151-.15,.358-.217,.572-.185,1.526,.24,3.106,.24,4.638,.001h0c.217-.032,.428,.036,.583,.189,.153,.153,.225,.373,.192,.589-.229,1.513-1.557,2.654-3.089,2.654Zm3-5c-.552,0-1-.448-1-1s.448-1,1-1,1,.448,1,1-.448,1-1,1Z"></path></g></svg>
</button>
<input type="text" id="reaction_1" name="reaction" autocomplete="off" class="form-control" placeholder="Choose an emoji..." data-emoji-picker-target="input">
</div>
<div data-emoji-picker-target="pickerContainer" class="hidden absolute z-50 mt-2 flex justify-start inset-x-0"></div>
</div>
</form>
</div>
<!-- Auto-submit disabled -->
<div class="space-y-2">
<h4 class="text-sm font-medium text-neutral-700 dark:text-neutral-300">Auto-submit disabled</h4>
<form class="p-4 border border-neutral-200 dark:border-neutral-700 rounded-lg bg-neutral-50 dark:bg-neutral-800">
<div class="space-y-4">
<div class="mb-4 relative gap-y-1.5" data-controller="emoji-picker" data-emoji-picker-auto-submit-value="false">
<label for="reaction_2" class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-2">Reaction (manual submit)</label>
<div class="flex items-center gap-2">
<button type="button" class="outline-hidden size-8 text-xl shrink-0 flex items-center justify-center rounded-md text-neutral-700 hover:bg-neutral-100 hover:text-neutral-800 focus:outline-hidden disabled:pointer-events-none disabled:opacity-50 dark:text-neutral-300 dark:hover:bg-neutral-800 dark:hover:text-neutral-200" data-action="click->emoji-picker#toggle" data-emoji-picker-target="button">
<svg xmlns="http://www.w3.org/2000/svg" class="size-6" width="18" height="18" viewBox="0 0 18 18"><g fill="currentColor"><path d="M9,1C4.589,1,1,4.589,1,9s3.589,8,8,8,8-3.589,8-8S13.411,1,9,1Zm-4,7c0-.552,.448-1,1-1s1,.448,1,1-.448,1-1,1-1-.448-1-1Zm4,6c-1.531,0-2.859-1.14-3.089-2.651-.034-.221,.039-.444,.193-.598,.151-.15,.358-.217,.572-.185,1.526,.24,3.106,.24,4.638,.001h0c.217-.032,.428,.036,.583,.189,.153,.153,.225,.373,.192,.589-.229,1.513-1.557,2.654-3.089,2.654Zm3-5c-.552,0-1-.448-1-1s.448-1,1-1,1,.448,1,1-.448,1-1,1Z"></path></g></svg>
</button>
<input type="text" id="reaction_2" name="reaction" autocomplete="off" class="form-control" placeholder="Choose an emoji..." data-emoji-picker-target="input">
</div>
<div data-emoji-picker-target="pickerContainer" class="hidden absolute z-50 mt-2 flex justify-start inset-x-0"></div>
</div>
<button type="submit" class="w-full flex items-center justify-center gap-1.5 rounded-lg border border-neutral-400/30 bg-neutral-800 px-3 py-2 text-xs font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-neutral-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-800 dark:hover:bg-neutral-100 dark:focus-visible:outline-neutral-200">
Send
</button>
</div>
</form>
</div>
</div>
Insert Mode
Insert mode allows you to insert the selected emoji at the cursor position instead of replacing the entire value of the input field.
<div class="w-full max-w-lg">
<div class="w-full bg-white dark:bg-neutral-800 rounded-xl shadow-sm border border-neutral-200 dark:border-neutral-700 p-4">
<div data-controller="emoji-picker"
data-emoji-picker-insert-mode-value="true"
data-emoji-picker-target-selector-value="#chat-message">
<textarea id="chat-message"
rows="4"
class="form-control w-full mb-3 min-h-24 max-h-48"
placeholder="Type your message here..."></textarea>
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<button type="button"
class="outline-hidden size-8 text-xl shrink-0 flex items-center justify-center rounded-md text-neutral-700 hover:bg-neutral-100 hover:text-neutral-800 focus:outline-hidden disabled:pointer-events-none disabled:opacity-50 dark:text-neutral-300 dark:hover:bg-neutral-700 dark:hover:text-neutral-200"
data-action="click->emoji-picker#toggle"
data-emoji-picker-target="button">
<svg xmlns="http://www.w3.org/2000/svg" class="size-5" width="18" height="18" viewBox="0 0 18 18"><g fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" stroke="currentColor"><circle cx="9" cy="9" r="7.25"></circle><circle cx="6" cy="8" r="1" fill="currentColor" data-stroke="none" stroke="none"></circle><circle cx="12" cy="8" r="1" fill="currentColor" data-stroke="none" stroke="none"></circle><path d="M11.897,10.757c-.154-.154-.366-.221-.583-.189h0c-1.532,.239-3.112,.238-4.638-.001-.214-.032-.421,.035-.572,.185-.154,.153-.227,.376-.193,.598,.23,1.511,1.558,2.651,3.089,2.651s2.86-1.141,3.089-2.654c.033-.216-.039-.436-.192-.589Z" fill="currentColor" data-stroke="none" stroke="none"></path></g></svg>
</button>
</div>
<button type="button" class="flex items-center justify-center gap-1.5 rounded-lg border border-neutral-400/30 bg-neutral-800 px-4 py-2 text-sm font-medium whitespace-nowrap text-white shadow-sm transition-all duration-100 ease-in-out select-none hover:bg-neutral-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-neutral-600 disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white dark:text-neutral-800 dark:hover:bg-neutral-100 dark:focus-visible:outline-neutral-200">
Send
</button>
</div>
<div data-emoji-picker-target="pickerContainer" class="hidden absolute z-50 mt-2 flex justify-center inset-x-0 *:w-full"></div>
<input type="text" name="emoji" class="form-control !hidden" data-emoji-picker-target="input">
</div>
</div>
</div>
Configuration
The emoji picker component uses Emoji Mart for the picker interface and provides keyboard navigation, form integration, and customizable styling through a Stimulus controller.
Controller Setup
Basic emoji picker structure with required data attributes:
<div data-controller="emoji-picker">
<button type="button" data-action="click->emoji-picker#toggle" data-emoji-picker-target="button">
<!-- Emoji icon -->
</button>
<input type="text" data-emoji-picker-target="input">
<div data-emoji-picker-target="pickerContainer" class="hidden"></div>
</div>
Configuration Values
Prop | Description | Type | Default |
---|---|---|---|
autoSubmit
|
Controls whether the form is automatically submitted when an emoji is selected or cleared |
Boolean
|
false
|
Targets
Target | Description | Required |
---|---|---|
button
|
The button element that triggers the emoji picker | Required |
input
|
The input field that stores the selected emoji value | Required |
pickerContainer
|
The container element where the emoji picker is rendered | Required |
Actions
Action | Description | Usage |
---|---|---|
toggle
|
Toggles the visibility of the emoji picker interface |
click->emoji-picker#toggle
|
Features
- Keyboard Navigation: Escape key closes picker, Backspace/Delete clears selected emoji
- Outside Click Detection: Automatically closes picker when clicking outside
- Form Integration: Optional auto-submit when emoji is selected (enabled by default)
- Visual Feedback: Button updates to show selected emoji
- Customizable Styling: CSS custom properties for theming
Browser Support
- Modern Browsers: Full support in all modern browsers with ES6+ support
- Emoji Mart: Requires modern browser support for custom elements
Accessibility Features
- Keyboard Accessible: Full keyboard navigation support
- Focus Management: Proper focus handling and visual indicators