Skip to main content
Light Dark System

Layout Controller

Et2LayoutController Controller

Layout

Any widget that implements Et2LayoutHost ( {{ classesImplementing(“Et2LayoutHost”) | safe }} ) can use the layout attribute (stack, 2-column, edit, absolute) to control layout behaviour. edit is the same as 2-column with a few tweaks for edit dialogs, notably the header & footer stay full-width. stack is a simple stack (1-column) of widgets.

2-column (wide container)

two-column layout

Collapsed to 1 column

two-column collapsed layout

Label wrapping

Wrapped lable layout

CSS Variables

--column-min-width

  • Default: 26rem
  • Used by 2-column and edit to determine minimum column width before collapsing to 1 column.
  • --column-min-width and --label-width together determine when the label / widget break point is reached.

Example:

et2-template[layout="edit"] {
  --column-min-width: 24rem;
}

--collapse-width

  • Default: 600px
  • Fallback container-query collapse point for narrower or legacy behavior.

Example:

et2-template[layout="2-column"] {
  --collapse-width: 680px;
}

Popups inside a layout

2-column and edit make their host a container query container so the grid can decide for itself when to collapse. et2-customfields does the same regardless of its layout. That breaks any hoisted Shoelace popup opened inside them: Floating UI reads a container-type ancestor as the containing block for the popup’s position: fixed, no browser does (checked on Chrome 152 and Firefox 142), and the dropdown ends up scrollTop - containerTop away from its field and clamped to the height of the panel it sits in.

Et2TopLayerPopupController, created alongside Et2LayoutController, moves those popups into the top layer while they are open, where both agree the coordinates are viewport coordinates. Nothing has to be done per widget, and the container itself is untouched - giving it real containment (contain: layout) would fix the dropdowns and break everything else that escapes a scrolling panel with position: fixed, including the rich text editor’s menus.

Grow rows

For stack, 2-column, and edit, row heights are managed by the layout to distribute extra vertical space.

  • et2-tabbox grows automatically
  • [grow] supports optional numeric factors (grow="2" etc.) to distribute extra space proportionally.

Widget Implementation

Et2LayoutController applies layout strategies (stack, 2-column, edit, absolute) to layout hosts and keeps grow rows sized correctly as content resizes.

Most column behavior is implemented in LESS (kdots/css/src/layouts/*.less), while this controller and Et2LayoutStrategies.ts handle runtime behavior like grow-row sizing and strategy lifecycle.

Row sizing is recalculated via ResizeObserver + requestAnimationFrame

Usage

Any widget that wants layout behavior should implement Et2LayoutHost and instantiate the controller.

import {Et2LayoutController, Et2LayoutHost} from "./Et2LayoutController";
import type {Et2LayoutName} from "./Et2LayoutStrategies";

export class MyLayoutHost extends HTMLElement implements Et2LayoutHost
{
	layout : Et2LayoutName = "2-column";
	private _layoutController = new Et2LayoutController(this);
}

At lifecycle updates, the controller:

  1. Looks up the strategy from layout
  2. Cleans up the previous strategy (if changed)
  3. Applies the active strategy to current children

2-column / edit responsive behavior

2-column and edit share the same grid base mixin in:

  • kdots/css/src/layouts/grid-base.less

The base grid uses:

grid-template-columns: repeat(auto-fit, minmax(var(--column-min-width, 26rem), 1fr));

This keeps two columns while there is enough room, then collapses to one column before columns become too narrow.

In one-column mode, label/input parts are normalized together so widgets do not wrap inconsistently per-field:

  • ::part(form-control-label) -> width: 100%, flex-basis: 100%, margin-right: 0
  • ::part(form-control-input) -> flex-basis: 100%
  • ::part(form-control-help-text) -> left: 0

Widgets implementing Et2LayoutHost

{{ classesImplementing(“Et2LayoutHost”) | safe }}

Used by

These widgets create and host this controller.