ui: Make toggle button group responsive (#36100)

Finn Evers created

This PR improves the toggle button group to be more responsive across
different layouts. This is accomplished by ensuring each button takes up
the same amount of space in the parent containers layout.

Ideally, this should be done with grids instead of a flexbox container,
as this would be much better suited for this purpose. Yet, since we lack
support for this, we go with this route for now.

| Before | After |
| --- | --- |
| <img width="1608" height="1094" alt="Bildschirmfoto 2025-08-13 um 11
24 26"
src="https://github.com/user-attachments/assets/2a4b5a59-6483-4f79-8fcb-e26e22071795"
/> | <img width="1608" height="1094" alt="Bildschirmfoto 2025-08-13 um
11 29 36"
src="https://github.com/user-attachments/assets/e6402729-6a8f-4a44-b79e-a569406edfff"
/> |


Release Notes:

- N/A

Change summary

crates/editor/src/element.rs                     |  4 
crates/onboarding/src/basics_page.rs             |  4 
crates/onboarding/src/editing_page.rs            |  2 
crates/repl/src/notebook/notebook_ui.rs          |  2 
crates/ui/src/components/button/button.rs        |  2 
crates/ui/src/components/button/button_like.rs   |  4 
crates/ui/src/components/button/icon_button.rs   |  4 
crates/ui/src/components/button/toggle_button.rs | 60 +++++++++++------
crates/ui/src/traits/fixed.rs                    |  2 
crates/zed/src/zed/quick_action_bar/repl_menu.rs |  2 
10 files changed, 50 insertions(+), 36 deletions(-)

Detailed changes

crates/editor/src/element.rs 🔗

@@ -3011,7 +3011,7 @@ impl EditorElement {
                     .icon_color(Color::Custom(cx.theme().colors().editor_line_number))
                     .selected_icon_color(Color::Custom(cx.theme().colors().editor_foreground))
                     .icon_size(IconSize::Custom(rems(editor_font_size / window.rem_size())))
-                    .width(width.into())
+                    .width(width)
                     .on_click(move |_, window, cx| {
                         editor.update(cx, |editor, cx| {
                             editor.expand_excerpt(excerpt_id, direction, window, cx);
@@ -3627,7 +3627,7 @@ impl EditorElement {
                                     ButtonLike::new("toggle-buffer-fold")
                                         .style(ui::ButtonStyle::Transparent)
                                         .height(px(28.).into())
-                                        .width(px(28.).into())
+                                        .width(px(28.))
                                         .children(toggle_chevron_icon)
                                         .tooltip({
                                             let focus_handle = focus_handle.clone();

crates/onboarding/src/basics_page.rs 🔗

@@ -58,7 +58,7 @@ fn render_theme_section(tab_index: &mut isize, cx: &mut App) -> impl IntoElement
                 .tab_index(tab_index)
                 .selected_index(theme_mode as usize)
                 .style(ui::ToggleButtonGroupStyle::Outlined)
-                .button_width(rems_from_px(64.)),
+                .width(rems_from_px(3. * 64.)),
             ),
         )
         .child(
@@ -305,8 +305,8 @@ fn render_base_keymap_section(tab_index: &mut isize, cx: &mut App) -> impl IntoE
         .when_some(base_keymap, |this, base_keymap| {
             this.selected_index(base_keymap)
         })
+        .full_width()
         .tab_index(tab_index)
-        .button_width(rems_from_px(216.))
         .size(ui::ToggleButtonGroupSize::Medium)
         .style(ui::ToggleButtonGroupStyle::Outlined),
     );

crates/onboarding/src/editing_page.rs 🔗

@@ -706,7 +706,7 @@ fn render_popular_settings_section(
                     })
                     .tab_index(tab_index)
                     .style(ToggleButtonGroupStyle::Outlined)
-                    .button_width(ui::rems_from_px(64.)),
+                    .width(ui::rems_from_px(3. * 64.)),
                 ),
         )
 }

crates/repl/src/notebook/notebook_ui.rs 🔗

@@ -295,7 +295,7 @@ impl NotebookEditor {
         _cx: &mut Context<Self>,
     ) -> IconButton {
         let id: ElementId = ElementId::Name(id.into());
-        IconButton::new(id, icon).width(px(CONTROL_SIZE).into())
+        IconButton::new(id, icon).width(px(CONTROL_SIZE))
     }
 
     fn render_notebook_controls(

crates/ui/src/components/button/button.rs 🔗

@@ -324,7 +324,7 @@ impl FixedWidth for Button {
     /// ```
     ///
     /// This sets the button's width to be exactly 100 pixels.
-    fn width(mut self, width: DefiniteLength) -> Self {
+    fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
         self.base = self.base.width(width);
         self
     }

crates/ui/src/components/button/button_like.rs 🔗

@@ -499,8 +499,8 @@ impl Clickable for ButtonLike {
 }
 
 impl FixedWidth for ButtonLike {
-    fn width(mut self, width: DefiniteLength) -> Self {
-        self.width = Some(width);
+    fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
+        self.width = Some(width.into());
         self
     }
 

crates/ui/src/components/button/icon_button.rs 🔗

@@ -133,7 +133,7 @@ impl Clickable for IconButton {
 }
 
 impl FixedWidth for IconButton {
-    fn width(mut self, width: DefiniteLength) -> Self {
+    fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
         self.base = self.base.width(width);
         self
     }
@@ -194,7 +194,7 @@ impl RenderOnce for IconButton {
             .map(|this| match self.shape {
                 IconButtonShape::Square => {
                     let size = self.icon_size.square(window, cx);
-                    this.width(size.into()).height(size.into())
+                    this.width(size).height(size.into())
                 }
                 IconButtonShape::Wide => this,
             })

crates/ui/src/components/button/toggle_button.rs 🔗

@@ -1,6 +1,6 @@
 use std::rc::Rc;
 
-use gpui::{AnyView, ClickEvent};
+use gpui::{AnyView, ClickEvent, relative};
 
 use crate::{ButtonLike, ButtonLikeRounding, ElevationIndex, TintColor, Tooltip, prelude::*};
 
@@ -73,8 +73,8 @@ impl SelectableButton for ToggleButton {
 }
 
 impl FixedWidth for ToggleButton {
-    fn width(mut self, width: DefiniteLength) -> Self {
-        self.base.width = Some(width);
+    fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
+        self.base.width = Some(width.into());
         self
     }
 
@@ -429,7 +429,7 @@ where
     rows: [[T; COLS]; ROWS],
     style: ToggleButtonGroupStyle,
     size: ToggleButtonGroupSize,
-    button_width: Rems,
+    group_width: Option<DefiniteLength>,
     selected_index: usize,
     tab_index: Option<isize>,
 }
@@ -441,7 +441,7 @@ impl<T: ButtonBuilder, const COLS: usize> ToggleButtonGroup<T, COLS> {
             rows: [buttons],
             style: ToggleButtonGroupStyle::Transparent,
             size: ToggleButtonGroupSize::Default,
-            button_width: rems_from_px(100.),
+            group_width: None,
             selected_index: 0,
             tab_index: None,
         }
@@ -455,7 +455,7 @@ impl<T: ButtonBuilder, const COLS: usize> ToggleButtonGroup<T, COLS, 2> {
             rows: [first_row, second_row],
             style: ToggleButtonGroupStyle::Transparent,
             size: ToggleButtonGroupSize::Default,
-            button_width: rems_from_px(100.),
+            group_width: None,
             selected_index: 0,
             tab_index: None,
         }
@@ -473,11 +473,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> ToggleButtonGroup<T
         self
     }
 
-    pub fn button_width(mut self, button_width: Rems) -> Self {
-        self.button_width = button_width;
-        self
-    }
-
     pub fn selected_index(mut self, index: usize) -> Self {
         self.selected_index = index;
         self
@@ -491,6 +486,24 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> ToggleButtonGroup<T
         *tab_index += (COLS * ROWS) as isize;
         self
     }
+
+    const fn button_width() -> DefiniteLength {
+        relative(1. / COLS as f32)
+    }
+}
+
+impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> FixedWidth
+    for ToggleButtonGroup<T, COLS, ROWS>
+{
+    fn width(mut self, width: impl Into<DefiniteLength>) -> Self {
+        self.group_width = Some(width.into());
+        self
+    }
+
+    fn full_width(mut self) -> Self {
+        self.group_width = Some(relative(1.));
+        self
+    }
 }
 
 impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
@@ -511,6 +524,7 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
                     let entry_index = row_index * COLS + col_index;
 
                     ButtonLike::new((self.group_name, entry_index))
+                        .full_width()
                         .rounding(None)
                         .when_some(self.tab_index, |this, tab_index| {
                             this.tab_index(tab_index + entry_index as isize)
@@ -527,7 +541,7 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
                         })
                         .child(
                             h_flex()
-                                .min_w(self.button_width)
+                                .w_full()
                                 .gap_1p5()
                                 .px_3()
                                 .py_1()
@@ -561,6 +575,13 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
         let is_transparent = self.style == ToggleButtonGroupStyle::Transparent;
 
         v_flex()
+            .map(|this| {
+                if let Some(width) = self.group_width {
+                    this.w(width)
+                } else {
+                    this.w_full()
+                }
+            })
             .rounded_md()
             .overflow_hidden()
             .map(|this| {
@@ -583,6 +604,8 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> RenderOnce
                             .when(is_outlined_or_filled && !last_item, |this| {
                                 this.border_r_1().border_color(border_color)
                             })
+                            .w(Self::button_width())
+                            .overflow_hidden()
                             .child(item)
                     }))
             }))
@@ -630,7 +653,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
                                 ],
                             )
                             .selected_index(1)
-                            .button_width(rems_from_px(100.))
                             .into_any_element(),
                         ),
                         single_example(
@@ -656,7 +678,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
                                 ],
                             )
                             .selected_index(1)
