egui icon indicating copy to clipboard operation
egui copied to clipboard

ComboBox, DragValue, and Button not aligned properly in a horizontal layout

Open J2ane opened this issue 4 months ago • 5 comments

🌟Problem Summary I'm facing a layout issue while using egui for building a UI.

Inside the CentralPanel, I used ui.horizontal(|ui| { ... }) to lay out several components in a single row, including:

Multiple ComboBoxes

Multiple DragValue inputs

Some Buttons

These components are not properly aligned in a single horizontal row — they appear at different vertical positions and look visually unbalanced, especially when the font size is large.

✅What I want to achieve I want all these widgets to be aligned neatly in a single row, with consistent height and baseline alignment, so they appear clean and well-organized.

`[package] name = "test0" version = "0.1.0" edition = "2024"

[dependencies] eframe = "0.32.0" egui = "0.32.0" `

` use eframe::{egui, Result};

fn fontsize_setting(ctx: &egui::Context, size: f32) { let mut style = (*ctx.style()).clone();

style.text_styles = [
    (egui::TextStyle::Heading, egui::FontId::new(size, egui::FontFamily::Proportional)),
    (egui::TextStyle::Body, egui::FontId::new(size, egui::FontFamily::Proportional)),
    (egui::TextStyle::Monospace, egui::FontId::new(size, egui::FontFamily::Monospace)),
    (egui::TextStyle::Button, egui::FontId::new(size, egui::FontFamily::Proportional)),
    (egui::TextStyle::Small, egui::FontId::new(size, egui::FontFamily::Proportional)),
].into();

ctx.set_style(style);

}

struct MyApp{ n_vec: Vec<String>, n_name: String, n_min: u32, }

impl Default for MyApp{ fn default() -> Self{ Self{ n_vec: vec!["".to_string()], n_name: "".to_string(), n_min: 0, } } }

impl eframe::App for MyApp{ fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { fontsize_setting(ctx, 30.0);

    egui::CentralPanel::default().show(ctx, |ui|{
        ui.horizontal(|ui|{
            egui::ComboBox::from_id_salt("C_1")
                .selected_text(&self.n_name)
                .show_ui(ui, |ui|{
                    for i in &self.n_vec{
                        ui.selectable_value(&mut self.n_name, i.clone(), i);
                    }
                });

            ui.add(|ui: &mut egui::Ui|{
                let response = ui.button("+").on_hover_text("C_1_b");
                if response.clicked() {
                    println!("+")
                }
                response
            });

            egui::ComboBox::from_id_salt("C_2")
                .selected_text(&self.n_name)
                .show_ui(ui, |ui|{
                    for i in &self.n_vec{
                        ui.selectable_value(&mut self.n_name, i.clone(), i);
                    }
                });

            ui.add(|ui: &mut egui::Ui|{
                let response = ui.button("+").on_hover_text("C_2_b");
                if response.clicked() {
                    println!("+")
                }
                response
            });

            ui.add(
            egui::DragValue::new(&mut self.n_min).range(0..=10000)
            ).on_hover_text("C_2_d");

            egui::ComboBox::from_id_salt("C_3")
                .selected_text(&self.n_name)
                .show_ui(ui, |ui|{
                    for i in &self.n_vec{
                        ui.selectable_value(&mut self.n_name, i.clone(), i);
                    }
                });

            ui.add(
            egui::DragValue::new(&mut self.n_min).range(0..=10000)
            ).on_hover_text("C_3_d");

            egui::ComboBox::from_id_salt("C_4")
                .selected_text(&self.n_name)
                .show_ui(ui, |ui|{
                    for i in &self.n_vec{
                        ui.selectable_value(&mut self.n_name, i.clone(), i);
                    }
                });

            ui.add(
            egui::DragValue::new(&mut self.n_min).range(0..=10000)
            ).on_hover_text("C_4_d");

            egui::ComboBox::from_id_salt("C_5")
                .selected_text(&self.n_name)
                .show_ui(ui, |ui|{
                    for i in &self.n_vec{
                        ui.selectable_value(&mut self.n_name, i.clone(), i);
                    }
                });

            ui.add(
            egui::DragValue::new(&mut self.n_min).range(0..=10000)
            ).on_hover_text("C_5_d");
        });
    });
}

}

fn main() -> Result<(), eframe::Error> { let option = eframe::NativeOptions::default(); eframe::run_native("test", option, Box::new(|_cc| Ok( Box::new(MyApp::default()) ))) }

`

Image

J2ane avatar Aug 04 '25 23:08 J2ane

The issue persists in v0.33.0. Any update on this?

tvdboom avatar Oct 14 '25 14:10 tvdboom

The vertical drift is an issue with using Ui::horizontal() without allocating some amount of space:

ui.horizontal(|ui| {
  ui.allocate_space([0., 100.].into());
   . . .
});

Normally allocating sufficient space resolves the alignment issues, but in your case the result would be similar to using Ui::horizontal_centered(): Image

The misaligned ComboBoxes are due to your use of TextStyles, as removing them prevents the both the misalignment and vertical drift: ImageImage

Using Ui::horizontal() without an allocation is usually better served by Ui::horizontal_top(), which in your case produces the result I presume you were expecting: Image

All that said, I think this still demonstrates a bug in the combination of a centered ComboBox and custom Style::text_styles (at least the ones you are using).

justincredible avatar Oct 18 '25 06:10 justincredible

How can I align images and comboboxes? Currently, the comboboxes are not centered and the images shift upwards, whether I use ui.horizontal or horizontal_centered, with and without ui.allocate_space([0., 100.].into());

