express.ui.Chat
express.ui.Chat(
id,
*,
client=None,
history=True,
greeting=None,
messages=(),
on_error='auto',
tokenizer=DEPRECATED,
)Examples
#| standalone: true
#| components: [editor, viewer]
#| layout: vertical
#| viewerHeight: 400
## file: app.py
from shiny.express import ui
# Set some Shiny page options
ui.page_opts(
title="Hello Shiny Chat",
fillable=True,
fillable_mobile=True,
)
# Create a chat instance
chat = ui.Chat(id="chat")
# Display it, with a startup message
chat.ui(
greeting="""
Hi! This is a simple Shiny `Chat` UI. Enter a message below and I will
simply repeat it back to you.
To learn more about chatbots and how to build them with Shiny, check out
[the documentation](https://shiny.posit.co/py/docs/genai-chatbots.html).
""",
)
# Define a callback to run when the user submits a message
@chat.on_user_submit
async def handle_user_input(user_input: str):
# Append a response to the chat
await chat.append_message(f"You said: {user_input}")
## file: requirements.txt
shiny
## file: _template.json
{
"type": "app",
"id": "chat-hello",
"title": "Hello Shiny Chat",
"next_steps": [
"Run the app with `shiny run app.py`."
]
}
Attributes
| Name | Description |
|---|---|
| latest_message_stream | React to changes in the latest message stream. |
Methods
| Name | Description |
|---|---|
| append_message | Append a message to the chat. |
| append_message_stream | Append a message as a stream of message chunks. |
| clear_messages | Clear all chat messages. |
| destroy | Destroy the chat instance. |
| enable_bookmarking | Enable bookmarking for the chat instance. |
| get_greeting | Get the current greeting content. |
| message_stream_context | Message stream context manager. |
| messages | Reactively read chat messages |
| on_user_submit | Define a function to invoke when user input is submitted. |
| remove_slash_command | Remove a previously registered slash command by name. |
| set_greeting | Set or clear the chat greeting. |
| set_user_message | Deprecated. Use update_user_input(value=value) instead. |
| slash_command | Register a slash command and its handler. |
| transform_assistant_response | Deprecated. Assistant response transformation features will be removed in a future version. |
| ui | Create a UI element for this Chat. |
| update_user_input | Update the user input. |
| user_input | Reactively read the user’s latest submission. |
append_message
express.ui.Chat.append_message(message, *, icon=None) Append a message to the chat.
Parameters
message
A given message can be one of the following:
* A string, which is interpreted as markdown and rendered to HTML on the
client.
* To prevent interpreting as markdown, mark the string as
[](:class:`~shiny.express.ui.HTML`).
* A UI element (specifically, a [](:class:`~shiny.express.ui.TagChild`)).
* This includes [](:class:`~shiny.express.ui.TagList`), which takes UI elements
(including strings) as children. TagList content is treated as
HTML: strings inside it are literal text (HTML-escaped), not
markdown. Use [](:class:`~shiny.express.ui.HTML`) for trusted raw HTML
strings.
* A dictionary with `content` and `role` keys. The `content` key can contain
content as described above, and the `role` key can be "assistant" or
"user".
* More generally, any type registered with [](:func:`shinychat.message_content`).
**NOTE:** content may include specially formatted **input suggestion** links
(see note below).
icon
An optional icon to display next to the message, currently only used for
assistant messages. The icon can be any HTML element (e.g., an
[](:func:`~shiny.express.ui.img`) tag) or a string of HTML. Pass ``False`` to remove
the icon for this message, or ``True`` to use the default icon.
Note
:::{.callout-note title="Input suggestions"}
Input suggestions are special links that send text to the user input box when
clicked (or accessed via keyboard). They can be created in the following ways:
* `<span class='suggestion'>Suggestion text</span>`: An inline text link that
places 'Suggestion text' in the user input box when clicked.
* `<img data-suggestion='Suggestion text' src='image.jpg'>`: An image link with
the same functionality as above.
* `<span data-suggestion='Suggestion text'>Actual text</span>`: An inline text
link that places 'Suggestion text' in the user input box when clicked.
A suggestion can also be submitted automatically by doing one of the following:
* Adding a `submit` CSS class or a `data-suggestion-submit="true"` attribute to
the suggestion element.
* Holding the `Ctrl/Cmd` key while clicking the suggestion link.
Note that a user may also opt-out of submitting a suggestion by holding the
`Alt/Option` key while clicking the suggestion link.
A markdown list (`<ul>` or `<ol>`) in which every item contains a single
suggestion element is automatically rendered as a grid of clickable cards instead
of inline chips. Each suggestion accepts an optional `title` attribute (plain
text), which becomes the card heading; the suggestion's body becomes the card
description. For ordered lists (`<ol>`), the list-item number is included in the
heading.
:::
:::{.callout-note title="Asides"}
An aside is a small pill that appears at the end of the paragraph or
list item it's attached to, showing a popover on hover, click, or
keyboard focus. Create one by writing (or prompting an LLM to write) an
inline `<shiny-aside>` tag anywhere in a block's markdown; the tag's
content becomes the popover body:
* `<shiny-aside label="a source name" url="https://...">markdown shown in the popover</shiny-aside>`
`label` controls the text on the identity chip. A safe `url` makes the
source heading in the popover a link. It also supplies a derived favicon
unless `icon` overrides it. Without a `label`, the aside falls back to
a plain numbered marker. The body is ordinary markdown: inline for a
one-liner, or — by separating it with blank lines — a rich block body
(paragraphs, lists, code) shown in the popover. Labeled asides in the
same paragraph or list item collapse into one pill, with each aside kept
as a separate popover page. Each unlabeled aside remains a separate
numbered pill. The grouped pill shows a `+N` overflow count only when
its labeled asides have different labels. Asides that share one label
use a single face with no count.
Set these CSS properties on the chat container to style aside markers:
* `--shiny-chat-aside-marker-color`
* `--shiny-chat-aside-marker-hover-color`
* `--shiny-chat-aside-marker-bg`
* `--shiny-chat-aside-marker-hover-bg`
* `--shiny-chat-aside-marker-font-family`
`grounded-span` identifies the answer text that is related to an aside.
Its value must exactly match text before the tag in the same paragraph
or list item. When the popover opens, shinychat highlights the most
recent match. If the value does not match, no text is highlighted.
Long content wraps and scrolls within the viewport. The popover keeps
the nearest scoped Bootstrap theme. In a paged popover, page changes
are announced to assistive technology without repeating the body.
Set `display="compact"` to show a compact numbered reference in the
message. The popover retains the source label. Compact asides in the
same paragraph or list item share a marker, such as `[2, 3]`.
To style only compact markers, set the CSS properties above on
`[data-shinychat-aside-display="compact"]`.
The favicon is fetched at render time from a third-party service
(DuckDuckGo's icon service), which receives the cited site's hostname.
To avoid that request — for privacy, or for offline/air-gapped
deployments — set the ``SHINYCHAT_ASIDE_FAVICON`` environment variable
to ``false``. You can still set `icon` to a URL you control; an
explicit `icon` bypasses the lookup entirely.
**Examples:**
* A labeled aside with a grounded span and a one-line body:
`Hub motors are cheaper<shiny-aside label="eBicycles" url="https://ebicycles.example/hub-vs-mid-drive" grounded-span="Hub motors are cheaper">[Hub Motor vs. Mid-Drive Motor Differences Explained](https://ebicycles.example/hub-vs-mid-drive)</shiny-aside>, and ideal for flatter terrain.`
* Compact labeled asides that share one numbered marker:
`'Revenue is recognized at shipment<shiny-aside display="compact" label="Revenue policy">Exact revenue policy.</shiny-aside> and records are retained for 30 days<shiny-aside display="compact" label="Retention policy">Exact retention policy.</shiny-aside>.'`
* Two asides cited in the same sentence collapse into a single pill
— the first source's label becomes the face, with a "+1" overflow:
`...<shiny-aside label="eBicycles" url="https://ebicycles.example">...</shiny-aside><shiny-aside label="WIRED" url="https://wired.example">...</shiny-aside>...`
* A label-less aside with a rich block body (a blank line starts a
block body instead of an inline one), falling back to a plain
numbered pill:
`Battery quality matters more than raw power<shiny-aside>
Methodology
- 40 commuter e-bike models
- released in 2024
` :::
:::{.callout-note title="Streamed messages"}
Use `.append_message_stream()` instead of this method when `stream=True` (or
similar) is specified in model's completion method.
:::
append_message_stream
express.ui.Chat.append_message_stream(message, *, icon=None) Append a message as a stream of message chunks.
Parameters
message
An (async) iterable of message chunks. Each chunk can be one of the
following:
* A string, which is interpreted as markdown and rendered to HTML on the
client.
* To prevent interpreting as markdown, mark the string as
[](:class:`~shiny.express.ui.HTML`).
* A UI element (specifically, a [](:class:`~shiny.express.ui.TagChild`)).
* This includes [](:class:`~shiny.express.ui.TagList`), which takes UI elements
(including strings) as children. TagList content is treated as
HTML: strings inside it are literal text (HTML-escaped), not
markdown. Use [](:class:`~shiny.express.ui.HTML`) for trusted raw HTML
strings.
* A dictionary with `content` and `role` keys. The `content` key can contain
content as described above, and the `role` key can be "assistant" or
"user".
* More generally, any type registered with [](:func:`shinychat.message_content_chunk`).
**NOTE:** content may include specially formatted **input suggestion** links
(see note below).
icon
An optional icon to display next to the message, currently only used for
assistant messages. The icon can be any HTML element (e.g., an
[](:func:`~shiny.express.ui.img`) tag) or a string of HTML. Pass ``False`` to remove
the icon for this message, or ``True`` to use the default icon.
Note
```{.callout-note title="Input suggestions"}
Input suggestions are special links that send text to the user input box when
clicked (or accessed via keyboard). They can be created in the following ways:
* `<span class='suggestion'>Suggestion text</span>`: An inline text link that
places 'Suggestion text' in the user input box when clicked.
* `<img data-suggestion='Suggestion text' src='image.jpg'>`: An image link with
the same functionality as above.
* `<span data-suggestion='Suggestion text'>Actual text</span>`: An inline text
link that places 'Suggestion text' in the user input box when clicked.
A suggestion can also be submitted automatically by doing one of the following:
* Adding a `submit` CSS class or a `data-suggestion-submit="true"` attribute to
the suggestion element.
* Holding the `Ctrl/Cmd` key while clicking the suggestion link.
Note that a user may also opt-out of submitting a suggestion by holding the
`Alt/Option` key while clicking the suggestion link.
A markdown list (`<ul>` or `<ol>`) in which every item contains a single
suggestion element is automatically rendered as a grid of clickable cards instead
of inline chips. Each suggestion accepts an optional `title` attribute (plain
text), which becomes the card heading; the suggestion's body becomes the card
description. For ordered lists (`<ol>`), the list-item number is included in the
heading.
```
```{.callout-note title="Asides"}
An aside is a small pill that appears at the end of the paragraph or
list item it's attached to, showing a popover on hover, click, or
keyboard focus. Create one by writing (or prompting an LLM to write) an
inline `<shiny-aside>` tag anywhere in a block's markdown; the tag's
content becomes the popover body:
* `<shiny-aside label="a source name" url="https://...">markdown shown in the popover</shiny-aside>`
`label` controls the text on the identity chip. A safe `url` makes the
source heading in the popover a link. It also supplies a derived favicon
unless `icon` overrides it. Without a `label`, the aside falls back to
a plain numbered marker. The body is ordinary markdown: inline for a
one-liner, or — by separating it with blank lines — a rich block body
(paragraphs, lists, code) shown in the popover. Labeled asides in the
same paragraph or list item collapse into one pill, with each aside kept
as a separate popover page. Each unlabeled aside remains a separate
numbered pill. The grouped pill shows a `+N` overflow count only when
its labeled asides have different labels. Asides that share one label
use a single face with no count.
Set these CSS properties on the chat container to style aside markers:
* `--shiny-chat-aside-marker-color`
* `--shiny-chat-aside-marker-hover-color`
* `--shiny-chat-aside-marker-bg`
* `--shiny-chat-aside-marker-hover-bg`
* `--shiny-chat-aside-marker-font-family`
`grounded-span` identifies the answer text that is related to an aside.
Its value must exactly match text before the tag in the same paragraph
or list item. When the popover opens, shinychat highlights the most
recent match. If the value does not match, no text is highlighted.
Long content wraps and scrolls within the viewport. The popover keeps
the nearest scoped Bootstrap theme. In a paged popover, page changes
are announced to assistive technology without repeating the body.
Set `display="compact"` to show a compact numbered reference in the
message. The popover retains the source label. Compact asides in the
same paragraph or list item share a marker, such as `[2, 3]`.
To style only compact markers, set the CSS properties above on
`[data-shinychat-aside-display="compact"]`.
The favicon is fetched at render time from a third-party service
(DuckDuckGo's icon service), which receives the cited site's hostname.
To avoid that request — for privacy, or for offline/air-gapped
deployments — set the ``SHINYCHAT_ASIDE_FAVICON`` environment variable
to ``false``. You can still set `icon` to a URL you control; an
explicit `icon` bypasses the lookup entirely.
**Examples:**
* A labeled aside with a grounded span and a one-line body:
`Hub motors are cheaper<shiny-aside label="eBicycles" url="https://ebicycles.example/hub-vs-mid-drive" grounded-span="Hub motors are cheaper">[Hub Motor vs. Mid-Drive Motor Differences Explained](https://ebicycles.example/hub-vs-mid-drive)</shiny-aside>, and ideal for flatter terrain.`
* Compact labeled asides that share one numbered marker:
`'Revenue is recognized at shipment<shiny-aside display="compact" label="Revenue policy">Exact revenue policy.</shiny-aside> and records are retained for 30 days<shiny-aside display="compact" label="Retention policy">Exact retention policy.</shiny-aside>.'`
* Two asides cited in the same sentence collapse into a single pill
— the first source's label becomes the face, with a "+1" overflow:
`...<shiny-aside label="eBicycles" url="https://ebicycles.example">...</shiny-aside><shiny-aside label="WIRED" url="https://wired.example">...</shiny-aside>...`
* A label-less aside with a rich block body (a blank line starts a
block body instead of an inline one), falling back to a plain
numbered pill:
`Battery quality matters more than raw power<shiny-aside>
Methodology
- 40 commuter e-bike models
- released in 2024
` ```
```{.callout-note title="Streamed messages"}
Use this method (over `.append_message()`) when `stream=True` (or similar) is
specified in model's completion method.
```
Returns
:
An extended task that represents the streaming task. The `.result()` method
of the task can be called in a reactive context to get the final state of the
stream.
clear_messages
express.ui.Chat.clear_messages(greeting=False)Clear all chat messages.
Parameters
greeting : bool = False-
If
True, also clears the greeting in addition to conversation messages. Clearing the greeting causes the{id}_greeting_requestedinput to fire again (if the chat is visible with no greeting and no messages), enabling a regenerate pattern: clear the greeting, then react to the request to generate a new one viaset_greeting.
destroy
express.ui.Chat.destroy()Destroy the chat instance.
enable_bookmarking
express.ui.Chat.enable_bookmarking(
client,
/,
*,
bookmark_store=None,
bookmark_on='response',
)Enable bookmarking for the chat instance.
This method registers on_bookmark and on_restore hooks on session.bookmark (shiny.bookmark.Bookmark) to save/restore chat state on both the Chat and client= instances. In order for this method to actually work correctly, a bookmark_store= must be specified in shiny.express.app_opts().
Parameters
client : ‘ClientWithState | chatlas.Chat[Any, Any]’-
The chat client instance to use for bookmarking. This can be a Chat model provider from chatlas, or more generally, an instance following the
ClientWithStateprotocol. bookmark_store : ‘Optional[BookmarkStore]’ = None-
A convenience parameter to set the
shiny.express.app_opts(bookmark_store=)which is required for bookmarking (and.enable_bookmarking()). IfNone, no value will be set. bookmark_on : Optional[Literal[‘response’]] = 'response'-
The event to trigger the bookmarking on. Supported values include: -
"response"(the default): a bookmark is triggered when the assistant is done responding. -None: no bookmark is triggered When this method triggers a bookmark, it also updates the URL query string to reflect the bookmarked state.
Raises
: ValueError-
If the Shiny App does have bookmarking enabled.
Returns
:CancelCallback-
A callback to cancel the bookmarking hooks.
get_greeting
express.ui.Chat.get_greeting()Get the current greeting content.
Returns
: str or None-
The current greeting content, or
Noneif no greeting is set or has been cleared.
message_stream_context
express.ui.Chat.message_stream_context() Message stream context manager.
A context manager for appending streaming messages into the chat. This context
manager can:
1. Be used in isolation to append a new streaming message to the chat.
* Compared to `.append_message_stream()` this method is more flexible but
isn't non-blocking by default (i.e., it doesn't launch an extended task).
2. Be nested within itself
* Nesting is primarily useful for making checkpoints to `.replace()` back
to (see the example below).
3. Be used from within a `.append_message_stream()`
* Useful for inserting additional content from another context into the
stream (e.g., see the note about tool calls below).
Yields
:
A `MessageStream` class instance, which has a method for `.append()`ing
message content chunks to as well as a `.replace()` method to reset the
stream back to its initial state (via `.replace("")`). Note that
`.append()` supports the same message content types as `.append_message()`.
Example
```python
import asyncio
from shiny import reactive
from shiny.express import ui
chat = ui.Chat(id="my_chat")
chat.ui()
@reactive.effect
async def _():
async with chat.message_stream_context() as msg:
await msg.append("Starting stream...
Progress:“) async with chat.message_stream_context() as progress: for x in [0, 50, 100]: await progress.append(f” {x}%“) await asyncio.sleep(1) await progress.replace(”“) await msg.replace(”“) await msg.append(”Completed stream”) ```
Note
A useful pattern for displaying tool calls in a chatbot is for the tool to
display using `.message_stream_context()` while the the response generation is
happening through `.append_message_stream()`. This allows the tool to display
things like progress updates (or other "ephemeral" content) and optionally
`.replace("")` the stream back to it's initial state when ready to display the
"final" content.
Note
`.replace()` resets the stream to the checkpoint captured when this context was
entered. It raises `ValueError` if the stream's content since that checkpoint
spans multiple content types (e.g. thinking followed by markdown), because the
replace wire action carries a single content type. Open a fresh
`.message_stream_context()` before the mixed content if you need a clean
checkpoint to replace back to.
messages
express.ui.Chat.messages(format=DEPRECATED, token_limits=DEPRECATED)Reactively read chat messages
Obtain chat messages within a reactive context.
Parameters
format :DEPRECATED_TYPE= DEPRECATED-
Removed. Raises
TypeErrorif provided. Use your LLM provider (e.g., chatlas, LangChain) to manage message formatting instead. token_limits :DEPRECATED_TYPE= DEPRECATED-
Removed. Raises
TypeErrorif provided. Use your LLM provider (e.g., chatlas, LangChain) to manage token limits instead.
Note
Messages are listed in the order they were added. As a result, when this method is called in a .on_user_submit() callback (as it most often is), the last message will be the most recent one submitted by the user.
Note
Returns
: tuple[ChatMessageDict, …]-
A tuple of chat messages. The
attachmentsfield, when present, containsAttachmentobjects. These are Pydantic models, so call.model_dump()on each one before passing them tojson.dumps()or any other JSON serializer.
on_user_submit
express.ui.Chat.on_user_submit(fn=None)Define a function to invoke when user input is submitted.
Apply this method as a decorator to a function (fn) that should be invoked when the user submits a message. This function can take up to two optional arguments: the user input message (a str) and any attached files (a list[Attachment], where each item exposes mime (MIME type), data_url (a data:<mime>;base64,... URL), and name (the original filename) attributes).
In many cases, the implementation of fn should also do the following:
- Generate a response based on the user input.
- If the response should be aware of chat history, use a package like chatlas to manage the chat state, or use the
.messages()method to get the chat history.
- Append that response to the chat component using
.append_message()( or.append_message_stream()if the response is streamed).
Parameters
fn :UserSubmitFunction| None = None-
A function to invoke when user input is submitted.
Note
This method creates a reactive effect that only gets invalidated when the user submits a message. Thus, the function fn can read other reactive dependencies, but it will only be re-invoked when the user submits a message.
remove_slash_command
express.ui.Chat.remove_slash_command(name)Remove a previously registered slash command by name.
Parameters
name : str-
The name of the command to remove (without the leading
/).
set_greeting
express.ui.Chat.set_greeting(greeting)Set or clear the chat greeting.
A greeting is displayed at the top of the chat before any conversation messages. It can be static content, streaming content from an async iterator, or None to remove an existing greeting.
If the greeting has already been dismissed, calling this method updates the greeting content but does not make it visible again. To show a new greeting after dismissal, first clear the chat with await chat.clear_messages(greeting=True).
Parameters
greeting : ‘str | HTML | Tag | TagList | ChatGreeting | None’-
The greeting content. Can be: *
None: clears the current greeting entirely (distinct from dismissal). Use this before setting a new greeting when implementing a regenerate pattern. * A markdown string,HTML,Tag, orTagList: displayed as a stand-alone greeting. * Achat_greetingobject with options such aspersistent. * Achat_greetingwrapping anAsyncIterableof strings: streams the greeting content chunk-by-chunk.
Notes
When no greeting is set and the chat is visible with no messages, an input named {id}_greeting_requested fires (where {id} is the chat’s ID). Use @reactive.event(input.{id}_greeting_requested) to generate a greeting on demand. This input fires on first load and again after clear_messages is called with greeting=True. When the user dismisses the greeting, {id}_greeting_dismissed fires with a Date.now() timestamp. If the greeting is later cleared after being dismissed, the input resets to None.
Examples
Static greeting (stand-alone, dismissed on first message by default):
@reactive.effect
async def _():
await chat.set_greeting(
"## Welcome!\n\nHow can I help you today?"
)Static greeting with custom options:
from shinychat import chat_greeting
@reactive.effect
async def _():
greeting = chat_greeting(
"## Welcome!",
persistent=True,
)
await chat.set_greeting(greeting)Streaming greeting from an async iterator:
@reactive.effect
async def _():
async def token_stream():
for token in ["Hello", " there", "!"]:
yield token
await chat.set_greeting(chat_greeting(token_stream()))LLM-generated greeting using greeting_requested:
import chatlas
from shinychat import Chat, chat_greeting
chat_model = chatlas.ChatOpenAI(model="gpt-4o")
chat = Chat(id="chat")
@reactive.effect
@reactive.event(input.chat_greeting_requested)
async def _():
response = await chat_model.stream_async(
"Write a short, friendly welcome message."
)
await chat.set_greeting(chat_greeting(response))Regenerate pattern (clear and re-request):
@reactive.effect
@reactive.event(input.regenerate)
async def _():
await chat.clear_messages(greeting=True)
# greeting_requested fires again after clear_messages(greeting=True),
# so the LLM-generated greeting handler above will run again.Clear the greeting (e.g., before setting a new one):
await chat.set_greeting(None)set_user_message
express.ui.Chat.set_user_message(value)Deprecated. Use update_user_input(value=value) instead.
slash_command
express.ui.Chat.slash_command(
name,
description,
fn=MISSING,
*,
echo=None,
force=False,
)Register a slash command and its handler.
Can be used as a decorator (handler supplied by decoration) or called directly with fn=. Pass fn=None to register a client-side command — one with no server handler, handled in JavaScript via the shiny:chat-slash-command DOM event (see the docs).
Parameters
name : str-
The slash command name (without the leading
/). Must contain only alphanumeric characters, underscores, or hyphens. description : str-
A short description shown in the command palette.
fn :UserSubmitFunction| None |MISSING_TYPE= MISSING-
The handler function (0 or 1 argument; one argument receives the text after the command name). Omit it to use
slash_commandas a decorator. PassNoneexplicitly to register a client-side command with no server handler. echo : bool | None = None-
Whether invoking the command participates in the conversation: adds the
/cmd user_inputuser message, shows a loading state, and stores the invocation in history. Defaults toTruewhen a handler is provided andFalseotherwise. Setecho=Falsefor a server handler that runs purely for its side effects (e.g. opening a modal). force : bool = False-
Whether to overwrite an existing command with the same name.
Returns
transform_assistant_response
express.ui.Chat.transform_assistant_response(fn=None)Deprecated. Assistant response transformation features will be removed in a future version.
ui
express.ui.Chat.ui(
messages=None,
greeting=None,
placeholder='Enter a message...',
width='min(clamp(680px, 50vw, 760px), 100%)',
height='auto',
fill=True,
icon_assistant=None,
icon_send=None,
enable_cancel=MISSING,
submit_key='enter',
allow_attachments=MISSING,
toolbar_input=None,
footer=None,
tool_grouping='tool',
drawer=True,
show_history=True,
**kwargs,
)Create a UI element for this Chat.
Parameters
messages : Optional[Iterable[str | TagChild |ChatMessageDict|ChatMessage| Any]] = None-
Deprecated.
greeting : Optional[Union[str,HTML, Tag, TagList,ChatGreeting]] = None-
An optional greeting to display at the top of the chat before any conversation messages. Can be a markdown string or a
chat_greetingobject. placeholder : str = 'Enter a message…'-
Placeholder text for the chat input.
width : ‘CssUnit’ = 'min(clamp(680px, 50vw, 760px), 100%)'-
The width of the UI element.
height : ‘CssUnit’ = 'auto'-
The height of the UI element.
fill : bool = True-
Whether the chat should vertically take available space inside a fillable container.
icon_assistant :HTML| Tag | TagList | bool | None = None-
The icon to use for the assistant chat messages. Can be a HTML or a tag in the form of
HTMLorTag.None(the default) orFalseomits the assistant icon entirely. PassTrueto use the built-in robot icon (individual messages can still opt in to a different icon via theiconargument of.append_message()). icon_send :HTML| Tag | TagList | bool | None = None-
The icon to use for the chat input’s ready-state submit button. Can be a HTML or a tag in the form of
HTMLorTag. IfNone(the default) orFalse, a default arrow icon is used. The button provides a filled circular surface (state-colored background, white icon); the supplied icon replaces only the glyph inside it. Seechat_uifor the CSS variables that control the button’s appearance. enable_cancel : ‘bool | MISSING_TYPE’ = MISSING-
Whether to show a stop button during streaming that allows the user to cancel the in-progress response. When
True, the chat UI shows a stop button in place of the send button while streaming. You must observeinput.<id>_cancelon the server and callctrl.cancel()on a chatlasStreamControllerto actually stop the stream. Defaults toTruewhen aclient=was provided toChat,Falseotherwise. submit_key : ‘Literal[“enter”, “enter+modifier”]’ = 'enter'-
Controls which key combination submits the chat message: -
"enter"(default): Enter submits, Shift+Enter adds a newline. -"enter+modifier": Ctrl+Enter (Cmd+Enter on Mac) submits, plain Enter adds a newline. allow_attachments : ‘bool | list[str] | MISSING_TYPE’ = MISSING-
Controls the file-attachment affordance (an attach button, plus clipboard paste and drag-and-drop) in the chat input. Pass
Trueto accept all supported types (PNG, JPEG, GIF, WebP, PDF, and common text/code files such as Markdown, plain text, CSV, JSON, and source files),Falseto disable, or a list of MIME types to restrict what is accepted (each must be one of the supported types). Attachments are delivered to your.on_user_submit()handler’s second argument as alist[Attachment], where each item exposesmime,name,size, anddata_urlattributes (and forwarded to aclient=automatically). When left unset (the default), a chat driven by aclient=enables attachments automatically; otherwise it stays hidden. The maximum combined size of all attachments in a single message is controlled globally by theSHINYCHAT_MAX_ATTACHMENT_SIZEenvironment variable (a raw byte count; defaults to approximately 30 MB). Files that would push the total over this cap are rejected in the browser with a notice. When bookmarking is enabled, preferbookmark_store="server": attachment data is saved in the bookmark and can exceed URL length limits withbookmark_store="url". toolbar_input : Optional[TagChild] = None-
Optional HTML content displayed directly below the chat input. Use
shiny.express.ui.toolbarto group toolbar controls. footer : Optional[TagChild] = None-
Optional HTML content displayed in a bottom-pinned, full-width chat region. This can be any HTML content (tags, tag lists, or strings). Useful for adding disclaimers, attribution, or other information. The footer text is styled slightly smaller and lighter than body text by default. Customize with CSS properties
--shiny-chat-footer-font-sizeand--shiny-chat-footer-coloron the chat container or footer element. tool_grouping : ‘Literal[“none”, “tool”, “all”]’ = 'tool'-
Controls how tool calls are grouped together in the UI: -
"tool"(default): calls to the same tool within a tool-calling loop are grouped into a single activity row. This groups by tool name across the whole loop, not just consecutive calls – e.g. calls to toolsX,Y,Z,X,Y(in that order) are grouped intoX(2 calls),Y(2 calls), andZ(1 call). -"all": every tool call within a tool-calling loop is grouped into a single activity row, regardless of tool name. -"none": each tool call is shown in its own activity row. Prose or thinking between calls starts a new tool-calling loop, so grouping never crosses those transcript boundaries. Individual tools can override this via agroupingtool annotation. For chatlas tools, preferannotations={"extra": {"grouping": ...}}: a top-levelgroupingkey is also read, but it isn’t part of chatlas’ToolAnnotations, so type checkers reject it. Chat-level"none"always disables grouping, even when a tool annotation requests"tool"or"all". drawer : bool |ChatDrawer= True-
Whether the artifact panel is available. Pass a
ChatDrawerto supply its initial content and configuration. show_history : bool = True-
Whether to render the chat’s built-in history selector.
kwargs : TagAttrValue = {}-
Additional attributes for the chat container element.
update_user_input
express.ui.Chat.update_user_input(
value=None,
placeholder=None,
submit=False,
focus=False,
attachments=None,
attachment_mode='append',
)Update the user input.
Parameters
value : str | None = None-
The value to set the user input to.
placeholder : str | None = None-
The placeholder text for the user input.
submit : bool = False-
Whether to automatically submit the text for the user. Requires
value. focus : bool = False-
Whether to move focus to the input element. Requires
valueorattachments. attachments : ‘list[Attachment] | None’ = None-
Attachments to stage in the input. Pass an empty list to clear any currently staged attachments. When
submit=Truethe attachments are sent alongsidevalueand then cleared from the input. attachment_mode : “Literal[‘append’, ‘set’]” = 'append'-
How to combine
attachmentswith any already-staged attachments."append"(default) adds to the existing set;"set"replaces it. Passattachment_mode="set"withattachments=[]to clear all staged attachments.
user_input
express.ui.Chat.user_input()Reactively read the user's latest submission.
Returns
:UserInput| None-
Nonebefore the first user submission; otherwise a named tuple of the submitted text and any attached files. Theattachmentslist is empty unlessallow_attachmentswas enabled inchat_ui. Supports destructuring after aNonecheck:: result = chat.user_input() if result is not None: text, attachments = result
Note
Most users shouldn’t need to use this method directly since the last item in .messages() contains the most recent user input. It can be useful for:
- Taking a reactive dependency on the user’s input outside of a
.on_user_submit()callback. - Maintaining message state separately from
.messages().