git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commitcd6d82a668f152842582c5da8a8b55424a8dbce2
parent6ba0c6c23b
authorLucas Galante <[email protected]>
date2026-09-08 18:09
feat(label)!: one control-label convention, no label-in-recess tabs

Every labeled control now carries its label the same way: the adapter
draws it detached in the strip above the content rect, `control_label_strip`
tall (font size + margin, the one formula), at `DETACHED_LABEL_INSET`,
outside the control's own relief. The two places that pulled the label
INTO the control's recess — the Dropdown trigger's carve-out tab and the
Slider2D pad's shared `carve_labeled_well` — are gone: the plate or well is
the control alone (Slider2D is a plain 64px pad again).

The conventions that let labels differ per widget go with it:

- `Layout::inflates_label_rect` and the adapter's `label_inflation`: a
  widget's rect is always its whole block (label strip + content), for
  `set_rect`, `rect`, hit-testing and paint alike. `layout` lands the
  content at the origin with the strip above; `render_widget` and the
  Column/Row/Section builders speak the same block; `LayoutHelper` too.
- The "side" label layout (`style.control.label.layout`) and the 90px
  `label_x_offset` it drove, in the trait, the painter, ParametersBg and
  every model's `side_offset` replica.
- `widget::label_offset(w)` and its type-name exemption list: callers use
  `WidgetHost::label_strip`, whose default is the base label's strip.
- Trackpad's model-drawn label (it rides the adapter now, like the rest).
- The per-model strip replicas (Dropdown, TextBox, Float3, Ramp, Slider2D)
  collapse onto `slider::detached_strip` / `layout::control_label_strip`.

`Layout::inline_label` stays for widgets whose base label IS their content
(Button face, Checkbox row, Label, ListItem, InfoBox, StatusBar).

Co-Authored-By: Claude Fable 5.1 <[email protected]>

 src/backend/window_runner.rs          |   8 +-
 src/layout.rs                         |  42 ++++-----
 src/scene/painter.rs                  |   7 --
 src/widget/container/parameters_bg.rs |  39 ++++-----
 src/widget/core.rs                    |  13 +--
 src/widget/display/float3.rs          |  45 ++--------
 src/widget/display/progress_bar.rs    |  15 ++--
 src/widget/input/button_strip.rs      |   2 +-
 src/widget/input/color_selector.rs    |   6 +-
 src/widget/input/dropdown.rs          | 158 ++++------------------------------
 src/widget/input/ramp.rs              |   6 +-
 src/widget/input/slider.rs            | 156 +++------------------------------
 src/widget/input/slider2d.rs          |  48 ++---------
 src/widget/input/spinbox.rs           |  21 +----
 src/widget/input/text_box.rs          |  83 ++++++------------
 src/widget/input/trackpad.rs          |  51 ++---------
 src/widget/layout_helper.rs           |  12 +--
 src/widget/mod.rs                     |  44 +++-------
 src/widget/model.rs                   |  84 ++++++------------
 19 files changed, 174 insertions(+), 666 deletions(-)

diff --git a/src/backend/window_runner.rs b/src/backend/window_runner.rs
index 3d4055e..e4df153 100644
--- a/src/backend/window_runner.rs
+++ b/src/backend/window_runner.rs
@@ -2787,7 +2787,7 @@ pub fn push_extra_quad_vertices(
     }
 
     let (wx, mut wy, ww, mut wh) = target_w.rect();
