Open this notebook in Jupyterlite | Download this notebook from GitHub (right-click to download).

import asyncio
import pandas as pd
import panel as pn

from import ChatMessage


The ChatMessage is a pane for displaying chat messages with support for various content types.

This pane provides a structured view of chat messages, including features like:

  • Displaying user avatars, which can be text, emoji, or images.

  • Showing the userโ€™s name.

  • Displaying the message timestamp in a customizable format.

  • Associating reactions with messages and mapping them to icons.

  • Rendering various content types including text, images, audio, video, and more.

See ChatFeed for a structured and straightforward way to build a list of ChatMessage objects.

See ChatInterface for a high-level, easy to use, ChatGPT like interface.

Chat Design Specification


For layout and styling related parameters see the customization user guide.


  • object (object): The message contents. Can be a string, pane, widget, layout, etc.

  • renderers (List[Callable]): A callable or list of callables that accept the value and return a Panel object to render the value. If a list is provided, will attempt to use the first renderer that does not raise an exception. If None, will attempt to infer the renderer from the value.

  • user (str): Name of the user who sent the message.

  • avatar (str | BinaryIO): The avatar to use for the user. Can be a single character text, an emoji, or anything supported by pn.pane.Image. If not set, uses the first character of the name.

  • default_avatars (Dict[str, str | BinaryIO]): A default mapping of user names to their corresponding avatars to use when the user is set but the avatar is not. You can modify, but not replace the dictionary. Note, the keys are only alphanumeric sensitive, meaning spaces, special characters, and case sensitivity is disregarded, e.g. "Chat-GPT3.5", "chatgpt 3.5" and "Chat GPT 3.5" all map to the same value.

  • avatar_lookup (Callable): A function that can lookup an avatar from a user name. The function signature should be (user: str) -> Avatar. If this is set, default_avatars is disregarded.

  • reactions (List): Reactions associated with the message.

  • reaction_icons (ChatReactionIcons | dict): A mapping of reactions to their reaction icons; if not provided defaults to {"favorite": "heart"}. Provides a visual representation of reactions.

  • timestamp (datetime): Timestamp of the message. Defaults to the instantiation time.

  • timestamp_format (str): The format in which the timestamp should be displayed.


  • show_avatar (bool): Whether to display the avatar of the user.

  • show_user (bool): Whether to display the name of the user.

  • show_timestamp (bool): Whether to display the timestamp of the message.

  • show_reaction_icons (bool): Whether to display the reaction icons.

  • show_copy_icon (bool): Whether to show the copy icon.

  • name (str): The title or name of the chat message widget, if any.

ChatMessage("Hi and welcome!")

The ChatMessage can display any Python object that Panel can display! For example Panel components, dataframes and plot figures.

df = pd.DataFrame({"x": [1, 2, 3], "y": [4, 5, 6]})

