При изменении ширины боковой панели (sidebar) в Streamlit иконки удаления (🗑️) могут "съезжать" относительно названий чатов, создавая визуальный дискомфорт и нарушая выравнивание элементов.
Пример проблемного поведения:
Sidebar узкий (200px):
┌────────────────────┐
│ 💬 Чат 1 🗑️ │ ← иконка справа
│ 💬 Чат 2 🗑️ │
└────────────────────┘
Sidebar широкий (400px):
┌──────────────────────────────────┐
│ 💬 Чат 1 🗑️ │ ← иконка "уехала" далеко вправо
│ 💬 Чат 2 🗑️ │ ← нарушено визуальное единство
└──────────────────────────────────┘
Вместо фиксированных пиксельных значений используем процентное соотношение колонок через st.columns():
# Соотношение 85% : 15%
col1, col2 = st.columns([0.85, 0.15], gap="small")col1всегда занимает 85% от доступной ширины sidebarcol2всегда занимает 15% от доступной ширины sidebar
При изменении ширины sidebar обе колонки масштабируются синхронно:
Sidebar 200px:
- col1 = 170px (85%)
- col2 = 30px (15%)
Sidebar 400px:
- col1 = 340px (85%)
- col2 = 60px (15%)
Соотношение остаётся: 85:15
gap="small"— минимальный фиксированный отступ между колонкамиuse_container_width=True— кнопки заполняют всю ширину своей колонки
# === СТАБИЛИЗАЦИЯ ВЫРАВНИВАНИЯ ИКОНКИ УДАЛЕНИЯ ===
#
# Проблема: при изменении ширины sidebar иконка 🗑️ может
# "съезжать" относительно названия чата, создавая визуальный
# дискомфорт.
#
# Решение: используем st.columns с фиксированным процентным
# соотношением [0.85, 0.15], что означает:
# - col1 занимает 85% доступной ширины (название чата)
# - col2 занимает 15% доступной ширины (кнопка удаления)
#
# Почему это работает:
# 1. Процентное соотношение остаётся постоянным независимо
# от абсолютной ширины sidebar
# 2. При ресайзе sidebar обе колонки масштабируются
# пропорционально, сохраняя относительное положение
# 3. gap="small" создаёт минимальный отступ между колонками,
# который также масштабируется пропорционально
# 4. use_container_width=True в обеих кнопках гарантирует,
# что они заполняют всю доступную ширину своей колонки
#
# Результат: иконка 🗑️ всегда остаётся на одном
# горизонтальном уровне с названием чата, независимо от
# ширины sidebar (100px или 500px — не важно).
#
col1, col2 = st.columns([0.85, 0.15], gap="small")
with col1:
# Кнопка переключения на выбранный чат
# use_container_width=True — кнопка растягивается на всю
# ширину колонки (85% от доступного пространства)
# Это обеспечивает адаптивность при изменении размера
if st.button(
label,
key=f"session_btn_{session_id}",
use_container_width=True,
type=button_type,
):
chat_manager.switch_session(session_id)
st.rerun()
with col2:
# Кнопка удаления чата
# Занимает фиксированные 15% от общей ширины
# use_container_width=True — заполняет всю колонку
# Благодаря процентному соотношению колонок, иконка
# всегда остаётся справа на одном уровне с названием
if st.button(
"🗑️",
key=f"delete_btn_{session_id}",
help="Удалить чат",
use_container_width=True,
):
st.session_state["pending_delete"] = session_id
st.rerun()-
Стабильное выравнивание
- Иконки всегда на одном горизонтальном уровне с названиями
- Визуальное единство при любой ширине sidebar
-
Адаптивность
- Не фиксируем абсолютную ширину sidebar
- Пользователь может свободно изменять размер
-
Простота реализации
- Только встроенные средства Streamlit
- Нет кастомного CSS
- Нет JavaScript
-
Масштабируемость
- Работает для любого количества элементов
- Легко применить к другим спискам
Sidebar узкий:
┌──────────────────────┐
│ 💬 Чат 1 🗑️ │ ← ОК
└──────────────────────┘
Sidebar широкий:
┌────────────────────────────────────────┐
│ 💬 Чат 1 🗑️ │ ← Иконка "уехала" вправо
└────────────────────────────────────────┘
Sidebar узкий:
┌──────────────────────┐
│ 💬 Чат 1 🗑️ │ ← ОК
└──────────────────────┘
Sidebar широкий:
┌────────────────────────────────────────┐
│ 💬 Чат 1 🗑️ │ ← Пропорционально расширено
└────────────────────────────────────────┘
Соотношение 85:15 сохраняется!
# Три колонки с равным соотношением [1, 1, 1]
# Каждая кнопка занимает ровно 33.3% ширины
col1, col2, col3 = st.columns(3)
with col1:
st.button("✍️", key="prompt_essay", use_container_width=True)
with col2:
st.button("🔍", key="prompt_explain", use_container_width=True)
with col3:
st.button("📝", key="prompt_summarize", use_container_width=True)Результат: Все три иконки всегда на одном уровне, равномерно распределены.
# Две колонки с равным соотношением [1, 1] (50:50)
confirm_col1, confirm_col2 = st.columns(2)
with confirm_col1:
st.button("✅ Да, удалить", use_container_width=True)
with confirm_col2:
st.button("❌ Отмена", use_container_width=True)Результат: Симметричные кнопки, всегда равной ширины.
Этот подход универсален и может быть применён к любым спискам элементов в Streamlit, где требуется:
-
Стабильное горизонтальное выравнивание
- Списки с кнопками действий
- Таблицы с иконками
- Меню с переключателями
-
Адаптивный дизайн
- Не зависит от ширины контейнера
- Работает на мобильных устройствах
- Поддерживает изменение размера окна
-
Чистый код
- Без хаков и костылей
- Только нативные средства Streamlit
- Легко поддерживать и модифицировать
| Случай | Соотношение | Пример |
|---|---|---|
| Текст + иконка | [0.85, 0.15] |
Название + 🗑️ |
| Длинный текст + иконка | [0.90, 0.10] |
Описание + ⚙️ |
| Две кнопки равной важности | [1, 1] |
Да / Нет |
| Три кнопки равной важности | [1, 1, 1] |
✍️ / 🔍 / 📝 |
| Главная + вторичная кнопка | [0.70, 0.30] |
Отправить / Отмена |
❌ Избегайте процентных колонок, если:
- Контент имеет фиксированную ширину (например, изображения)
- Нужна точная пиксельная выверка
- Элементы должны перестраиваться (wrap) при ресайзе
✅ Используйте процентные колонки, если:
- Нужна адаптивность к ширине контейнера
- Важно сохранить пропорции элементов
- Элементы остаются на одной линии
- ✅ Использование
st.columns()с процентным соотношением - ✅
gap="small"для минимального отступа - ✅
use_container_width=Trueдля заполнения колонок - ✅ Подробные комментарии на русском языке
- 🎯 Иконки всегда на одном уровне
- 📏 Стабильное выравнивание при любой ширине
- 🚀 Без кастомного CSS — только Streamlit
- 📖 Понятный и поддерживаемый код
Проблема решена полностью! 🎉