-    let top_room = crate::widget::label_offset(target_w);
+    let top_room = target_w.label_strip();
     wy += top_room;
     wh -= top_room;
     let extra_radii = crate::widget::CornerRadii::new(
@@ -2801,7 +2801,7 @@ pub fn push_extra_quad_vertices(
 
     if let Some((color, thickness)) = target_w.solid_border() {
         let (rx, mut ry, rw, mut rh) = target_w.rect();
-        let top = crate::widget::label_offset(target_w);
+        let top = target_w.label_strip();
         ry += top;
         rh -= top;
         if (qx - rx).abs() < 0.1 && (qy - ry).abs() < 0.1 && (qw - rw).abs() < 0.1 && (qh - rh).abs() < 0.1 {
@@ -2863,7 +2863,7 @@ pub fn push_extra_quad_vertices_clipped(
     }
 
     let (wx, mut wy, ww, mut wh) = target_w.rect();
-    let top_room = crate::widget::label_offset(target_w);
+    let top_room = target_w.label_strip();
     wy += top_room;
     wh -= top_room;
     let extra_radii = crate::widget::CornerRadii::new(
@@ -2877,7 +2877,7 @@ pub fn push_extra_quad_vertices_clipped(
 
     if let Some((color, thickness)) = target_w.solid_border() {
         let (rx, mut ry, rw, mut rh) = target_w.rect();
-        let top = crate::widget::label_offset(target_w);
+        let top = target_w.label_strip();
         ry += top;
         rh -= top;
         if (qx - rx).abs() < 0.1 && (qy - ry).abs() < 0.1 && (qw - rw).abs() < 0.1 && (qh - rh).abs() < 0.1 {
diff --git a/src/layout.rs b/src/layout.rs
index 855bf50..10230e2 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -86,7 +86,6 @@ fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props:
                 "style.control.label.font" => "control_label_font",
                 "style.control.label.font_detached" => "control_label_font_detached",
                 "style.control.label.margin" => "control_label_margin",
-                "style.control.label.layout" => "control_label_layout",
                 "style.control.slider.height" => "slider_height",
                 "style.control.slider.corner_radius" => "slider_corner_radius",
                 "style.control.slider.band_thickness" => "slider_band_thickness",
@@ -389,7 +388,6 @@ static CONTROL_LABEL_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(No
 static CONTROL_LABEL_FONT_DETACHED: RwLock<String> = RwLock::new(String::new());
 static CONTROL_LABEL_FONT_DETACHED_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
 static CONTROL_LABEL_MARGIN: RwLock<f32> = RwLock::new(6.0);
-static CONTROL_LABEL_LAYOUT: RwLock<String> = RwLock::new(String::new());
 static PLATE_CORNER_RADIUS: RwLock<f32> = RwLock::new(12.0);
 static LIST_FONT: RwLock<String> = RwLock::new(String::new());
 static LIST_FONT_CACHED: RwLock<Option<(String, f32)>> = RwLock::new(None);
@@ -464,13 +462,6 @@ pub fn reload_config() {
                     }
                 }
             }
-            if let Some(rest) = trimmed.strip_prefix("control_label_layout") {
-                let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
-                let val_str = rest.trim_end_matches('"').trim().to_string();
-                if let Ok(mut lock) = CONTROL_LABEL_LAYOUT.write() {
-                    *lock = val_str;
-                }
-            }
             if let Some(rest) = trimmed.strip_prefix("nested_section_label_alignment") {
                 let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
                 let val_str = rest.trim_end_matches('"').trim();
@@ -1228,13 +1219,9 @@ pub fn control_label_margin() -> f32 {
     *CONTROL_LABEL_MARGIN.read().unwrap()
 }
 
-pub fn control_label_layout() -> String {
-    let lock = CONTROL_LABEL_LAYOUT.read().unwrap();
-    if lock.is_empty() {
-        "top".to_string()
-    } else {
-        lock.clone()
-    }
+pub(crate) fn control_label_strip() -> f32 {
+    let (_, font_size) = control_label_font_detached_parsed();
+    font_size + control_label_margin()
 }
 
 pub fn label_margin() -> f32 {
@@ -3682,11 +3669,12 @@ pub fn render_widget<T: WidgetHost + 'static>(pc: &mut dyn RenderTarget, w: &mut
     if let Some(w_id) = id {
         ctx.register_widget(w_id, w as *mut T as *mut (dyn WidgetHost + 'static));
     }
-    // The legacy contract: `y` is the top of the detached label, `wh` the content
-    // height below it. `layout` takes the CONTENT origin (the label hangs above it),
-    // so step down by the strip.
+    // The flat-host contract, the same block `set_rect` takes: `(x, y)` is the top of
+    // the detached label and `wh` the block height, label strip included. `layout`
+    // takes the CONTENT origin and height, so step down by the strip.
     let strip = w.label_strip();
-    w.layout(crate::widget::Point { x, y: y + strip }, crate::widget::LayoutConstraints::new(ww, ww, wh, wh), ctx);
+    let content_h = (wh - strip).max(0.0);
+    w.layout(crate::widget::Point { x, y: y + strip }, crate::widget::LayoutConstraints::new(ww, ww, content_h, content_h), ctx);
 
     // Shape, which on this path nobody else does. A flat host consumes
     // `all_quads`, so `prepare_text` — where a TextBox records the per-glyph x
@@ -3703,7 +3691,7 @@ pub fn render_widget<T: WidgetHost + 'static>(pc: &mut dyn RenderTarget, w: &mut
     let (style_r, corners) = w.corner_style();
     let r = if corners != (false, false, false, false) { style_r } else { 0.0 };
     let (wx, mut wy, www, mut whh) = w.rect();
-    let top_room = crate::widget::label_offset(w);
+    let top_room = w.label_strip();
     wy += top_room;
     whh -= top_room;
 
@@ -4053,7 +4041,7 @@ impl Column {
         if let Some(pref) = w.preferred_height() {
             wh = pref;
         }
-        let top_room = crate::widget::label_offset(w);
+        let top_room = w.label_strip();
         let total_h = wh + top_room;
         let x = self.ax(x_off);
         let y = self.ay();
@@ -4234,7 +4222,7 @@ impl Section {
             wh = pref;
         }
         let pad = self.padding();
-        let top_room = crate::widget::label_offset(w);
+        let top_room = w.label_strip();
         let total_h = wh + top_room;
 
         let name = w.type_name();
@@ -5426,7 +5414,7 @@ impl<'a, P: RenderTarget> SectionContext<'a, P> {
             wh = pref;
         }
         let pad = self.padding();
-        let top_room = crate::widget::label_offset(w);
+        let top_room = w.label_strip();
         let total_h = wh + top_room;
 
         let name = w.type_name();
@@ -5682,7 +5670,7 @@ impl<'b, 'a, P: RenderTarget> VStack<'b, 'a, P> {
         let y = max_h;
 
         let pref_h = w.preferred_height().unwrap_or(wh);
-        let top_room = crate::widget::label_offset(w);
+        let top_room = w.label_strip();
         let total_h = pref_h + top_room;
 
         w.set_row_rect(self.context.left + pad, self.context.cw - 2.0 * pad);
@@ -5856,11 +5844,11 @@ mod tests {
     impl WidgetHost for MockWidgetWithLabel {
         crate::impl_widget_base!(MockWidgetWithLabel);
         fn rect(&self) -> (f32, f32, f32, f32) {
-            let offset = crate::widget::label_offset(self);
+            let offset = self.label_strip();
             (self.base.x, self.base.y - offset, self.base.w, self.base.h + offset)
         }
         fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
-            let offset = crate::widget::label_offset(self);
+            let offset = self.label_strip();
             self.base.x = x;
             self.base.y = y + offset;
             self.base.w = w;
diff --git a/src/scene/painter.rs b/src/scene/painter.rs
index 500a8ee..21314f8 100644
--- a/src/scene/painter.rs
+++ b/src/scene/painter.rs
@@ -143,13 +143,6 @@ pub fn base_control_label(w: &dyn WidgetHost) -> Vec<TextLabel> {
         if let Some(ref label) = b.label {
             let (_, font_size) = crate::layout::control_label_font_detached_parsed();
             let color = crate::colors::control_label_color_detached_for_state(b.hovered, b.focused);
-            if crate::layout::control_label_layout() == "side" {
-                let label_x = w.label_x_offset();
-                if label_x > 0.0 {
-                    let y_pos = crate::layout::align_text_y(b.y, b.h, font_size, 0.0);
-                    return vec![TextLabel { text: label.clone(), x: b.x + 4.0, y: y_pos, font_size, color }];
-                }
-            }
             return vec![TextLabel { text: label.clone(), x: b.x, y: b.y, font_size, color }];
         }
     }
diff --git a/src/widget/container/parameters_bg.rs b/src/widget/container/parameters_bg.rs
index 13254e0..d84c30c 100644
--- a/src/widget/container/parameters_bg.rs
+++ b/src/widget/container/parameters_bg.rs
@@ -613,12 +613,7 @@ impl ParametersBg {
                     // only the interior left edge. A ring encapsulated
                     // within the bevel doubled the valley on the adjoining
                     // sides.
-                    let label_top = if crate::layout::control_label_layout() == "side" {
-                        0.0
-                    } else {
-                        crate::layout::control_label_font_detached_parsed().1
-                            + crate::layout::control_label_margin()
-                    };
+                    let label_top = crate::layout::control_label_strip();
                     let band_h = r.3 - label_top;
                     let inset = crate::layout::bevel_width().min(band_h * 0.2);
                     let by = r.1 + label_top + inset;
@@ -1194,7 +1189,7 @@ impl ParametersBg {
                 if let Some(t) = &self.toggles[i] {
                     let (x, y, w, h) = t.rect();
                     if w > 0.0 && h > 0.0 {
-                        let ty = crate::widget::label_offset(t);
+                        let ty = t.label_strip();
                         let rect = Rect { x, y: y + ty, width: w, height: h - ty };
                         for c in t.inner().flat_carves(rect) {
                             let raised = match c.kind {
@@ -1222,7 +1217,7 @@ impl ParametersBg {
                 // a fill with its own lit edge and stays on the widget's paint.
                 if let Some(c) = &self.colors[i] {
                     let (x, y, w, h) = c.rect();
-                    let ty = crate::widget::label_offset(c);
+                    let ty = c.label_strip();
                     if let Some((rx, ry, rw, rh, rr, rd)) =
                         c.inner().field_relief(Rect { x, y: y + ty, width: w, height: h - ty })
                     {
@@ -1241,13 +1236,10 @@ impl ParametersBg {
                 if ww <= 0.0 || h <= 0.0 {
                     continue;
                 }
-                // The side-label inset the widget's own paint applies (0 for the
-                // exempt kinds — button, toggle), and the top-label band, which
-                // stays outside the relief like every other host.
-                let lx = w.label_x_offset();
-                let ty = crate::widget::label_offset(w);
+                // The top-label band stays outside the relief like every other host.
+                let ty = w.label_strip();
                 let depth = crate::layout::bevel_width().min((h - ty) * 0.2);
-                out.push((x + lx, y + ty, ww - lx, h - ty, r4(radius), depth, raised, all));
+                out.push((x, y + ty, ww, h - ty, r4(radius), depth, raised, all));
             }
         }
         out
@@ -1283,7 +1275,7 @@ impl ParametersBg {
                 if let (Some(d), Some(tb)) = (&self.choices[i], &self.texts[i]) {
                     let (bx, by, bw, bh) = d.rect();
                     let (_, _, _, th) = tb.rect();
-                    let ty = crate::widget::label_offset(tb);
+                    let ty = tb.label_strip();
                     if bw > 0.0 && bh > 0.0 {
                         let depth = crate::layout::bevel_width().min((th - ty) * 0.2);
                         let r = (crate::layout::textbox_corner_radius() - depth).max(2.0);
@@ -1294,7 +1286,7 @@ impl ParametersBg {
             } else if p.2.starts_with("spinbox") {
                 if let Some(sb) = &self.spinboxes[i] {
                     let (x, y, w, h) = sb.rect();
-                    let ty = crate::widget::label_offset(sb);
+                    let ty = sb.label_strip();
                     let band = Rect { x, y: y + ty, width: w, height: h - ty };
                     if let Some((_, Some(((run, radii, rd, edges), _)))) =
                         sb.inner().relief_parts(band)
@@ -1305,18 +1297,17 @@ impl ParametersBg {
             } else if p.2.starts_with("choice") {
                 // The dropdown trigger: the widget's own raised paint is one
                 // `inset_plate` on its content band — the same ring here, on
-                // the same band (side-label inset, top-label band excluded),
+                // the same band (top-label band excluded),
                 // same radius, same depth cap. The face stays the plate: the
                 // pane carries no dropdown fill (a `Border` face never reaches
                 // the rounded-quad view), so the trigger is flush and bare.
                 if let Some(d) = &self.choices[i] {
                     let (x, y, w, h) = d.rect();
                     if w > 0.0 && h > 0.0 {
-                        let lx = d.label_x_offset();
-                        let ty = crate::widget::label_offset(d);
+                        let ty = d.label_strip();
                         let depth = crate::layout::bevel_width().min((h - ty) * 0.2);
                         let r = crate::layout::dropdown_corner_radius();
-                        out.push((x + lx, y + ty, w - lx, h - ty, (r, r, r, r), depth, (true, true, true, true)));
+                        out.push((x, y + ty, w, h - ty, (r, r, r, r), depth, (true, true, true, true)));
                     }
                 }
             } else if p.2 == "toggle" || p.2 == "checkbox" {
@@ -1326,7 +1317,7 @@ impl ParametersBg {
                 if let Some(t) = &self.toggles[i] {
                     let (x, y, w, h) = t.rect();
                     if w > 0.0 && h > 0.0 {
-                        let ty = crate::widget::label_offset(t);
+                        let ty = t.label_strip();
                         let rect = Rect { x, y: y + ty, width: w, height: h - ty };
                         for c in t.inner().flat_carves(rect) {
                             if matches!(c.kind, crate::layout::CarveKind::Trough) {
@@ -1358,7 +1349,7 @@ impl ParametersBg {
             }
             if let Some(sb) = &self.spinboxes[i] {
                 let (x, y, w, h) = sb.rect();
-                let ty = crate::widget::label_offset(sb);
+                let ty = sb.label_strip();
                 let band = Rect { x, y: y + ty, width: w, height: h - ty };
                 if let Some((_, Some(((_, _, rd, _), (sa, sb2, sw, host))))) =
                     sb.inner().relief_parts(band)
@@ -1675,7 +1666,7 @@ impl Input for ParametersBg {
             if p.2.starts_with("slider") {
                 let r = rects[i];
                 if let Some(s) = &mut self.sliders[i] {
-                    let top = crate::widget::label_offset(s);
+                    let top = s.label_strip();
                     if py >= r.1 + top && py <= r.1 + r.3 {
                         s.drag_begin(px, py);
                         self.dragging_param = Some(i);
@@ -2584,7 +2575,7 @@ impl Input for ParametersBg {
                             let latched = !ui.scroll_gesture_new
                                 && ui.scroll_initiate_widget_id == Some(s.base().id());
                             let (sx, sy, sw, sh) = s.rect();
-                            let ty = crate::widget::label_offset(s);
+                            let ty = s.label_strip();
                             latched
                                 || s.inner().scroll_hit(
                                     Rect { x: sx, y: sy + ty, width: sw, height: sh - ty },
diff --git a/src/widget/core.rs b/src/widget/core.rs
index 93834a1..a2ba8b1 100644
--- a/src/widget/core.rs
+++ b/src/widget/core.rs
@@ -740,16 +740,11 @@ impl Widget {
         }
     }
 
+    /// The detached-label strip this widget carries above its content: the one
+    /// control-label formula (`layout::control_label_strip`) when a label is set,
+    /// zero otherwise.
     pub fn label_offset(&self) -> f32 {
-        if crate::layout::control_label_layout() == "side" {
-            return 0.0;
-        }
-        if self.label.is_some() {
-            let (_, font_size) = crate::layout::control_label_font_detached_parsed();
-            font_size + crate::layout::control_label_margin()
-        } else {
-            0.0
-        }
+        if self.label.is_some() { crate::layout::control_label_strip() } else { 0.0 }
     }
 }
 
diff --git a/src/widget/display/float3.rs b/src/widget/display/float3.rs
index 8bd82b8..bb0a283 100644
--- a/src/widget/display/float3.rs
+++ b/src/widget/display/float3.rs
@@ -1,8 +1,7 @@
 //! Narrow-trait `Float3` — a labeled group of three STANDARD [`Slider`]s (X/Y/Z), each with
 //! the toolkit's readout, embedded by value inside `ParametersBg` (its only consumer), which
 //! drives it through direct `WidgetHost` calls. The group label is the ordinary detached
-//! control label (the adapter's, exactly like a slider row's — `inflates_label_rect = false`,
-//! the label eats into the assigned rect); below it sit three `Adapted<Slider>` children in
+//! control label (the adapter's, exactly like a slider row's); below it sit three `Adapted<Slider>` children in
 //! whatever style the DE config gives every other slider (the band that swallowed the rodent,
 //! the recessed well, the square track), each fronted by its axis letter. The model caches its
 //! laid-out rect ([`Layout::rect_assigned`]) and lays the children out from it; paint and input
@@ -52,15 +51,9 @@ impl Float3 {
         })
     }
 
-    /// The height a labeled (`labeled`) group lays out to: the detached label band (none in
-    /// the side layout) plus three slider rows and their gaps — the row-height table entry.
+    /// The height a labeled (`labeled`) group lays out to: the detached label band plus three slider rows and their gaps — the row-height table entry.
     pub fn preferred_height(labeled: bool) -> f32 {
-        let top = if labeled && crate::layout::control_label_layout() != "side" {
-            let (_, font_size) = crate::layout::control_label_font_detached_parsed();
-            font_size + crate::layout::control_label_margin()
-        } else {
-            0.0
-        };
+        let top = if labeled { crate::layout::control_label_strip() } else { 0.0 };
         top + 3.0 * crate::layout::slider_height() + 2.0 * ROW_GAP
     }
 
@@ -95,36 +88,18 @@ impl Float3 {
         &self.sliders
     }
 
-    /// Detached-label band above the rows — a replica of `Widget::label_offset` over the
-    /// synced label (zero in the side layout or unlabeled), the Slider's own formula.
+    /// Detached-label band above the rows (zero unlabeled) — the adapter's
+    /// `Widget::label_offset` over the synced label.
     fn label_top(&self) -> f32 {
-        if crate::layout::control_label_layout() == "side" {
-            return 0.0;
-        }
-        if self.label.is_some() {
-            let (_, font_size) = crate::layout::control_label_font_detached_parsed();
-            font_size + crate::layout::control_label_margin()
-        } else {
-            0.0
-        }
-    }
-
-    /// The side-layout label inset (`WidgetHost::label_x_offset` — this type is not exempt).
-    fn side_offset(&self) -> f32 {
-        if crate::layout::control_label_layout() == "side" && self.label.is_some() {
-            90.0
-        } else {
-            0.0
-        }
+        crate::widget::input::slider::detached_strip(&self.label)
     }
 
     /// The three slider rows' rects (`(x, y, w, h)`, X/Y/Z), laid out below the label band and
     /// right of the axis-letter column. Each is exactly the rect its sub-slider is assigned.
     pub fn get_row_rects(&self) -> Vec<(f32, f32, f32, f32)> {
         let top = self.rect.y + self.label_top();
-        let side = self.side_offset();
-        let x = self.rect.x + side + AXIS_W;
-        let w = (self.rect.width - side - AXIS_W).max(10.0);
+        let x = self.rect.x + AXIS_W;
+        let w = (self.rect.width - AXIS_W).max(10.0);
         let h = crate::layout::slider_height();
         (0..3).map(|i| (x, top + i as f32 * (h + ROW_GAP), w, h)).collect()
     }
@@ -179,10 +154,6 @@ impl Adapted<Float3> {
 impl Layout for Float3 {
     /// The Slider convention: the label eats into the assigned rect, the host sizes the row
     /// for it ([`Float3::preferred_height`]).
-    fn inflates_label_rect(&self) -> bool {
-        false
-    }
-
 
     /// The three rows alone: the adapter adds the detached-label strip itself
     /// (`Adapted::preferred_height`), as it does for every non-inflating widget.
diff --git a/src/widget/display/progress_bar.rs b/src/widget/display/progress_bar.rs
index 8280af6..9bee403 100644
--- a/src/widget/display/progress_bar.rs
+++ b/src/widget/display/progress_bar.rs
@@ -140,18 +140,19 @@ mod tests {
         }
     }
 
-    /// The detached-label convention survives the adapter: `set_rect` grows the widget by the
-    /// label offset, and painting is inset below the label region (config-independent: the
-    /// expected offset is derived from the observed rect).
+    /// The one detached-label convention: the assigned rect is the whole block, the
+    /// label strip at its top and the track painted below it (config-independent: the
+    /// strip is read back from the widget).
     #[test]
-    fn label_inflates_rect_and_insets_paint() {
+    fn label_strip_heads_the_block_and_insets_paint() {
         let ctx = UiContext::new();
         let mut bar = ProgressBar::new(0.5).with_recessed(false).with_label("Progress");
-        WidgetHost::set_rect(&mut bar, 0.0, 10.0, 100.0, 8.0);
+        let offset = WidgetHost::label_strip(&bar);
+        assert!(offset > 0.0, "a labeled bar carries a strip");
+        WidgetHost::set_rect(&mut bar, 0.0, 10.0, 100.0, 8.0 + offset);
 
         let (_, y, _, h) = WidgetHost::rect(&bar);
-        let offset = h - 8.0;
-        assert!(offset >= 0.0, "rect grew by the label offset");
+        assert_eq!(h, 8.0 + offset, "the rect is the block it was given");
         assert_eq!(y, 10.0, "origin is unchanged");
 
         let quads = WidgetHost::all_rounded_quads(&bar, &ctx);
diff --git a/src/widget/input/button_strip.rs b/src/widget/input/button_strip.rs
index 54d8451..81abe3d 100644
--- a/src/widget/input/button_strip.rs
+++ b/src/widget/input/button_strip.rs
@@ -69,7 +69,7 @@ impl ButtonStrip {
 
     /// The laid-out content rect: the rect mirrored from the adapter by
     /// `Layout::rect_assigned` (or the constructor arguments until the first layout)
-    /// less the detached label strip the adapter inflated it by, so the segments,
+    /// less the detached label strip at the top of that block, so the segments,
     /// the well and the hit-testing all sit below the label.
     fn rect(&self) -> (f32, f32, f32, f32) {
         let strip = crate::widget::input::slider::detached_strip(&self.label);
diff --git a/src/widget/input/color_selector.rs b/src/widget/input/color_selector.rs
index 6af8d99..8311104 100644
--- a/src/widget/input/color_selector.rs
+++ b/src/widget/input/color_selector.rs
@@ -226,9 +226,9 @@ impl Paint for ColorSelector {
     /// The legacy `extra_quads` body against the laid-out rect (field, caret while
     /// editing, and the soft-glow rounded color preview), plus the hex readout label.
     fn paint(&self, rect: Rect, ctx: &mut PaintCtx) {
-        // The well is color_selector_height tall, seated at the rect's TOP —
-        // the label rides above the rect (inflating-label convention) and the
-        // row's bottom band belongs to the NEXT row's label. Hosts that hand
+        // The well is color_selector_height tall, seated at the content rect's TOP —
+        // the label rides in the strip above it and the row's bottom band belongs
+        // to the NEXT row's label. Hosts that hand
         // over a whole param row (ParametersBg's 40px color rows) get a
         // standard control-height well instead of a row-tall one.
         let well_h = crate::layout::color_selector_height().min(rect.height);
diff --git a/src/widget/input/dropdown.rs b/src/widget/input/dropdown.rs
index a9a2582..d22a935 100644
--- a/src/widget/input/dropdown.rs
+++ b/src/widget/input/dropdown.rs
@@ -1,7 +1,7 @@
 //! Narrow-trait `Dropdown` (Phase 5p — first popover widget through `Paint::popover` /
-//! `draw_popover`, the 5o surface). Detached-label control on the Slider convention (no rect
-//! inflation; the label eats into the assigned rect), `Control::control_label`'s +4px inset via
-//! `Layout::detached_label_inset`, side-label inset computed from the synced label.
+//! `draw_popover`, the 5o surface). Detached-label control: the adapter draws the label in
+//! the strip above the trigger, at `Layout::detached_label_inset`; the trigger's plate is the
+//! control alone.
 //!
 //! Parity notes (all legacy-faithful, verified against the pre-migration impl):
 //! - `parent_snapshot` is the data form of the legacy public, direct-write-only `parent`
@@ -72,16 +72,6 @@ fn text_advance(text: &str, font_family: &str, font_size: f32) -> f32 {
     }
 }
 
-/// Side-layout label inset — the legacy `WidgetHost::label_x_offset` default for non-exempt
-/// widgets (Dropdown was never in the exempt list).
-fn side_offset(label: &Option<String>) -> f32 {
-    if crate::layout::control_label_layout() == "side" && label.is_some() {
-        90.0
-    } else {
-        0.0
-    }
-}
-
 #[derive(Debug, Clone)]
 pub struct Dropdown {
     pub options: Vec<String>,
@@ -224,18 +214,10 @@ impl Dropdown {
         text_advance(&self.display_text(), &font_family, font_size) + Self::LABEL_INSET
     }
 
-    /// The detached-label strip height above the content rect — a replica of
-    /// `Widget::label_offset` over the synced label (zero in side layout or unlabeled).
+    /// The detached-label strip height above the content rect (zero unlabeled) —
+    /// the adapter's `Widget::label_offset` over the synced label.
     fn label_top(&self) -> f32 {
-        if crate::layout::control_label_layout() == "side" {
-            return 0.0;
-        }
-        if self.label.is_some() {
-            let (_, font_size) = crate::layout::control_label_font_detached_parsed();
-            font_size + crate::layout::control_label_margin()
-        } else {
-            0.0
-        }
+        crate::widget::input::slider::detached_strip(&self.label)
     }
 
     /// Expansion/contraction duration — the status-interface module-menu pace.
@@ -319,9 +301,8 @@ impl Dropdown {
             // No band: the revealed menu IS the whole open surface.
             return (ax, ay, aw, ah);
         }
-        let label_x = side_offset(&self.label);
-        let (tx, ty) = (content.x + label_x, content.y);
-        let (tw, th) = ((content.width - label_x).max(0.0), content.height);
+        let (tx, ty) = (content.x, content.y);
+        let (tw, th) = (content.width, content.height);
         let x0 = tx.min(ax);
         let y0 = ty.min(ay);
         let x1 = (tx + tw).max(ax + aw);
@@ -360,9 +341,7 @@ impl Dropdown {
 
         let base_y = content.y - self.label_top();
         let open_upward = self.open_upward.unwrap_or(base_y > 400.0);
-        let label_x = side_offset(&self.label);
-
-        let mut rx = content.x + label_x;
+        let mut rx = content.x;
         let mut ry = if self.menu_replaces_trigger {
             // The menu takes the trigger's slot: flush with its bottom edge
             // (upward) or its top edge (downward) rather than stacked past it.
@@ -427,9 +406,8 @@ impl Dropdown {
     /// with the root plate-concentric corner adjustment) or `extra_quads` (plain) depending on
     /// the configured radius, byte-for-byte on the same content rect.
     fn paint_background(&self, content: Rect, ctx: &mut PaintCtx) {
-        let label_x = side_offset(&self.label);
-        let x = content.x + label_x;
-        let w = content.width - label_x;
+        let x = content.x;
+        let w = content.width;
         let y = content.y;
         let visual_h = content.height;
 
@@ -487,105 +465,6 @@ impl Dropdown {
             let (x, y, w, visual_h) = (inner.x, inner.y, inner.width, inner.height);
             let r4 = [r4t.0, r4t.1, r4t.2, r4t.3];
             let face = if raw_bg[3] > 0.001 { bg_color } else { [0.0; 4] };
-            let strip = self.label_top();
-            if strip > 0.0 {
-                // Labeled: the label sits in a CARVE-OUT tab, the section-
-                // title idiom — a flat recessed well hugging the label run,
-                // its bottom open into the trigger's groove ring below (the
-                // tab's walls: top, right, left). Right of the tab the ring
-                // keeps its normal top wall, starting at the tab's throat.
-                let g = depth * 0.5;
-                let orad = (r4[0] + g, r4[1] + g, r4[2] + g, r4[3] + g);
-                let (outer_x, outer_r) = (x - g, x + w + g);
-                let (tab_top, ring_top) = (y - strip - g, y - g);
-                // The tab hugs the label run (drawn at x + inset): text width
-                // plus the inset each side, kept inside the trigger's span.
-                let (fam, fsize) = crate::layout::control_label_font_detached_parsed();
-                let text_w = self
-                    .label
-                    .as_deref()
-                    .map(|l| crate::widget::display::measure_text_width(l, &fam, fsize))
-                    .unwrap_or(0.0);
-                let inset = crate::layout::DETACHED_LABEL_INSET; // the label's x offset
-                let tab_w = (text_w + 2.0 * inset + 2.0 * g)
-                    .max(2.0 * orad.0 + 8.0)
-                    .min(outer_r - outer_x);
-                let tab_r = outer_x + tab_w;
-
-                // The tab: bottom open into the ring, pieces extended `depth`
-                // past their interior seams so the tessellator's host fades
-                // crossfade there instead of notching the walls. With the
-                // fillet, the tab's right wall must END at the fillet's
-                // vertical tangent (crossfading out under the arc) or its
-                // straight run ghosts through the curve — the tab piece stops
-                // there and a left-only bridge carries the left wall across
-                // the fillet span down to the ring's own fade-in.
-                let fr = 6.0_f32.min(strip * 0.5);
-                let filleted = outer_r - tab_r > fr + 4.0;
-                let tab_bottom = if filleted { ring_top - fr } else { ring_top };
-                ctx.recess_edges(
-                    Rect { x: outer_x, y: tab_top, width: tab_w, height: tab_bottom - tab_top + depth },
-                    (orad.0, orad.1.min(strip * 0.5), 0.0, 0.0),
-                    depth,
-                    (true, true, false, true),
-                );
-                if filleted {
-                    ctx.recess_edges(
-                        Rect { x: outer_x, y: ring_top - fr, width: tab_w, height: fr + depth },
-                        (0.0, 0.0, 0.0, 0.0),
-                        depth,
-                        (false, false, false, true),
-                    );
-                }
-                // The ring proper: right + bottom + left walls, one prim so
-                // its corners blend internally.
-                ctx.recess_edges(
-                    Rect { x: outer_x, y: ring_top, width: w + 2.0 * g, height: visual_h + 2.0 * g },
-                    (0.0, 0.0, orad.2, orad.3),
-                    depth,
-                    (false, true, true, true),
-                );
-                // Ring top wall, right of the tab. The concave fillet rounds
-                // the throat; the straight run starts a fillet radius past it
-                // (extended `depth` left so its fade-in lands under the
-                // fillet's hard tangent cut instead of leaving a gap).
-                if filleted {
-                    ctx.concave_fillet(
-                        tab_r + fr,
-                        ring_top - fr,
-                        fr,
-                        depth,
-                        std::f32::consts::FRAC_PI_2,
-                        false,
-                    );
-                    ctx.recess_edges(
-                        Rect {
-                            x: tab_r + fr - depth,
-                            y: ring_top,
-                            width: outer_r - tab_r - fr + depth,
-                            height: visual_h + 2.0 * g,
-                        },
-                        (0.0, orad.1, 0.0, 0.0),
-                        depth,
-                        (true, false, false, false),
-                    );
-                } else if outer_r - tab_r > 0.5 {
-                    ctx.recess_edges(
-                        Rect { x: tab_r - depth, y: ring_top, width: outer_r - tab_r + depth, height: visual_h + 2.0 * g },
-                        (0.0, orad.1, 0.0, 0.0),
-                        depth,
-                        (true, false, false, false),
-                    );
-                }
-                let rect = Rect { x, y, width: w, height: visual_h };
-                let rrad = (r4[0], r4[1], r4[2], r4[3]);
-                if face[3] > 0.001 {
-                    ctx.bevel(rect, rrad, face, depth);
-                } else {
-                    ctx.boss(rect, rrad, depth);
-                }
-                return;
-            }
             ctx.inset_plate(
                 Rect { x, y, width: w, height: visual_h },
                 (r4[0], r4[1], r4[2], r4[3]),
@@ -673,9 +552,8 @@ impl Dropdown {
         };
 
         let (font_family, font_size) = crate::layout::control_label_font_detached_parsed();
-        let label_x = side_offset(&self.label);
-        let x = content.x + label_x;
-        let w = content.width - label_x;
+        let x = content.x;
+        let w = content.width;
         let start_x = x + 8.0;
         let right_limit = x + w - 28.0; // 10px margin before the arrow
         let fade_start_x = (right_limit - 24.0).max(start_x); // Fade out over the last 24px
@@ -911,11 +789,6 @@ impl Layout for Dropdown {
         }
     }
 
-    fn inflates_label_rect(&self) -> bool {
-        false
-    }
-
-
     /// Content size for the scene layout engine (Phase 2b). A normal dropdown is wide enough for
     /// the widest option (via `content_width`, which already includes the arrow/padding inset), so
     /// the control doesn't resize as the selection changes. A menu-button dropdown (fixed
@@ -1053,9 +926,8 @@ impl Paint for Dropdown {
         // trigger paint) — display text left, ▼ right, the paint_text palette.
         // A menu that replaces the trigger has no band to redraw on.
         if !self.menu_replaces_trigger {
-            let label_x = side_offset(&self.label);
-            let (tx, ty) = (rect.x + label_x, rect.y);
-            let (tw, th) = ((rect.width - label_x).max(0.0), rect.height);
+            let (tx, ty) = (rect.x, rect.y);
+            let (tw, th) = (rect.width, rect.height);
             let band_bounds = Some([ux, uy, ux + uw, uy + uh]);
             let font = crate::layout::control_label_font_detached();
             let text_y = crate::layout::align_text_y(ty, th, 12.0, 0.0);
diff --git a/src/widget/input/ramp.rs b/src/widget/input/ramp.rs
index 1914c13..a37227c 100644
--- a/src/widget/input/ramp.rs
+++ b/src/widget/input/ramp.rs
@@ -943,11 +943,7 @@ impl Ramp {
     /// The detached-label strip height the labeled dropdowns carry
     /// (`Widget::label_offset`'s formula).
     pub fn label_strip() -> f32 {
-        if crate::layout::control_label_layout() == "side" {
-            return 0.0;
-        }
-        let (_, font_size) = crate::layout::control_label_font_detached_parsed();
-        font_size + crate::layout::control_label_margin()
+        crate::layout::control_label_strip()
     }
 
     /// Lay out the control strip under the curve area. One rhythm: the label
diff --git a/src/widget/input/slider.rs b/src/widget/input/slider.rs
index fd3dccf..3df9f91 100644
--- a/src/widget/input/slider.rs
+++ b/src/widget/input/slider.rs
@@ -1,8 +1,6 @@
-//! Narrow-trait `Slider` and `RangeSlider` (Phase 5h). Detached-label widgets that do NOT
-//! inflate their rect (`inflates_label_rect = false`): the label eats into the assigned rect,
-//! so the adapter's content rect is exactly the legacy `y + label_offset` / `h - label_offset`
-//! band the old geometry used. The side-label inset (`label_x_offset`) is computed by the model
-//! from its synced label + config. Drags are host-driven through the `Input` drag hooks; the
+//! Narrow-trait `Slider` and `RangeSlider` (Phase 5h). Detached-label widgets: the adapter
+//! draws the control label in the strip above the content rect the geometry here works in.
+//! Drags are host-driven through the `Input` drag hooks; the
 //! readout edit mode uses `EventCtx::request_focus` and the wheel gating uses the legacy scroll
 //! gesture state through `EventCtx::ui`.
 
@@ -25,14 +23,6 @@ struct SliderGeom {
     track_w: f32,
 }
 
-fn side_offset(label: &Option<String>) -> f32 {
-    if crate::layout::control_label_layout() == "side" && label.is_some() {
-        90.0
-    } else {
-        0.0
-    }
-}
-
 #[derive(Debug, Clone)]
 pub struct Slider {
     dragging: bool,
@@ -124,9 +114,8 @@ impl Slider {
 
 
     fn geom(&self, rect: Rect) -> SliderGeom {
-        let side = side_offset(&self.label);
-        let x = rect.x + side;
-        let w = rect.width - side;
+        let x = rect.x;
+        let w = rect.width;
         let (track_x, track_w) = if self.show_readout {
             let readout_w = 60.0;
             let gap = 8.0;
@@ -232,12 +221,6 @@ impl Adapted<Slider> {
 }
 
 impl Layout for Slider {
-    fn inflates_label_rect(&self) -> bool {
-        false // legacy Slider::set_rect stored the assigned rect verbatim
-    }
-
-
-
     fn intrinsic_size(&self) -> Option<Size> {
         Some(Size::new(0.0, crate::layout::slider_height()))
     }
@@ -662,123 +645,14 @@ pub(crate) fn paint_band_shape(ctx: &mut PaintCtx, track_x: f32, track_w: f32, c
     }
 }
 
-/// The recessed track's carve, in the labeled composition of the Slider2D pad (and
-/// any well that reaches up into its label strip): with a detached label (`strip` > 0, the label strip's height above
-/// `track`) the label sits in a CARVE-OUT tab, the section-title idiom (the labeled
-/// Dropdown's composition) — a flat recessed well hugging the label run, bottom open
-/// into the track's well; the well's top wall picks up right of the tab's throat.
-/// Pieces extend `depth` past interior seams (host-fade crossfade), the tab's right
-/// wall ends at the fillet's vertical tangent (or it ghosts through the arc), and a
-/// left-only bridge carries the left wall across the fillet span. Unlabeled, one
-/// plain recess.
-pub(crate) fn carve_labeled_well(ctx: &mut PaintCtx, track: Rect, strip: f32, label_w: f32, radius: f32, depth: f32) {
-    // Carve INSIDE the track (`layout::carve_inside`): every piece derives from
-    // this rect, so the whole composition — tab, fillet, bridge — moves in with it
-    // and the outer walls land on the track's edges.
-    let (track, radii) = crate::layout::carve_inside(track, (radius, radius, radius, radius), depth);
-    let radius = radii.0;
-    let track_end = track.x + track.width;
-    if strip > 0.0 {
-        // Labeled: the label sits in a CARVE-OUT tab, the section-
-        // title idiom (the labeled Dropdown's composition) — a flat
-        // recessed well hugging the label run, bottom open into the
-        // track's well; the well's top wall picks up right of the
-        // tab's throat.
-        let inset = crate::layout::DETACHED_LABEL_INSET; // the label's x offset
-        let fr = 6.0_f32.min(strip * 0.5);
-        let mut tab_w = (label_w + 2.0 * inset).max(2.0 * radius + 8.0).min(track.width);
-        // A throat too short for the fillet and a run of top wall past it reads
-        // as a notch beside the tab: then the tab spans the whole track (the
-        // Slider2D's pad, a narrow slider) and the well's top wall is the tab's.
-        if track.width - tab_w < 2.0 * fr + 4.0 {
-            tab_w = track.width;
-        }
-        let tab_r = track.x + tab_w;
-        // The labeled-Dropdown composition: pieces extend `depth`
-        // past interior seams (host-fade crossfade), the tab's right
-        // wall ends at the fillet's vertical tangent (or it ghosts
-        // through the arc), and a left-only bridge carries the left
-        // wall across the fillet span.
-        let filleted = track_end - tab_r > 2.0 * fr + 4.0;
-        let tab_bottom = if filleted { track.y - fr } else { track.y };
-        // The tab's crossfade extension past its bottom seam is capped at the
-        // fillet radius: a deep wall (a tall well's) otherwise ran on below the
-        // well's top edge as a stub beside the fillet.
-        let ext = if filleted { depth.min(fr) } else { depth };
-        ctx.recess_edges(
-            Rect { x: track.x, y: track.y - strip, width: tab_w, height: tab_bottom - (track.y - strip) + ext },
-            (radius, radius.min(strip * 0.5), 0.0, 0.0),
-            depth,
-            (true, true, false, true),
-        );
-        if filleted {
-            ctx.recess_edges(
-                Rect { x: track.x, y: track.y - fr, width: tab_w, height: fr + depth },
-                (0.0, 0.0, 0.0, 0.0),
-                depth,
-                (false, false, false, true),
-            );
-        }
-        ctx.recess_edges(track, (0.0, 0.0, radius, radius), depth, (false, true, true, true));
-        if filleted {
-            ctx.concave_fillet(
-                tab_r + fr,
-                track.y - fr,
-                fr,
-                depth,
-                std::f32::consts::FRAC_PI_2,
-                false,
-            );
-            ctx.recess_edges(
-                Rect {
-                    x: tab_r + fr - depth,
-                    y: track.y,
-                    width: track_end - tab_r - fr + depth,
-                    height: track.height,
-                },
-                (0.0, radius, 0.0, 0.0),
-                depth,
-                (true, false, false, false),
-            );
-        } else if track_end - tab_r > 0.5 {
-            ctx.recess_edges(
-                Rect { x: tab_r - depth, y: track.y, width: track_end - tab_r + depth, height: track.height },
-                (0.0, radius, 0.0, 0.0),
-                depth,
-                (true, false, false, false),
-            );
-        }
-    } else {
-        ctx.recess(track, (radius, radius, radius, radius), depth);
-    }
-}
-
-/// The detached-label strip height above a content rect (zero in side layout or
-/// unlabeled) — the adapter's `label_offset`, replicated for widgets that reach up
-/// into the strip to carve the label tab.
+/// The detached-label strip height above a content rect (zero unlabeled) — the
+/// adapter's `Widget::label_offset` over a model's synced label, for models whose
+/// cached rect is the whole block.
 pub(crate) fn detached_strip(label: &Option<String>) -> f32 {
-    if crate::layout::control_label_layout() == "side" {
-        return 0.0;
-    }
-    if label.is_some() {
-        let (_, font_size) = crate::layout::control_label_font_detached_parsed();
-        font_size + crate::layout::control_label_margin()
-    } else {
-        0.0
-    }
-}
-
-/// The detached label's measured width (the tab hugs it), zero when unlabeled.
-pub(crate) fn detached_label_width(label: &Option<String>) -> f32 {
-    let (fam, fsize) = crate::layout::control_label_font_detached_parsed();
-    label.as_deref().map(|l| crate::widget::display::measure_text_width(l, &fam, fsize)).unwrap_or(0.0)
+    if label.is_some() { crate::layout::control_label_strip() } else { 0.0 }
 }
 
 impl Layout for RangeSlider {
-    fn inflates_label_rect(&self) -> bool {
-        false
-    }
-
     fn intrinsic_size(&self) -> Option<Size> {
         Some(Size::new(0.0, crate::layout::rangeslider_height()))
     }
@@ -798,8 +672,7 @@ impl Paint for RangeSlider {
     /// swallowed length. The swells sit where the thumbs' centres were, so the
     /// drag geometry below is unchanged.
     fn paint(&self, rect: Rect, ctx: &mut PaintCtx) {
-        let side = side_offset(&self.label);
-        let (x, y, w, h) = (rect.x + side, rect.y, rect.width - side, rect.height);
+        let (x, y, w, h) = (rect.x, rect.y, rect.width, rect.height);
         let thumb_size = h * 0.9;
         let range = w - thumb_size;
         let lo = x + self.value_low * range + thumb_size / 2.0;
@@ -817,9 +690,8 @@ impl Input for RangeSlider {
                 if !ui.scroll_gesture_new && ui.scroll_initiate_widget_id != Some(ectx.id) {
                     return false;
                 }
-                let side = side_offset(&self.label);
                 let r = ectx.rect;
-                let (x, y, w, h) = (r.x + side, r.y, r.width - side, r.height);
+                let (x, y, w, h) = (r.x, r.y, r.width, r.height);
                 if *px >= r.x && *px <= r.x + r.width && *py >= y && *py <= y + h {
                     if ui.scroll_gesture_new {
                         ui.scroll_initiate_widget_id = Some(ectx.id);
@@ -865,8 +737,7 @@ impl Input for RangeSlider {
         self.active_thumb.is_some()
     }
     fn drag_begin(&mut self, px: f32, _py: f32, rect: Rect) {
-        let side = side_offset(&self.label);
-        let (x, w) = (rect.x + side, rect.width - side);
+        let (x, w) = (rect.x, rect.width);
         let thumb_size = rect.height * 0.9;
         let range = w - thumb_size;
         let thumb_low_x = x + self.value_low * range;
@@ -890,8 +761,7 @@ impl Input for RangeSlider {
     }
     fn drag_update(&mut self, px: f32, _py: f32, rect: Rect) -> bool {
         let Some(active) = self.active_thumb else { return false };
-        let side = side_offset(&self.label);
-        let (x, w) = (rect.x + side, rect.width - side);
+        let (x, w) = (rect.x, rect.width);
         let thumb_size = rect.height * 0.9;
         let range = w - thumb_size;
         if range <= 0.0 {
diff --git a/src/widget/input/slider2d.rs b/src/widget/input/slider2d.rs
index a413114..64cc3f8 100644
--- a/src/widget/input/slider2d.rs
+++ b/src/widget/input/slider2d.rs
@@ -1,7 +1,6 @@
 //! `Slider2D` — a two-axis pad control: one thumb dragged across a recessed
 //! square well maps to an `(x, y)` pair in 0..1 × 0..1, y-up. Follows the
-//! `Slider` narrow-trait shape: a detached label that does NOT inflate the
-//! rect (the label eats into the assigned rect), host-driven drags through
+//! `Slider` narrow-trait shape: the adapter's detached label above the pad, host-driven drags through
 //! the `Input` drag hooks, and `take_change`-based polling by composites.
 
 use crate::scene::layout::{Rect, Size};
@@ -55,20 +54,6 @@ impl Slider2D {
         )
     }
 
-    /// The detached-label strip height above the content rect — the Slider /
-    /// Dropdown replica of `Widget::label_offset` over the synced label.
-    fn label_top(&self) -> f32 {
-        if crate::layout::control_label_layout() == "side" {
-            return 0.0;
-        }
-        if self.label.is_some() {
-            let (_, font_size) = crate::layout::control_label_font_detached_parsed();
-            font_size + crate::layout::control_label_margin()
-        } else {
-            0.0
-        }
-    }
-
     fn set_from_point(&mut self, px: f32, py: f32, rect: Rect) -> bool {
         let inset = Self::THUMB_R + 2.0;
         let w = (rect.width - 2.0 * inset).max(1.0);
@@ -87,22 +72,9 @@ impl Slider2D {
 }
 
 impl Layout for Slider2D {
-    fn inflates_label_rect(&self) -> bool {
-        false // the Slider rule: the detached label eats into the assigned rect
-    }
-
-
-    /// A 64px pad, or wide enough for its label's carve-out tab plus a filleted
-    /// throat beside it (the tab is clipped to the pad; the label spilled past a
-    /// 64px one, and a throat too short for the fillet reads as a notch).
+    /// A 64px pad.
     fn intrinsic_size(&self) -> Option<Size> {
-        let label_w = crate::widget::input::slider::detached_label_width(&self.label);
-        let tab_w = if label_w > 0.0 {
-            label_w + 2.0 * crate::layout::DETACHED_LABEL_INSET + 24.0 + crate::layout::bevel_width()
-        } else {
-            0.0
-        };
-        Some(Size::new(64.0_f32.max(tab_w), 64.0))
+        Some(Size::new(64.0, 64.0))
     }
 
     fn intrinsic_measure_width(&self) -> bool {
@@ -150,16 +122,10 @@ impl Paint for Slider2D {
         );
 
         let depth = crate::layout::bevel_width().min(rect.height * 0.2);
-        // The well's ring, with a labeled pad's label in a carve-out tab — the one
-        // labeled-well composition the sliders share (`carve_labeled_well`).
-        crate::widget::input::slider::carve_labeled_well(
-            ctx,
-            rect,
-            self.label_top(),
-            crate::widget::input::slider::detached_label_width(&self.label),
-            radius,
-            depth,
-        );
+        // The well's ring, carved inside the pad's rect (`layout::carve_inside`); the
+        // control label above is the adapter's, outside the well like every control's.
+        let (inner, radii) = crate::layout::carve_inside(rect, (radius, radius, radius, radius), depth);
+        ctx.recess(inner, radii, depth);
     }
 }
 
diff --git a/src/widget/input/spinbox.rs b/src/widget/input/spinbox.rs
index b2a60a5..e4531ff 100644
--- a/src/widget/input/spinbox.rs
+++ b/src/widget/input/spinbox.rs
@@ -1,5 +1,5 @@
-//! Narrow-trait `Spinbox` (Phase 5i). Slider-style label convention (no rect inflation; label
-//! eats into the assigned rect, side-label inset computed from the synced label). Sub-zone
+//! Narrow-trait `Spinbox` (Phase 5i). The adapter's detached label sits above the content rect
+//! the geometry here works in. Sub-zone
 //! hover (the -/+ buttons) is tracked from `PointerMove` against the content rect; a click on
 //! the display area enters edit mode and takes focus via `EventCtx::request_focus`.
 
@@ -11,14 +11,6 @@ use crate::widget::{
     Paint, TextEditorState,
 };
 
-fn side_offset(label: &Option<String>) -> f32 {
-    if crate::layout::control_label_layout() == "side" && label.is_some() {
-        90.0
-    } else {
-        0.0
-    }
-}
-
 #[derive(Debug, Clone)]
 pub struct Spinbox {
     pub value: i32,
@@ -115,9 +107,8 @@ impl Spinbox {
     }
 
     fn geom(&self, rect: Rect) -> SpinGeom {
-        let side = side_offset(&self.label);
-        let x = rect.x + side;
-        let w = rect.width - side;
+        let x = rect.x;
+        let w = rect.width;
         let pad = crate::layout::spinbox_button_padding();
         SpinGeom {
             x,
@@ -272,10 +263,6 @@ impl Adapted<Spinbox> {
 }
 
 impl Layout for Spinbox {
-    fn inflates_label_rect(&self) -> bool {
-        false
-    }
-
 
 
     fn intrinsic_size(&self) -> Option<Size> {
diff --git a/src/widget/input/text_box.rs b/src/widget/input/text_box.rs
index cc29a14..1bfd635 100644
--- a/src/widget/input/text_box.rs
+++ b/src/widget/input/text_box.rs
@@ -11,7 +11,7 @@
 //! Parity notes:
 //! - The legacy render split is asymmetric and preserved faithfully: the non-rounded path
 //!   (`extra_quads`) draws at the full base x/width with a disabled special-case; the rounded
-//!   path (`all_rounded_quads`) insets by the side label and has NO disabled branch.
+//!   path (`all_rounded_quads`) has NO disabled branch.
 //! - Releases: legacy `mouse_input` hit-gated releases too (out-of-rect releases were dropped).
 //!   The adapter delivers releases ungated, so the model re-checks containment itself against
 //!   the plain rect (the row-substituted release geometry is approximated — flagged).
@@ -36,16 +36,6 @@ pub fn get_font_db() -> &'static resvg::usvg::fontdb::Database {
     })
 }
 
-/// Side-layout label inset — the legacy `WidgetHost::label_x_offset` default for non-exempt
-/// widgets (TextBox was never in the exempt list).
-fn side_offset(label: &Option<String>) -> f32 {
-    if crate::layout::control_label_layout() == "side" && label.is_some() {
-        90.0
-    } else {
-        0.0
-    }
-}
-
 #[derive(Debug, Clone)]
 pub struct TextBox {
     pub text: String,
@@ -106,7 +96,7 @@ pub struct TextBox {
     /// first shape (readers fall back to the grid).
     line_glyph_positions: Vec<Vec<f32>>,
     pub update_on_type: bool,
-    /// Synced control label ([`Paint::sync_label`]) — drives the side/detached offsets.
+    /// Synced control label ([`Paint::sync_label`]) — drives the detached strip offset.
     label: Option<String>,
     /// Own hover flag, maintained from `MouseEnter`/`MouseLeave` (adapter bookkeeping).
     hovered: bool,
@@ -165,23 +155,14 @@ impl TextBox {
         })
     }
 
-    /// The detached-label strip height — a replica of `Widget::label_offset` over the synced
-    /// label (zero in side layout or unlabeled).
+    /// The detached-label strip height above the content (zero unlabeled) — the
+    /// adapter's `Widget::label_offset` over the synced label.
     fn label_top(&self) -> f32 {
-        if crate::layout::control_label_layout() == "side" {
-            return 0.0;
-        }
-        if self.label.is_some() {
-            let (_, font_size) = crate::layout::control_label_font_detached_parsed();
-            font_size + crate::layout::control_label_margin()
-        } else {
-            0.0
-        }
+        crate::widget::input::slider::detached_strip(&self.label)
     }
 
     fn map_x_to_idx(&self, click_x: f32) -> usize {
-        let label_x = side_offset(&self.label);
-        let relative_x = click_x - (self.rect.x + label_x + 8.0) + self.scroll_x;
+        let relative_x = click_x - (self.rect.x + 8.0) + self.scroll_x;
         if self.glyph_positions.is_empty() {
             let char_width = self.char_width();
             return ((relative_x / char_width).round() as isize)
@@ -690,20 +671,19 @@ impl TextBox {
 
     /// Map a press/drag position to a buffer index — the shared body of the legacy
     /// `mouse_input` press arm and `drag_update`.
-    fn position_to_idx(&self, px: f32, py: f32, with_label_x: bool) -> usize {
+    fn position_to_idx(&self, px: f32, py: f32) -> usize {
         let char_width = self.char_width();
         let top = self.label_top();
-        let label_x = if with_label_x { side_offset(&self.label) } else { 0.0 };
         if self.multiline {
             let line_height = self.line_height();
             let max_chars = if self.line_wrap_enabled() {
-                ((((self.rect.width - label_x) - 16.0) / char_width).floor() as usize).max(1)
+                (((self.rect.width - 16.0) / char_width).floor() as usize).max(1)
             } else {
                 999999
             };
             let (lines, index_map) = self.wrap_text(max_chars);
             let click_line = (((py - (self.rect.y + top + 8.0) + self.scroll_y) / line_height).floor() as isize).max(0) as usize;
-            let rel_x = px - (self.rect.x + label_x + 8.0) + self.scroll_x;
+            let rel_x = px - (self.rect.x + 8.0) + self.scroll_x;
             let click_col = self.line_x_to_col(click_line.min(lines.len() - 1), rel_x);
             self.map_2d_to_1d(&index_map, click_line, click_col, lines.len() - 1)
         } else {
@@ -714,7 +694,7 @@ impl TextBox {
     /// Extend the selection to a drag position — the shared body of the legacy
     /// `on_cursor_moved` drag arm and `drag_update` (which used no label inset).
     fn extend_selection_to(&mut self, px: f32, py: f32) -> bool {
-        let drag_idx = self.position_to_idx(px, py, false);
+        let drag_idx = self.position_to_idx(px, py);
         if self.cursor_idx != drag_idx {
             self.cursor_idx = drag_idx;
             self.just_focused = false;
@@ -992,7 +972,7 @@ impl TextBox {
 
     /// Selection highlight + caret quads, shared by both render branches. `x`/`w` are the
     /// (possibly label-inset) horizontal span the branch draws in — the legacy paths differed
-    /// (non-rounded used the full base span, rounded inset by the side label).
+    /// (non-rounded and rounded alike use the full base span).
     fn selection_quads(&self, x: f32, w: f32, out: &mut Vec<(f32, f32, f32, f32, [f32; 4])>) {
         if !(self.editing || self.select_anchor.is_some()) {
             return;
@@ -1140,9 +1120,8 @@ impl TextBox {
             [0xcc, 0xcc, 0xd4]
         };
 
-        let label_x = side_offset(&self.label);
-        let x = self.rect.x + label_x;
-        let w = self.rect.width - label_x;
+        let x = self.rect.x;
+        let w = self.rect.width;
 
         if self.multiline {
             let char_width = self.char_width();
@@ -1211,11 +1190,10 @@ impl TextBox {
             return None;
         }
         let top = self.label_top();
-        let label_x = side_offset(&self.label);
         let well = Rect {
-            x: self.rect.x + label_x,
+            x: self.rect.x,
             y: self.rect.y + top,
-            width: self.rect.width - label_x,
+            width: self.rect.width,
             height: self.rect.height - top,
         };
         let depth = crate::layout::bevel_width().min(well.height * 0.2);
@@ -1292,10 +1270,6 @@ impl Adapted<TextBox> {
 }
 
 impl Layout for TextBox {
-    fn inflates_label_rect(&self) -> bool {
-        false
-    }
-
 
     /// One row for a single-line box; a multiline box has no natural height of its own —
     /// the host sizes it, and a layout strategy leaves its assigned rect alone.
@@ -1376,13 +1350,11 @@ impl Paint for TextBox {
     }
 
     fn text_bounds(&self, rect: Rect) -> Option<[f32; 4]> {
-        // Legacy bounded-text getters clipped to the full base rect, inset on the left by the
-        // side label.
+        // Legacy bounded-text getters clipped to the full block rect.
         let top = self.label_top();
         let base_y = rect.y - top;
         let base_h = rect.height + top;
-        let label_x = side_offset(&self.label);
-        Some([rect.x + label_x, base_y, rect.x + rect.width, base_y + base_h])
+        Some([rect.x, base_y, rect.x + rect.width, base_y + base_h])
     }
 
     /// The legacy `prepare_text`: sync font family/size with the live config defaults, then
@@ -1468,8 +1440,7 @@ impl Paint for TextBox {
         // one `selection_quads`/`value_labels` compute at paint time.
         self.line_glyph_positions.clear();
         if self.multiline {
-            let label_x = side_offset(&self.label);
-            let wrap_w = self.rect.width - label_x;
+            let wrap_w = self.rect.width;
             let max_chars = if self.line_wrap_enabled() {
                 (((wrap_w - 16.0) / self.char_width()).floor() as usize).max(1)
             } else {
@@ -1517,7 +1488,7 @@ impl Paint for TextBox {
         let _ = (base_y, base_h);
 
         if radius <= 0.0 {
-            // Legacy `extra_quads`: full base span (no side-label inset), disabled
+            // Legacy `extra_quads`: full base span, disabled
             // special-case with early return.
             let mut quads: Vec<(f32, f32, f32, f32, [f32; 4])> = Vec::new();
             if self.disabled {
@@ -1547,10 +1518,9 @@ impl Paint for TextBox {
                 ctx.quad(Rect { x: qx, y: qy, width: qw, height: qh }, qc);
             }
         } else {
-            // Legacy `all_rounded_quads`: side-label inset, no disabled special-case.
-            let label_x = side_offset(&self.label);
-            let x = self.rect.x + label_x;
-            let w = self.rect.width - label_x;
+            // Legacy `all_rounded_quads`: no disabled special-case.
+            let x = self.rect.x;
+            let w = self.rect.width;
 
             // One background regardless of focus (see the flat path above).
             let bg_color = crate::colors::textbox_background_color();
@@ -1676,7 +1646,7 @@ impl Input for TextBox {
                     self.begin_editing();
                     ectx.request_focus();
                 }
-                let idx = self.position_to_idx(*px, *py, true);
+                let idx = self.position_to_idx(*px, *py);
                 self.cursor_idx = idx;
                 self.select_anchor = Some(idx);
                 self.all_selected = false;
@@ -1688,11 +1658,8 @@ impl Input for TextBox {
             Event::MouseButton { button: MouseButton::Left, state: ElementState::Released, x: px, y: py, .. } => {
                 if self.disabled { return false; }
                 // Legacy gated releases on the hit test; the adapter delivers them ungated, so
-                // re-check containment (plain rect + side inset — row spans approximated).
-                let label_x = side_offset(&self.label);
-                let top = self.label_top();
-                let (bx, by, bw, bh) = (self.rect.x + label_x, self.rect.y, self.rect.width - label_x, self.rect.height);
-                let _ = top;
+                // re-check containment (the plain block rect — row spans approximated).
+                let (bx, by, bw, bh) = (self.rect.x, self.rect.y, self.rect.width, self.rect.height);
                 if !(*px >= bx && *px <= bx + bw && *py >= by && *py <= by + bh) {
                     return false;
                 }
diff --git a/src/widget/input/trackpad.rs b/src/widget/input/trackpad.rs
index 8f21ffd..4a64f38 100644
--- a/src/widget/input/trackpad.rs
+++ b/src/widget/input/trackpad.rs
@@ -12,8 +12,8 @@ pub struct Finger {
 
 /// Touchpad visualization/input area (narrow-trait model, Phase 6as leaf sweep). The
 /// content rect is cached on assignment (the ParametersBg pattern) because the finger
-/// math runs from events and drags as well as paint; the control label stays
-/// model-drawn (`inline_label`) to keep the legacy detached-top layout byte-identical.
+/// math runs from events and drags as well as paint; the control label is the adapter's
+/// detached one above the pad, like every control's.
 #[derive(Debug, Clone)]
 pub struct Trackpad {
     rect: Rect,
@@ -51,35 +51,10 @@ impl Adapted<Trackpad> {
 }
 
 impl Trackpad {
-
-    fn label_offset(&self) -> f32 {
-        if crate::layout::control_label_layout() == "side" {
-            return 0.0;
-        }
-        if self.label.is_some() {
-            let (_, font_size) = crate::layout::control_label_font_detached_parsed();
-            font_size + crate::layout::control_label_margin()
-        } else {
-            0.0
-        }
-    }
-
-    fn label_x_offset(&self) -> f32 {
-        if crate::layout::control_label_layout() == "side" && self.label.is_some() {
-            90.0
-        } else {
-            0.0
-        }
-    }
-
-    /// The inner touch area (content rect minus the detached-label reservation).
+    /// The touch area: the cached block rect less the detached-label strip above it.
     fn touch_area(&self) -> (f32, f32, f32, f32) {
-        let label_x = self.label_x_offset();
-        let x = self.rect.x + label_x;
-        let w = self.rect.width - label_x;
-        let top = self.label_offset();
-        let visual_h = self.rect.height - top;
-        (x, self.rect.y + top, w, visual_h)
+        let top = crate::widget::input::slider::detached_strip(&self.label);
+        (self.rect.x, self.rect.y + top, self.rect.width, self.rect.height - top)
     }
 
     fn finger_at(&self, px: f32, py: f32) -> Finger {
@@ -93,10 +68,6 @@ impl Trackpad {
 }
 
 impl Layout for Trackpad {
-    fn inline_label(&self) -> bool {
-        true
-    }
-
     fn rect_assigned(&mut self, rect: Rect) {
         self.rect = rect;
     }
@@ -160,7 +131,7 @@ impl Paint for Trackpad {
             );
         }
 
-        // 4. Labels ("Touchpad Area" hint + the model-drawn control label)
+        // 4. The "Touchpad Area" hint (the control label is the adapter's).
         ctx.text(
             "Touchpad Area".to_string(),
             x + 12.0,
@@ -168,16 +139,6 @@ impl Paint for Trackpad {
             11.0,
             [0x73, 0x73, 0x8c],
         );
-        if let Some(ref label) = self.label {
-            let (_, font_size) = crate::layout::control_label_font_detached_parsed();
-            ctx.text(
-                label.clone(),
-                self.rect.x,
-                self.rect.y,
-                font_size,
-                colors::control_label_color_detached_for_state(self.hovered, false),
-            );
-        }
     }
 }
 
diff --git a/src/widget/layout_helper.rs b/src/widget/layout_helper.rs
index d29eb08..ce42c68 100644
--- a/src/widget/layout_helper.rs
+++ b/src/widget/layout_helper.rs
@@ -21,10 +21,10 @@ impl ColumnLayout {
         }
     }
 
+    /// `height` is the CONTENT height; the widget's block adds its label strip.
     pub fn add_widget(&mut self, widget: &mut dyn WidgetHost, height: f32) {
-        let label_off = widget.base().label_offset();
-        let total_h = height + label_off;
-        widget.set_rect(self.x + self.margin, self.current_y, self.width - 2.0 * self.margin, height);
+        let total_h = height + widget.label_strip();
+        widget.set_rect(self.x + self.margin, self.current_y, self.width - 2.0 * self.margin, total_h);
         self.current_y += total_h + self.gap;
     }
 
@@ -36,7 +36,7 @@ impl ColumnLayout {
         let mut max_label_off = 0.0;
         for &widget_ptr in widgets {
             unsafe {
-                let off = (*widget_ptr).base().label_offset();
+                let off = (*widget_ptr).label_strip();
                 if off > max_label_off {
                     max_label_off = off;
                 }
@@ -48,7 +48,9 @@ impl ColumnLayout {
         let mut curr_x = self.x + self.margin;
         for &widget_ptr in widgets {
             unsafe {
-                (*widget_ptr).set_rect(curr_x, self.current_y, widget_w, height);
+                // Content lines up on one row; a shorter label strip starts lower.
+                let off = (*widget_ptr).label_strip();
+                (*widget_ptr).set_rect(curr_x, self.current_y + max_label_off - off, widget_w, height + off);
             }
             curr_x += widget_w + gap;
         }
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 53c503d..74011ca 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -207,12 +207,13 @@ pub trait WidgetHost {
     fn preferred_height(&self) -> Option<f32> { None }
 
     /// The height of the detached-label strip above this widget's content: zero for
-    /// unlabeled and inline-label widgets. A widget's occupied rect is its content plus
-    /// this strip, whichever legacy convention its `set_rect` follows; `layout` lands
-    /// the content at the origin and the strip above it. A strategy reserves that
+    /// unlabeled widgets and for those whose base label IS their content
+    /// ([`Layout::inline_label`]). A widget's rect is always its content plus this
+    /// strip — `set_rect` takes that block, `layout` lands the content at the origin
+    /// and hangs the strip above it. A strategy reserves that
     /// row above every child's content (`container_layout::label_lead`) and puts
     /// `layout::CONTROL_GAP` between the blocks.
-    fn label_strip(&self) -> f32 { 0.0 }
+    fn label_strip(&self) -> f32 { self.base().label_offset() }
 
     fn mark_dirty(&mut self, ctx: &mut UiContext) {
         let b = self.base_mut();
@@ -268,9 +269,12 @@ pub trait WidgetHost {
         Size { width, height }
     }
 
+    /// Land the CONTENT box at `origin`, the label strip hanging above it — the one
+    /// placement contract (`Adapted` repeats it over its measured content size).
     fn layout(&mut self, origin: Point, constraints: LayoutConstraints, ctx: &mut UiContext) {
         let size = self.measure(constraints, ctx);
-        self.set_rect(origin.x, origin.y, size.width, size.height);
+        let strip = self.label_strip();
+        self.set_rect(origin.x, origin.y - strip, size.width, size.height + strip);
     }
 
     fn rect(&self) -> (f32, f32, f32, f32) {
@@ -318,10 +322,7 @@ pub trait WidgetHost {
             return false;
         }
         let b = self.base();
-        let (mut hx, mut hw) = if b.row_w > 0.0 { (b.row_x, b.row_w) } else { (x, w) };
-        let label_x = self.label_x_offset();
-        hx += label_x;
-        hw -= label_x;
+        let (hx, hw) = if b.row_w > 0.0 { (b.row_x, b.row_w) } else { (x, w) };
         px >= hx && px <= hx + hw && py >= y && py <= y + h
     }
 
@@ -344,10 +345,9 @@ pub trait WidgetHost {
         } else {
             return None;
         };
-        let label_x = self.label_x_offset();
         let b = self.base();
-        let hx = if b.row_w > 0.0 { b.row_x } else { b.x } + label_x;
-        let hw = if b.row_w > 0.0 { b.row_w } else { b.w } - label_x;
+        let hx = if b.row_w > 0.0 { b.row_x } else { b.x };
+        let hw = if b.row_w > 0.0 { b.row_w } else { b.w };
         Some((hx, b.y, hw, b.h, hc))
     }
 
@@ -360,18 +360,6 @@ pub trait WidgetHost {
     // inherent `Adapted<W>` reads; index-driven rosters (TI, designer) route them
     // through per-slot matches like the other value drains.
 
-    fn label_x_offset(&self) -> f32 {
-        let name = self.type_name();
-        if name == "Label" || name == "Button" || name == "Checkbox" || name == "Toggle" || name == "Ramp" {
-            return 0.0;
-        }
-        if crate::layout::control_label_layout() == "side" && self.base().label.is_some() {
-            90.0
-        } else {
-            0.0
-        }
-    }
-
     fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> { Vec::new() }
     fn extra_arcs(&self) -> Vec<(f32, f32, f32, f32, f32, f32, [f32; 4])> { Vec::new() }
     fn extra_circles(&self) -> Vec<(f32, f32, f32, [f32; 4])> { Vec::new() }
@@ -668,14 +656,6 @@ pub trait GeomController {
     fn take_geom_toggle(&mut self) -> bool;
 }
 
-pub fn label_offset(w: &dyn WidgetHost) -> f32 {
-    let name = w.type_name();
-    if name == "Label" || name == "Button" || name == "Checkbox" || name == "Toggle" {
-        return 0.0;
-    }
-    w.base().label_offset()
-}
-
 #[derive(Debug, Clone, Copy, PartialEq)]
 pub struct CornerRadii {
     pub top_left: f32,
diff --git a/src/widget/model.rs b/src/widget/model.rs
index c3fb7d2..ce5cc5a 100644
--- a/src/widget/model.rs
+++ b/src/widget/model.rs
@@ -41,21 +41,16 @@ pub trait Layout {
         None
     }
 
-    /// Whether this widget draws its control label *inline* (inside its own rect, like
-    /// `Checkbox`/`Toggle`/`Button`) rather than detached above it (like `ProgressBar`/`Slider`).
-    /// Inline-label widgets get no `set_rect` height inflation and no content-rect inset —
-    /// mirroring the legacy `label_offset` free function's type-name special cases.
+    /// Whether this widget's base label IS its content — the text a `Button` face, a
+    /// `Checkbox` row or a `Label` draws itself — rather than a control label, which
+    /// the adapter draws detached above the content (the one convention for every
+    /// labeled control: `layout::control_label_strip` tall, at
+    /// [`detached_label_inset`](Layout::detached_label_inset)). Inline-label widgets
+    /// carry no label strip and get no content-rect inset.
     fn inline_label(&self) -> bool {
         false
     }
 
-    /// Whether `set_rect` grows the widget past the assigned rect to make room for a detached
-    /// label above (`ProgressBar`'s legacy convention). Sliders keep the assigned rect and let
-    /// the label eat into it instead. Irrelevant for inline-label widgets. Default: grow.
-    fn inflates_label_rect(&self) -> bool {
-        true
-    }
-
     /// Horizontal inset of the detached base label: [`crate::layout::DETACHED_LABEL_INSET`]
     /// for every control, so a column of labels is one line and the carve-out tabs
     /// (which hug the label at this inset) sit under their labels.
@@ -72,7 +67,7 @@ pub trait Layout {
     }
 
     /// Whether the adapter's hit test substitutes the base row rect (`row_x`/`row_w`, pushed in
-    /// by row-layout hosts via `set_row_rect`) plus the side-label inset — the legacy
+    /// by row-layout hosts via `set_row_rect`) — the legacy
     /// `WidgetHost::hit_test` default geometry. Migrated controls so far dropped it (accepted
     /// drift); TextBox restores it (cce-files' save-name box relies on row hits). Default: off,
     /// keeping the other migrated widgets exactly as they shipped.
@@ -710,19 +705,8 @@ impl<W: Layout + Paint + Input + 'static> Adapted<W> {
     }
 
     /// The rect the wrapped widget paints into: the widget's rect minus the detached-label
-    /// region at the top (zero inset when there is no label, or when the widget draws its label
-    /// inline — `Widget::label_offset` / [`Layout::inline_label`]).
-    /// How far past an assigned rect `set_rect` grows this widget for its detached
-    /// label — the legacy inflating convention; zero for the eating convention and
-    /// for inline labels.
-    fn label_inflation(&self) -> f32 {
-        if Layout::inline_label(&self.inner) || !Layout::inflates_label_rect(&self.inner) {
-            0.0
-        } else {
-            self.base.label_offset()
-        }
-    }
-
+    /// strip at the top (zero when there is no label, or when the widget's label is its
+    /// content — [`Layout::inline_label`]).
     fn content_rect(&self) -> Rect {
         let top = if Layout::inline_label(&self.inner) { 0.0 } else { self.base.label_offset() };
         Rect {
@@ -1025,13 +1009,6 @@ impl<W: Layout + Paint + Input + 'static> Adapted<W> {
         if let Some(ref label) = b.label {
             let (_, font_size) = crate::layout::control_label_font_detached_parsed();
             let color = crate::colors::control_label_color_detached_for_state(b.hovered, b.focused);
-            if crate::layout::control_label_layout() == "side" {
-                let label_x = WidgetHost::label_x_offset(self);
-                if label_x > 0.0 {
-                    let y_pos = crate::layout::align_text_y(b.y, b.h, font_size, 0.0);
-                    return vec![TextLabel { text: label.clone(), x: b.x + 4.0, y: y_pos, font_size, color }];
-                }
-            }
             let inset = Layout::detached_label_inset(&self.inner);
             return vec![TextLabel { text: label.clone(), x: b.x + inset, y: b.y, font_size, color }];
         }
@@ -1116,12 +1093,11 @@ impl<W: Layout + Paint + Input + 'static> WidgetHost for Adapted<W> {
         // containers — the ctx-carrying half of the arrangement the model can't do in
         // `arrange_children`.
         // `origin` is the CONTENT box's top-left and `measure` its height; the detached
-        // label hangs in the strip above, so the occupied rect starts `strip` higher.
-        // `set_rect` speaks the widget's legacy convention: an inflating widget grows
-        // by the strip itself, one whose label eats into its rect needs it included.
+        // label hangs in the strip above, so the block `set_rect` takes starts `strip`
+        // higher and is `strip` taller.
         let size = self.measure(constraints, ctx);
         let strip = self.label_strip();
-        self.set_rect(origin.x, origin.y - strip, size.width, size.height + strip - self.label_inflation());
+        self.set_rect(origin.x, origin.y - strip, size.width, size.height + strip);
         let host_id = self.base.id();
         Layout::register_embedded_children(&mut self.inner, host_id, ctx);
     }
@@ -1156,17 +1132,16 @@ impl<W: Layout + Paint + Input + 'static> WidgetHost for Adapted<W> {
 
     // --- Legacy structural conventions the adapter owns on the widget's behalf ---
 
-    /// The detached-label convention shared by legacy control widgets: the widget grows past the
-    /// rect its parent assigns to make room for the label above (`ProgressBar`/`Slider`-style
-    /// `set_rect` overrides). Inline-label widgets ([`Layout::inline_label`]) draw the label
-    /// inside their rect and get no inflation. Zero-cost when no label is set.
+    /// The assigned rect is the widget's whole block: the detached label strip (if any)
+    /// at its top, the content below (`content_rect`). One convention for every
+    /// control — a caller sizing a labeled widget by hand adds `label_strip` to the
+    /// content height; `layout` does that for it.
     fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
         let r = Layout::adjust_rect(&self.inner, Rect { x, y, width: w, height: h });
-        let inflation = self.label_inflation();
         self.base.x = r.x;
         self.base.y = r.y;
         self.base.w = r.width;
-        self.base.h = r.height + inflation;
+        self.base.h = r.height;
         // Ungated rect notification (TextBox re-clamps scroll on every assignment, hidden or
         // not — the legacy `set_rect` side effect).
         let landed = Rect { x: self.base.x, y: self.base.y, width: self.base.w, height: self.base.h };
@@ -1180,9 +1155,7 @@ impl<W: Layout + Paint + Input + 'static> WidgetHost for Adapted<W> {
         }
     }
 
-    /// The intrinsic content height — the control below the label. Which legacy
-    /// `set_rect` convention the widget follows (rect inflated by the label, or the
-    /// label eating into it) is `layout`'s business, not the caller's.
+    /// The intrinsic content height — the control below the label.
     fn preferred_height(&self) -> Option<f32> {
         Layout::intrinsic_size(&self.inner).map(|s| s.height)
     }
@@ -1213,7 +1186,7 @@ impl<W: Layout + Paint + Input + 'static> WidgetHost for Adapted<W> {
     /// overrides do today) — unless the widget opts back in
     /// ([`Paint::legacy_focus_highlight`], TextBox), in which case this replicates the
     /// `WidgetHost` default byte-for-byte: primary tint when ctx-focused (or active), secondary
-    /// when hovered, over the row-substituted, side-label-inset span.
+    /// when hovered, over the row-substituted span.
     fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, f32, [f32; 4])> {
         // A forwarding widget (Paginator → its ButtonStrip) serves the forwarded value here —
         // and only here; `all_quads`/`paint_self` gate on `legacy_focus_highlight` instead, so
@@ -1232,9 +1205,8 @@ impl<W: Layout + Paint + Input + 'static> WidgetHost for Adapted<W> {
         } else {
             return None;
         };
-        let label_x = WidgetHost::label_x_offset(self);
-        let hx = if self.base.row_w > 0.0 { self.base.row_x } else { self.base.x } + label_x;
-        let hw = if self.base.row_w > 0.0 { self.base.row_w } else { self.base.w } - label_x;
+        let hx = if self.base.row_w > 0.0 { self.base.row_x } else { self.base.x };
+        let hw = if self.base.row_w > 0.0 { self.base.row_w } else { self.base.w };
         Some((hx, self.base.y, hw, self.base.h, hc))
     }
 
@@ -1541,16 +1513,13 @@ impl<W: Layout + Paint + Input + 'static> WidgetHost for Adapted<W> {
         }
         let (x, y, w, h) = self.rect();
         // Row-hit opt-in ([`Layout::hit_row_rect`]): replicate the legacy `hit_test` default's
-        // geometry — substitute the host-pushed row span and inset by the side label — before
+        // geometry — substitute the host-pushed row span — before
         // the narrow test. The width<=0 reject also comes from that default.
         if Layout::hit_row_rect(&self.inner) {
             if w <= 0.0 || h <= 0.0 {
                 return false;
             }
-            let (mut hx, mut hw) = if self.base.row_w > 0.0 { (self.base.row_x, self.base.row_w) } else { (x, w) };
-            let label_x = WidgetHost::label_x_offset(self);
-            hx += label_x;
-            hw -= label_x;
+            let (hx, hw) = if self.base.row_w > 0.0 { (self.base.row_x, self.base.row_w) } else { (x, w) };
             return Input::hit(&self.inner, Rect { x: hx, y, width: hw, height: h }, px, py);
         }
         Input::hit(&self.inner, Rect { x, y, width: w, height: h }, px, py)
@@ -1698,9 +1667,8 @@ mod tests {
         Rect { x, y, width: w, height: h }
     }
 
-    /// One rhythm for labeled controls whichever legacy convention they follow: the
-    /// preferred height is the CONTENT height for the inflating kind (ProgressBar) and
-    /// the eating kind (Slider, Spinbox, Dropdown) alike, and `layout` places that
+    /// One rhythm for labeled controls: the preferred height is the CONTENT height
+    /// for every kind (ProgressBar, Slider, Spinbox, Dropdown), and `layout` places that
     /// content at the origin with the label strip hanging above it — so a strategy
     /// placing content boxes lines mixed controls up by content, neither squashes a
     /// track to the label's leftovers nor lets a label spill into the gap below.
@@ -1728,7 +1696,7 @@ mod tests {
             let (_, top, _, landed) = w.rect();
             assert!((top - (100.0 - strip)).abs() < 0.01, "{name}: the label hangs above the origin (top {top})");
             assert!((landed - (content + strip)).abs() < 0.01, "{name}: occupied {landed} = content + strip");
-            let painted = landed - crate::widget::label_offset(w);
+            let painted = landed - w.label_strip();
             assert!((painted - content).abs() < 0.01, "{name}: content {painted}, wanted {content}");
         }
     }