-                            .button_width(rems_from_px(100.))
                             .into_any_element(),
                         ),
                         single_example(
@@ -675,7 +696,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
                                 ],
                             )
                             .selected_index(3)
-                            .button_width(rems_from_px(100.))
                             .into_any_element(),
                         ),
                         single_example(
@@ -718,7 +738,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
                                 ],
                             )
                             .selected_index(3)
-                            .button_width(rems_from_px(100.))
                             .into_any_element(),
                         ),
                     ],
@@ -763,7 +782,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
                                 ],
                             )
                             .selected_index(1)
-                            .button_width(rems_from_px(100.))
                             .style(ToggleButtonGroupStyle::Outlined)
                             .into_any_element(),
                         ),
@@ -783,7 +801,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
                                 ],
                             )
                             .selected_index(3)
-                            .button_width(rems_from_px(100.))
                             .style(ToggleButtonGroupStyle::Outlined)
                             .into_any_element(),
                         ),
@@ -827,7 +844,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
                                 ],
                             )
                             .selected_index(3)
-                            .button_width(rems_from_px(100.))
                             .style(ToggleButtonGroupStyle::Outlined)
                             .into_any_element(),
                         ),
@@ -873,7 +889,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
                                 ],
                             )
                             .selected_index(1)
-                            .button_width(rems_from_px(100.))
                             .style(ToggleButtonGroupStyle::Filled)
                             .into_any_element(),
                         ),
