image

09 Feb 2026

9K

35K

Building a Chat Input Widget with Emoji Picker in Flutter

Modern chat applications are incomplete without an intuitive and feature-rich input field. A key component of this is the ability to easily insert emojis, enhancing user expression and engagement. In this article, we'll walk through creating a sophisticated chat input widget in Flutter, complete with a text field, a send button, and an integrated emoji picker.

Prerequisites

  • Basic understanding of Flutter and Dart.
  • Flutter SDK installed.

Step 1: Project Setup and Dependencies

First, create a new Flutter project or open an existing one. We'll need a package for the emoji picker. A popular and well-maintained choice is emoji_picker_flutter. Add it to your pubspec.yaml file:


dependencies:
  flutter:
    sdk: flutter
  emoji_picker_flutter: ^2.1.1 # Use the latest version

After adding the dependency, run flutter pub get to fetch the package.

Step 2: Basic Chat Input Field Structure

Let's start by building the foundation of our chat input widget. We'll use a StatefulWidget to manage the input text, focus, and the visibility of the emoji picker.


import 'package:flutter/material.dart';
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart'; // Import emoji picker

class ChatInputWidget extends StatefulWidget {
  final Function(String) onSendMessage;

  const ChatInputWidget({Key? key, required this.onSendMessage}) : super(key: key);

  @override
  _ChatInputWidgetState createState() => _ChatInputWidgetState();
}

class _ChatInputWidgetState extends State {
  final TextEditingController _controller = TextEditingController();
  final FocusNode _focusNode = FocusNode();
  bool _isEmojiPickerVisible = false;

  @override
  void dispose() {
    _controller.dispose();
    _focusNode.dispose();
    super.dispose();
  }

  void _sendMessage() {
    if (_controller.text.isNotEmpty) {
      widget.onSendMessage(_controller.text);
      _controller.clear();
    }
  }

  void _toggleEmojiPicker() {
    setState(() {
      _isEmojiPickerVisible = !_isEmojiPickerVisible;
      if (_isEmojiPickerVisible) {
        _focusNode.unfocus(); // Hide keyboard when emoji picker is shown
      } else {
        _focusNode.requestFocus(); // Show keyboard when emoji picker is hidden
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Row(
          children: [
            IconButton(
              icon: Icon(
                _isEmojiPickerVisible ? Icons.keyboard : Icons.emoji_emotions,
              ),
              onPressed: _toggleEmojiPicker,
            ),
            Expanded(
              child: TextField(
                controller: _controller,
                focusNode: _focusNode,
                maxLines: null, // Allows multiline input
                keyboardType: TextInputType.multiline,
                decoration: InputDecoration(
                  hintText: 'Type a message...',
                  border: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(25.0),
                    borderSide: BorderSide.none,
                  ),
                  filled: true,
                  contentPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
                ),
                onChanged: (text) {
                  // You can add typing indicators or other logic here
                },
                onTap: () {
                  if (_isEmojiPickerVisible) {
                    setState(() {
                      _isEmojiPickerVisible = false; // Hide emoji picker when typing
                    });
                  }
                },
              ),
            ),
            IconButton(
              icon: Icon(Icons.send),
              onPressed: _sendMessage,
            ),
          ],
        ),
        if (_isEmojiPickerVisible)
          SizedBox(
            height: 250, // Adjust height as needed
            child: EmojiPicker(
              textEditingController: _controller,
              config: Config(
                columns: 7,
                emojiSizeMax: 32.0, // Adjust size as needed
                verticalSpacing: 0,
                horizontalSpacing: 0,
                gridPadding: EdgeInsets.zero,
                initCategory: Category.RECENT,
                bgColor: Color(0xFFF2F2F2),
                indicatorColor: Colors.blue,
                iconColor: Colors.grey,
                iconColorSelected: Colors.blue,
                progressIndicatorColor: Colors.blue,
                backspaceColor: Colors.blue,
                skinToneDialogBgColor: Colors.white,
                skinToneIndicatorColor: Colors.grey,
                enableSkinTonePicker: true,
                showRecentsTab: true,
                recentsLimit: 28,
                noRecents: const Text(
                  'No Recents',
                  textAlign: TextAlign.center,
                  style: TextStyle(fontSize: 20, color: Colors.black26),
                ),
                tabIndicatorAnimDuration: kTabScrollDuration,
                categoryIcons: const CategoryIcons(),
                buttonMode: ButtonMode.MATERIAL, // or ButtonMode.CUPERTINO
              ),
            ),
          ),
      ],
    );
  }
}

