I’m building a chat application using Streamlit, and I’m using st.popover
to contain the chat interface. However, I’m struggling with ensuring reliable auto-scrolling to the bottom of the chat message list when new messages are added. Here’s my setup:
import streamlit as st
# Custom CSS for styling the popover and chat messages
st.markdown("""
<style>
.stPopover > div[data-testid="stPopoverContent"] {
width: 400px !important;
max-width: 80vw !important;
height: 600px !important;
max-height: 80vh !important;
display: flex;
flex-direction: column;
}
.chat-messages {
flex-grow: 1;
overflow-y: auto;
padding: 10px;
background: #f9f9f9;
border: 1px solid #ddd;
border-radius: 5px;
}
.chat-input {
position: sticky;
bottom: 0;
background-color: white;
padding: 10px;
border-top: 1px solid #e0e0e0;
z-index: 10;
}
</style>
""", unsafe_allow_html=True)
# JavaScript to scroll to the bottom
scroll_script = """
<script>
setTimeout(() => {
const container = window.parent.document.querySelector('.chat-messages');
if (container) {
container.scrollTop = container.scrollHeight || 999999;
}
}, 200);
</script>
"""
# Initialize chat history
if "messages" not in st.session_state:
st.session_state.messages = []
# Chat popover
with st.popover("💬 Chat Assistant", use_container_width=True):
# Div for messages with scrolling
st.markdown('<div class="chat-messages">', unsafe_allow_html=True)
# Display chat messages
for message in st.session_state.messages:
with st.chat_message("user" if "user" in message else "assistant"):
st.markdown(message)
st.markdown('</div>', unsafe_allow_html=True)
# Div for input with sticky positioning
st.markdown('<div class="chat-input">', unsafe_allow_html=True)
# Input area
if prompt := st.chat_input("Type your message here..."):
st.session_state.messages.append(f"You said: {prompt}")
# Inject scroll script to ensure bottom is visible
st.markdown(scroll_script, unsafe_allow_html=True)
# Refresh to update chat
st.experimental_rerun()
st.markdown('</div>', unsafe_allow_html=True)
# Ensure scroll script runs
st.markdown(scroll_script, unsafe_allow_html=True)