@@ -893,7 +908,7 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
                                 ],
                             )
                             .selected_index(3)
-                            .button_width(rems_from_px(100.))
+                            .width(rems_from_px(100.))
                             .style(ToggleButtonGroupStyle::Filled)
                             .into_any_element(),
                         ),
@@ -937,7 +952,7 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
                                 ],
                             )
                             .selected_index(3)
-                            .button_width(rems_from_px(100.))
+                            .width(rems_from_px(100.))
                             .style(ToggleButtonGroupStyle::Filled)
                             .into_any_element(),
                         ),
@@ -957,7 +972,6 @@ impl<T: ButtonBuilder, const COLS: usize, const ROWS: usize> Component
                         ],
                     )
                     .selected_index(1)
-                    .button_width(rems_from_px(100.))
                     .into_any_element(),
                 )])
                 .into_any_element(),

crates/ui/src/traits/fixed.rs 🔗

@@ -3,7 +3,7 @@ use gpui::DefiniteLength;
 /// A trait for elements that can have a fixed with. Enables the use of the `width` and `full_width` methods.
 pub trait FixedWidth {
     /// Sets the width of the element.
-    fn width(self, width: DefiniteLength) -> Self;
+    fn width(self, width: impl Into<DefiniteLength>) -> Self;
 
     /// Sets the element's width to the full width of its container.
     fn full_width(self) -> Self;

crates/zed/src/zed/quick_action_bar/repl_menu.rs 🔗

@@ -216,7 +216,7 @@ impl QuickActionBar {
                             .size(IconSize::XSmall)
                             .color(Color::Muted),
                     )
-                    .width(rems(1.).into())
+                    .width(rems(1.))
                     .disabled(menu_state.popover_disabled),
                 Tooltip::text("REPL Menu"),
             );