Explanation of the Code

Let's break down the key parts of the implementation:

  • _controller (TextEditingController): Manages the text input in the TextField.
  • _focusNode (FocusNode): Controls the focus of the TextField. We use it to programmatically hide the keyboard when the emoji picker is shown and vice-versa.
  • _isEmojiPickerVisible (bool): A state variable to toggle the visibility of the emoji picker.
  • _sendMessage(): A simple method to handle sending the message (e.g., passing it to a parent widget via onSendMessage callback).
  • _toggleEmojiPicker(): This crucial method switches between showing the keyboard and showing the emoji picker. When the emoji picker is to be shown, _focusNode.unfocus() is called to dismiss the keyboard. When the emoji picker is to be hidden, _focusNode.requestFocus() is called to bring the keyboard back.
  • TextField: The main input area. It's configured to be multiline (maxLines: null, keyboardType: TextInputType.multiline) and has a tap handler to hide the emoji picker if it's visible when the user taps into the text field.
  • Emoji Toggle Button: An IconButton whose icon changes based on whether the emoji picker is visible (Icons.keyboard) or hidden (Icons.emoji_emotions). Its onPressed calls _toggleEmojiPicker.
  • Send Button: Another IconButton to trigger the message sending.
  • EmojiPicker Widget: This widget from the emoji_picker_flutter package is conditionally rendered based on _isEmojiPickerVisible.
    • textEditingController: _controller: Connects the emoji picker directly to our TextField's controller, so selected emojis are automatically inserted at the current cursor position.
    • config: Allows extensive customization of the emoji picker's appearance and behavior, such as column count, emoji size, background color, and more.

Usage

To use this ChatInputWidget in your application, simply place it where you need a chat input, for example, at the bottom of a Scaffold:


import 'package:flutter/material.dart';
import 'chat_input_widget.dart'; // Assuming you saved the widget in 'chat_input_widget.dart'

class ChatScreen extends StatefulWidget {
  const ChatScreen({Key? key}) : super(key: key);

  @override
  State createState() => _ChatScreenState();
}

class _ChatScreenState extends State {
  List messages = [];

  void _addMessage(String message) {
    setState(() {
      messages.add(message);
    });
    // In a real app, you would send this message to a backend
    print("Sending message: $message");
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Flutter Chat')),
      body: Column(
        children: [
          Expanded(
            child: ListView.builder(
              reverse: true, // Newest messages at the bottom
              itemCount: messages.length,
              itemBuilder: (context, index) {
                return Align(
                  alignment: Alignment.centerRight, // Example: right align user messages
                  child: Card(
                    margin: EdgeInsets.all(8),
                    child: Padding(
                      padding: const EdgeInsets.all(12.0),
                      child: Text(messages[messages.length - 1 - index]),
                    ),
                  ),
                );
              },
            ),
          ),
          Divider(height: 1),
          ChatInputWidget(onSendMessage: _addMessage),
        ],
      ),
    );
  }
}

Conclusion

By following these steps, you've successfully created a robust and interactive chat input widget with an integrated emoji picker in Flutter. This enhances the user experience significantly, allowing for more expressive communication. Remember to customize the styling and behavior of both the TextField and the EmojiPicker to match your application's design language.

Related Articles

May 14, 2026

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter

Building a Multi-Event Countdown Timer Widget with Reminders, Notifications, Repeat, and Custom Labels in Flutter Countdown timers are essential in many applic

May 11, 2026

Unleashing Dynamic UIs: Flutter's Animation Prowess

Unleashing Dynamic UIs: Flutter's Animation Prowess for Slide & Scale Effects Flutter's declarative UI framework, combined with its powerful animation capabilit

May 11, 2026

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy

Building a Product Detail Page Widget in Flutter with Related Items, Review Carousel, Promo Badges, and Quick Buy A well-designed Product Detail Page (PDP) is