vegalite = {
  "$schema": "",
  "data": {"url": ""},
  "mark": "bar",
  "encoding": {
    "x": {"aggregate": "sum", "field": "yield", "type": "quantitative"},
    "y": {"field": "variety", "type": "nominal"},
    "color": {"field": "site", "type": "nominal"}
  "width": "container",
vgl_pane = pn.pane.Vega(vegalite, height=240)


You can specify a custom user name and avatar

ChatMessage("Want to hear some beat boxing?", user="Beat Boxer", avatar="๐ŸŽถ")

Instead of searching for emojis online, you can also set a personalized emoji avatar by using its name wrapped in \N{}!

ChatMessage("Want to hear some beat boxing?", user="Beat Boxer", avatar="\N{musical note}")

You can combine ChatMessage with other Panel components as you like.

        "Yes. I want to hear some beat boxing", user="Music Lover", avatar="๐ŸŽธ"
            "Here goes. Hope you like this one?",
        user="Beat Boxer",

ChatMessage can be initialized without any input.

chat_message =

That way, the value, user, and/or avatar can be dynamically updated either by setting the value like thisโ€ฆ

chat_message.object = pn.pane.Markdown("## Cheers!")

Or updating multiple values at once with the .param.update method!

chat_message.param.update(user="Jolly Guy", avatar="๐ŸŽ…")
<param.parameterized._ParametersRestorer object at 0x11329aac0>

If you donโ€™t specify an avatar on construction, then an avatar will be looked up in the default_avatars dictionary.

{'client': '๐Ÿง‘', 'customer': '๐Ÿง‘', 'employee': '๐Ÿง‘', 'human': '๐Ÿง‘', 'person': '๐Ÿง‘', 'user': '๐Ÿง‘', 'agent': '๐Ÿค–', 'ai': '๐Ÿค–', 'assistant': '๐Ÿค–', 'bot': '๐Ÿค–', 'chatbot': '๐Ÿค–', 'machine': '๐Ÿค–', 'robot': '๐Ÿค–', 'system': 'โš™๏ธ', 'exception': 'โŒ', 'error': 'โŒ', 'adult': '๐Ÿง‘', 'baby': '๐Ÿ‘ถ', 'boy': '๐Ÿ‘ฆ', 'child': '๐Ÿง’', 'girl': '๐Ÿ‘ง', 'man': '๐Ÿ‘จ', 'woman': '๐Ÿ‘ฉ', 'chatgpt': '', 'gpt3': '', 'gpt4': '', 'dalle': '', 'openai': '', 'huggingface': '๐Ÿค—', 'calculator': '๐Ÿงฎ', 'langchain': '๐Ÿฆœ', 'retriever': '๐Ÿ“„', 'tool': '๐Ÿ› ๏ธ', 'translator': '๐ŸŒ', 'wolfram': '', 'wolfram alpha': '', 'llama': '๐Ÿฆ™', 'llama2': '๐Ÿช'}

You can modify the ChatMessage.default_avatars in-place.

Note, the keys are only alphanumeric sensitive, meaning spaces, special characters, and case sensitivity is disregarded, e.g. "Chat-GPT3.5", "chatgpt 3.5" and "Chat GPT 3.5" all map to the same value.

ChatMessage.default_avatars["Wolfram"] = "๐Ÿบ"
ChatMessage.default_avatars["#1 good-to-go guy"] = "๐Ÿ‘"

    ChatMessage("Mathematics!", user="Wolfram"),
    ChatMessage("Good to go!", user="#1 Good-to-Go Guy"),
    ChatMessage("What's up?", user="Other Guy"),

The timestamp can be formatted using timestamp_format."%b %d, %Y %I:%M %p")

The ChatMessage can serialized into a string.

widget = pn.widgets.FloatSlider(value=3, name="Number selected")
Number selected=3

If youโ€™d like a plain interface with only the value displayed, set show_user, show_copy_icon, show_avatar, and show_timestamp to False and provide an empty dict to reaction_icons.

    "Plain and simple",

You can set the usual styling and layout parameters like sizing_mode, height, width, max_height, max_width, styles and stylesheet.

    "Want to hear some beat boxing?",
    user="Beat Boxer",
    styles={"background": "#CBC3E3"},

Some active reactions can be associated with the message too."Love this!", reactions=["favorite"])

If youโ€™d like to display other reactions_icons, pass a pair of reaction key to tabler icon name.

message =
    "Looks good!",
    reaction_icons={"like": "thumb-up", "dislike": "thumb-down"},

You may bind a callback to the selected reactions.

Here, when the user clicks or sets reactions, print_reactions activates.

def print_reactions(reactions):
    print(f"{reactions} selected.")

pn.bind(print_reactions, message.param.reactions)
<function bind.<locals>.wrapped at 0x113858af0>

The easiest and most optimal way to stream output to the ChatMessage is through async generators.

If youโ€™re unfamiliar with this term, donโ€™t fret!

Itโ€™s simply prefixing your function with async and replacing return with yield.

This example will show you how to replace the ChatMessage value.

async def replace_response():
    for value in range(0, 28):
        await asyncio.sleep(0.2)
        yield value


This example will show you how to append to the ChatMessage value.

sentence = """
    The greatest glory in living lies not in never falling,
    but in rising every time we fall.

async def append_response():
    value = ""
    for token in sentence.split():
        value += f" {token}"
        await asyncio.sleep(0.2)
        yield value

ChatMessage(append_response, user="Wise guy", avatar="๐Ÿค“")

For an example on renderers, see ChatInterface.

Open this notebook in Jupyterlite | Download this notebook from GitHub (right-click to download).