Image

tvdboom avatar Oct 21 '25 10:10 tvdboom

You should compare the image alignment to a button/label as the combo box is not centered correctly. Outside of manual layout, I don't think there's a way to get the combo box working without a fix.

If you're willing to fork, you can use the below workaround. I will stress that this is just a bandage, but hopefully it points towards the root cause.

ComboBox alignment workaround
diff --git a/crates/egui/src/containers/combo_box.rs b/crates/egui/src/containers/combo_box.rs
index 8195024f..21cbf28d 100644
--- a/crates/egui/src/containers/combo_box.rs
+++ b/crates/egui/src/containers/combo_box.rs
@@ -235,6 +235,7 @@ impl ComboBox {

         let button_id = ui.make_persistent_id(id_salt);

+        let parent_horizontal_aligned = (ui.layout().is_horizontal(), ui.layout().cross_align);
         ui.horizontal(|ui| {
             let mut ir = combo_box_dyn(
                 ui,
@@ -246,6 +247,7 @@ impl ComboBox {
                 close_behavior,
                 popup_style,
                 (width, height),
+                parent_horizontal_aligned,
             );
             if let Some(label) = label {
                 ir.response.widget_info(|| {
@@ -329,6 +331,7 @@ fn combo_box_dyn<'c, R>(
     close_behavior: Option<PopupCloseBehavior>,
     popup_style: StyleModifier,
     (width, height): (Option<f32>, Option<f32>),
+    parent_horizontal_aligned: (bool, crate::Align),
 ) -> InnerResponse<Option<R>> {
     let popup_id = ComboBox::widget_to_popup_id(button_id);

@@ -339,7 +342,7 @@ fn combo_box_dyn<'c, R>(
     let close_behavior = close_behavior.unwrap_or(PopupCloseBehavior::CloseOnClick);

     let margin = ui.spacing().button_padding;
-    let button_response = button_frame(ui, button_id, is_popup_open, Sense::click(), |ui| {
+    let button_response = button_frame(ui, button_id, parent_horizontal_aligned, is_popup_open, Sense::click(), |ui| {
         let icon_spacing = ui.spacing().icon_spacing;
         let icon_size = Vec2::splat(ui.spacing().icon_width);

@@ -426,6 +429,7 @@ fn combo_box_dyn<'c, R>(
 fn button_frame(
     ui: &mut Ui,
     id: Id,
+    parent_horizontal_aligned: (bool, crate::Align),
     is_popup_open: bool,
     sense: Sense,
     add_contents: impl FnOnce(&mut Ui),
@@ -437,6 +441,17 @@ fn button_frame(

     let mut outer_rect = ui.available_rect_before_wrap();
     outer_rect.set_height(outer_rect.height().at_least(interact_size.y));
+    let row_height = ui.text_style_height(&TextStyle::Button);
+    if row_height > crate::FontId::default().size && parent_horizontal_aligned.0 {
+        let adjustment = match parent_horizontal_aligned.1 {
+            crate::Align::Min => 0.,
+            crate::Align::Center => f32::midpoint(row_height - interact_size.y, 2. * margin.y),
+            crate::Align::Max => row_height - interact_size.y + 2. * margin.y,
+        };
+        let mut center = outer_rect.center();
+        center.y -= adjustment;
+        outer_rect.set_center(center);
+    }

     let inner_rect = outer_rect.shrink2(margin);
     let mut content_ui = ui.new_child(UiBuilder::new().max_rect(inner_rect));

justincredible avatar Nov 02 '25 20:11 justincredible

I am also experiencing this issue using ui.horizontal_wrapped and generating egui::Frames in a loop. I was able to work around it by removing the .inner_margin() and .outer_margin() on the frame, and instead wrapped my content within the frame in a ui.horizontal_centered and a ui.vertical_centered in order to put padding on the left and right.

(This snippet won't run without the rest of my code, I just wanted to include a code example of the workaround)

ui.horizontal_wrapped(|ui| {
            let block_min_size = egui::Vec2::new(200.0, 250.0);

            for option in options {
                let is_selected = *current_selection == option;

                let frame_inner_response = ui.allocate_ui_with_layout(
                    block_min_size,
                    egui::Layout::top_down(egui::Align::LEFT),
                    |block_ui| {
                        egui::Frame::default()
                            .fill(if is_selected { Color32::from_rgb(40, 60, 80) } else { block_ui.visuals().panel_fill })
                            .stroke(Stroke::new(1.5, Color32::from_rgb(55,55,55)))
                            .corner_radius(egui::CornerRadius::same(5))
                            // .outer_margin(5) // <- Commenting out these lines works around the issue
                            // .inner_margin(5)
                            .show(block_ui, |ui| {
                                // Enforce the size inside the frame
                                ui.set_min_size(block_min_size);
                                ui.set_max_size(block_min_size);

                                // This `horizontal_centered` is to add padding whilst working around the bug.
                                ui.horizontal_centered(|ui| {
                                    ui.add_space(5.);
                                    ui.vertical_centered(|ui| {
                                        ui.add_space(5.0);
                                        option.show_block(ui); // Function to draw most of the content in the box, not shown here.
                                        ui.label(egui::RichText::new(option.name()).strong().size(14.0));
                                        ui.add_space(5.0);
                                    });
                                    ui.add_space(3.);
                                });
                            });
                    }
                );
            }
        });

Bug present:

Image

Bug worked around:

Image

jrh4567 avatar Nov 23 '25 21:11 jrh4567