---
title: "Custom Layout"
slug: "custom-layout"
category: "Widgets"
reactComponent: "ArcWidgetLayout"
reactTier: "mini"
reactSince: "0.1.0-alpha.5"
reactExample: "layout.tsx"
locale: "es"
hosts: "vue"
---

# Custom Layout

## AI Defaults (read first)

- **Dashboard wrappers**: MUST have `fullscreen: true`, `fullscreenMode: if isAdminMode() then "" else "full" end`, `height: "100%"`.
- **embedded**: `false` for standalone/dashboard layouts, `true` when nested inside another widget.
- **direction**: Always set explicitly (`"horizontal"` or `"vertical"`).
- **Block widths**: At least one block should use `width: "fraction"` to fill remaining space.
- **Scrollable areas**: Use `height: "fraction"` + `scrollSettings: { scrollY: true }` for content below fixed headers.
- **uniqueId**: Required, no special characters.
- **Sticky headers**: Need `overflow-x: hidden` on parent (NOT `overflow: hidden`), background-color, and z-index.
- **Two for-loops**: NEVER place two for-loops side by side in blocks – each needs its own wrapper layout.
- **Nested widgets (Next-Gen)**: In `blocks[].value`, use `{ widget: "arc-widget-…", uid: "…", data: { … } }` — not `arcCustomXxx({ … })`. Requires current `bundle.js`; legacy Ninox-formula widgets do not support this. See `docs/patterns/nested-widget-descriptors.md`.

# Custom Layout

![](https://framerusercontent.com/images/aFEbrJb3sdVWKcqtanFF6Fd1L1I.png)

Con el widget Layout diseñas interfaces sin rejilla de tabla — un contenedor flexible (como Flexbox): alineación, espaciado, colores, widgets anidados.


Ya sea que quieras colocar secciones sencillas una junto a otra, construir una tarjeta informativa con fondo gráfico o estructurar elementos interactivos en una página — `arcCustomLayout` es tu herramienta para ello.

**Con esto controlas:**

- Orientación (horizontal / vertical)
- Espaciado, colores, imágenes de fondo
- Comportamiento flexible según tamaño de pantalla
- Estilos individuales por bloque
- Interacciones (p. ej. acciones de clic en áreas)

A diferencia de `arcCustomGrid`, pensado para estructuras bidimensionales (filas *y* columnas), el Layout trabaja deliberadamente de forma **unidimensional**: determinas si los contenidos se muestran **horizontalmente (uno junto a otro)** o **verticalmente (uno debajo de otro)** — incluyendo alineación, espaciado, colores y adaptaciones responsivas.

**🎯 Lo que puedes hacer con esto:**

- **Layouts tipo tarjeta** con título, descripción y botones uno junto a otro
- **Áreas hero** con un gran bloque de texto + imagen uno junto a otro (horizontal)
- **Bloques de formulario verticales** con espaciado específico y color de fondo
- **Bloques interactivos** con funciones de clic (popup, update, eliminar, etc.)
- **Combinación de varias fuentes de datos** en un layout dinámico

## Código de aplicación

### Inicio rápido – Layout base simple

Un ejemplo compacto con el que puedes empezar directamente. Ideal cuando quieres colocar algunos contenidos uno junto a otro (`direction: "horizontal"`) o uno debajo de otro (`direction: "vertical"`) — por ejemplo bloques de texto, KPIs o botones. Los parámetros más importantes, como alineación, ancho y espaciado, ya están definidos y se pueden adaptar fácilmente.

✅ Uso rápido
✅ Flexible y adaptable
✅ Perfecto para copiar y empezar


```javascript
arcCustomLayout({
    uniqueId: "Beispiel "+ Nr,
    embedded: true,
    direction: "horizontal",
    alignX: "left",
    alignY: "center",
    width: "100%",
    height: "auto",
    gap: "10px",
    backgroundColor: "",
    paddingY: "",
    paddingX: "",
    styles: "",
    scrollSettings: {
            scrollY: false,
            scrollX: false,
        },
    blocks: [{
            width: "fraction",
            height: "auto",
            lineHeight: "",
            alignX: "left",
            color: "",
            styles: "",
            value: ""
                }, {
            width: "auto",
            height: "auto",
            lineHeight: "",
            alignX: "left",
            color: "",
            value: "",

            }]
    })
```

### Layout completo – Layout ancho en el contenedor de un registro

Este setup está pensado para layouts que **no están integrados** y se extienden **por toda el área de contenido de un registro o de una página**. Especialmente adecuado para **dashboards, páginas de resumen o pantallas de inicio** que deban resultar ordenadas y enfocadas dentro de Ninox.

✅ Ancho completo dentro de la pestaña o del registro
✅ Sin integrar — el layout se sostiene por sí mismo
✅ Ideal para componentes más grandes, paneles o páginas de resumen

### Código con todos los parámetros


```javascript
arcCustomLayout({
    uniqueId: Nr,
    embedded: true,
    fullscreen: false, // Gibst du hier "true" an, wird das Layout in dem ganzen Datensatz eingebunden
    fullscreenMode: "full",
    page: false, // Gibst du an, wenn du den fullscreen auf einer Tabelle einstellen möchtest.
    showAdminTools: false,
    hideHeaderIcons: true,
    direction: "vertical",
    alignX: "center",
    alignY: "center",
    width: "",
    height: "",
    gap: "5px",
    backgroundColor: "#ccc",
    paddingY: "10px",
    paddingX: "5px",
    blocks: [{
            width: "",
            height: "auto",
            lineHeight: "1.6",
            alignX: "left",
            value: "",
            color: "#000",
            styles: "", // Hier kannst du eigene CSS Styles hinterlegen.
            clickAction: {
                    recordId: Nr,
                    type: "popup"
                            }
                }, {
            width: "",
            height: "auto",
            lineHeight: "",
            alignX: "left",
            value: "",
            color: "", // Fallback: "#000"
            clickAction: {
                type: "openFullscreen",
                recordId: raw(record(Firmen,1).Nr)
                },
            }]
    })
```

**💡Nota:** También puedes anidar varios layouts entre sí. Así puedes construir interfaces complejas y hacer que Ninox resulte aún más claro.

<iframe src="https://www.youtube.com/embed/vVciopAN3Dw?iv_load_policy=3&rel=0&modestbranding=1&playsinline=1&autoplay=1&mute=1" data-thumbnail="Medium Quality" frameborder="0" allow="presentation; fullscreen; accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"></iframe>

## Ajustes generales

Los parámetros de arriba indican los ajustes generales de todo tu layout. Es, por así decirlo, el contenedor más externo de tu layout.

### uniqueId

***uniqueId*** es la denominación individual de tu layout. Asegúrate de asignar aquí un título único. Esto es importante si quieres mostrar varios layouts en una página / en una tabla y que no se sobrescriban entre sí.

```javascript
uniqueId: "Layout container",
```

### embedded

***embedded*** indica si tu layout debe integrarse en otro widget.

```javascript
embedded: true, // Ja, es wird in einem anderen Widget integriert
embedded: false, // Nein, es ist nicht in anderen Widgets integriert
embedded: false, // default: false
```

### fullscreen

***fullscreen*** determina si tu layout debe mostrarse en toda la pantalla (registro / página).

```javascript
fullscreen: true,
fullscreen: false, // default: false
```

### ninoxVersion

***ninoxVersion*** indica la versión de Ninox Web en la que te encuentras (p. ej. `"3.13"`). Para un comportamiento de pantalla completa correcto, establécelo; los valores a partir de `3.13` también aplican a versiones más nuevas (p. ej. `3.15`).

```javascript
ninoxVersion: "3.13",
```

### page

***page*** define si integras el layout en una página o en una tabla. `true` = página, `false` = tabla / registro. Antes `fullscreenMode: "full"` solo actuaba en páginas; con `page: false` la pantalla completa también actúa sobre registros de una tabla.

```javascript
page: true, // Page
page: false, // Tabelle / Record
```

### fullscreenMode

***fullscreenMode***: `"full"` oculta el header y las pestañas de Ninox. Úsalo junto con `fullscreen: true` y `ninoxVersion`.

```javascript
fullscreenMode: "full",
fullscreenMode: "", // default: kein Full-Chrome-Hide
```

### showAdminTools

***showAdminTools*** controla si el botón de admin (llave de Ninox) permanece visible para los administradores.

```javascript
showAdminTools: true,
showAdminTools: false, // default: false in vielen Dashboard-Beispielen
```

### scrollSettings

***scrollSettings*** controla si el layout tiene scroll vertical (`scrollY`) u horizontal (`scrollX`). El layout recuerda la posición de scroll — tras triggers que recargan Ninox brevemente, se conserva la última posición.

#### scrollY

```javascript
scrollSettings: {
  scrollY: true,
  scrollX: false,
}
```

#### scrollX

```javascript
scrollSettings: {
  scrollY: false,
  scrollX: true,
}
```

### direction

***direction*** indica cómo se deben organizar tus demás contenidos (blocks).


```javascript
direction: "vertical", // Alle Inhalte werden untereinander angeordnet.
direction: "horizontal", // Alle Inhalte werden nebeneinander angeordnet.
```

<iframe src="https://www.youtube.com/embed/7pxvuLLhbkY?iv_load_policy=3&rel=0&modestbranding=1&playsinline=1&autoplay=1&mute=1" data-thumbnail="Medium Quality" frameborder="0" allow="presentation; fullscreen; accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"></iframe>### alignX

**alignX:** indica cómo se organizan tus contenidos en el eje X.


```javascript
alignX: "center", // zentriert // default: center
alignX: "left", // linksbündig
alignX: "right", // rechtsbündig
alignX: "between", // teilt Blocks auf Gesamtbreite auf
```

### alignY

**alignY:** indica cómo se organizan tus contenidos en el eje Y.


```javascript
alignY: "center", // zentriert // default: center
alignY: "top", // oben angeordnet
alignY: "bottom", // unten angeordnet
alignY: "between", // teilt Blocks auf Gesamthöhe auf
```

<iframe src="https://www.youtube.com/embed/VW8ycZiNM2Y?iv_load_policy=3&rel=0&modestbranding=1&playsinline=1&autoplay=1&mute=1" data-thumbnail="Medium Quality" frameborder="0" allow="presentation; fullscreen; accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"></iframe>### width

***width*** indica el ancho.


```javascript
width: "100%", // Prozent-Werte. Hier: volle Breite.
width: "100px", // Pixel-Werte
```

### height

***height*** indica la altura.


```javascript
height: "auto", // Automatisch an den Inhalt angepasste Höhe.
height: "300px", // Pixel-Werte
```

### gap

Con ***gap*** determinas cuánto espacio debe haber entre tus ***blocks***.

![](https://framerusercontent.com/images/Vkymdh6PJgSU5aZU3b6Bun2Uk4s.png)


```javascript
gap: "5px", // Pixel-Werte
```

### actions (clic en todo el layout)

Con ***actions*** a nivel de widget (no dentro de un bloque) haces que **todo el layout sea clicable** — útil, por ejemplo, para un wrapper de tarjeta o un dimmer móvil, sin necesidad de un bloque propio para ello.

```javascript
actions: [{
    recordId: Nr,
    type: "popup"
}]
```

Varias entradas se ejecutan de forma consecutiva, `dataBag` funciona igual que en cualquier otra action de tipo `update`/`create`:

```javascript
actions: [{
    type: "update",
    recordId: Nr,
    fieldId: fieldId(Nr, "helper_mainContentState"),
    dataBag: {
        base: helper_mainContentState,
        patch: { sidebarOpen: "" }
    }
}]
```

**Importante:** Si un bloque individual tiene sus propias `actions` o `clickAction`, su clic **no** se propaga adicionalmente a las `actions` del widget (sin doble disparo) — solo los clics fuera de un bloque configurado activan las `actions` del widget.

Detalles sobre las actions de bloque: [`actions Block`](#actions-block-empfohlen) más abajo.

### loading (nivel de widget)

Con **`loading`** controlas si aparece un spinner sobre el área de destino al ejecutar las `actions` del widget. Sin `loading` (o con `show: false`) la action se ejecuta como hasta ahora — solo sin feedback visual.

```javascript
loading: {
    show: true,
    target: "layout",              // Default: "layout" | { blockId: "sidebar" }
    indicator: { type: "spinner", color: "#4970ff", width: "32px", height: "32px" },
    dim: true,                     // halbtransparenter Hintergrund über dem Zielbereich
    minDuration: 300,              // Mindest-Anzeigedauer in ms
    hideOnFinish: true             // Default: true
}
```

**`target`** a nivel de widget:

| Wert | Bedeutung |
|---|---|
| `"layout"` (Default) | Spinner sobre todo el layout |
| `{ blockId: "sidebar" }` | Spinner solo sobre el bloque con este `blockId` |

**`indicator.type`:** `dots` | `spinner` | `pulse` — igual que en el botón.

#### overlay (modal a pantalla completa)

Con **`loading.overlay`** aparece, en lugar del overlay local de bloque/layout, un **modal a pantalla completa** sobre toda la vista — idéntico al del botón (`arcModalWindows`). `target` y `dim` se ignoran en este modo.

| `overlay` | Verhalten |
|-----------|-----------|
| `true` | Solo backdrop + spinner |
| `{ title, message }` | Mensaje fijo en la tarjeta del diálogo |
| `{ sequences: [...] }` | Mensajes dinámicos (tiempo y/o booleanos de Ninox) |

```javascript
loading: {
    show: true,
    overlay: {
        title: { value: "Wird gespeichert…", styles: "font-size: 18px; font-weight: 700;" },
        message: "Bitte einen Moment warten.",
        indicator: { type: "spinner", color: "#4970ff", width: "32px", height: "32px" }
    },
    minDuration: 300,
    hideOnFinish: true
}
```

API completa de overlay (`width`, `height`, `backgroundColor`, `sequences`, `when`, `priority`): consulta [Button Loading — overlay](../mini_widgets/buttons.md#overlay-zusätzlich).


## Ajustes en el bloque de layout

Los parámetros dentro de un bloque determinan los valores correspondientes a cada bloque de layout. También puedes añadir más bloques (separados por comas).


```javascript
blocks: [{ // Beginn Block 1
            blockId: "sidebar",   // optional — stabile ID für Loading-Targeting
            width: "",
            height: "auto",
            lineHeight: "1.6", // default: normal
            alignX: "left",
            color: "#000", // Schriftfarbe von Text im Layoutblock
            backgroundColor: "#333", // Hintergrundfarbe von Text im Layoutblock
            styles: "", // Hier kannst du eigene CSS Styles hinterlegen.
            value: "", // Hier gibst du den Wert an, der im Block ausgegeben werden soll.
            clickAction: {
                    recordId: Nr,
                    type: "popup"
                            }
                },
             { // Beginn Block 2
            width: "",
            height: "auto",
            lineHeight: "1.6", // default: normal
            alignX: "left",
            color: "#000",
            backgroundColor: "#ccc",
            styles: "", // Hier kannst du eigene CSS Styles hinterlegen.
            value: "",
            clickAction: {
                    recordId: Nr,
                    type: "popup"
                            }
                }
            }]
```

### width

***width*** y ***height*** indican el ancho y la altura del bloque de layout.


```javascript
width: "auto", // Automatisch an den Inhalt angepasste Weite. // default:
width: "300px", // Pixel-Werte oder Prozenz-Werte

height: "auto", // Automatisch an den Inhalt angepasste Höhe. // default:
height: "300px", // Pixel-Werte oder Prozenz-Werte
```

### lineHeight

Con ***lineHeight*** determinas el espacio entre líneas en el layout.


```javascript
lineHeight: "1.6", // default: normal
```

### alignX

**alignX:** indica cómo se organizan tus contenidos en el eje X dentro del bloque de layout.


```javascript
alignX: "center", // zentriert // default: center
alignX: "left", // linksbündig
alignX: "right", // rechtsbündig
alignX: "between", // teilt Blocks auf Gesamtbreite auf
```

### color & backgroundColor

Con ***color*** y ***backgroundColor*** determinas el color del texto y el color de fondo del texto dentro de tu bloque de layout.


```javascript
color: "#33AAFF", //  default: #000
backgroundColor: "#ccc", // default: no default color
```

### styles

En ***styles*** puedes añadir tus propios estilos CSS al bloque de layout.


```javascript
styles: "", // z.B. font-weight: 700;
```

### value

***value*** es el valor que se representa dentro del bloque de layout. Aquí puedes indicar, por ejemplo, texto, campos de Ninox o customWidgets.


```javascript
value: "Überschrift", // einfacher Text
value: Titel, // Ninox-Feld
value: arcCustomButton({...}) // Top-Level oder Legacy: Funktionsaufruf

value: "", // default: Gibst du keinen Wert an, wird nichts ausgegeben ;)
```

#### Widgets anidados (Next-Gen, notación de objeto)

Cuando un bloque contiene **otro arcWidget** y ambos provienen del **`bundle.js`** actual, puedes establecer un **objeto descriptor** en lugar de `arcCustomButton({ … })`:

```javascript
value: {
    widget: "arc-widget-button",
    uniqueId: "block-btn-" + Nr,   // uid: "block-btn-" + Nr ist gleichwertig
    data: {
        title: "Speichern",
        actions: [{ type: "update", recordId: Nr, field: fieldId(Nr, "Status"), value: "done" }]
    }
}
```

- **`widget`**: ID interna (`arc-widget-layout`, `arc-widget-button`, …) — no el nombre de la función
- **`uniqueId`** o **`uid`**: ambos aceptados — top-level, **no** dentro de `data`
- **`data`**: los mismos parámetros que en la llamada de función

**Importante:** Solo funciona con **widgets Next-Gen** en el bundle. Las **fórmulas legacy de Ninox** antiguas (todavía no desplegadas como widget JS) no entienden estos objetos.

En detalle: [`nested-widget-descriptors`](../patterns/nested-widget-descriptors.md)

#### Bloque actions (recomendado)

***actions*** describe tus acciones en los bloques respectivos como array. Varias entradas se ejecutan de forma consecutiva — p. ej., primero vaciar el estado, luego abrir el registro.

```javascript
actions: [{
    recordId: Nr,
    type: "update",
    fieldId: fieldId(Nr, "helper_uiState"),
    dataBag: {
        base: helper_uiState,
        patch: { activeTab: "details" }
    }
}, {
    recordId: Nr,
    type: "popup"
}]
```

***openTable*** abre una vista de tabla en Ninox:

```javascript
actions: [{
    type: "openTable",
    tableId: "A"
}]
```

***openUrl*** abre una URL externa:

```javascript
actions: [{
    type: "openUrl",
    value: "https://www.arc-rider.com"
}]
```

**Tipos de action** (igual que en Button y otros widgets):

Trigger `update` repetido con `polling` + booleano de fórmula `active`: consulta [`action-polling`](../patterns/action-polling.md).

```javascript
// Folgende Actions benötigen die zwei Parameter: "recordId" und "type"
"popup"
"delete"
"openRecord"
"fullscreen"

// Folgende Action benötigt die 4 Parameter: "recordId", "type", "field", "value"
"update"

// Folgende Action benötigt 2 Parameter: "type", "value"
"openUrl"

// Folgende Action benötigt 2 Parameter: "type", "tableId"
"openTable"

// Folgende Actions benötigen nur den Parameter "type"
"closeFullScreen"
"closeRecord"
"closeAllRecords"
```

Actualizaciones parciales de estado mediante `dataBag` en actions de tipo `update`: [`data-bag-actions`](../patterns/data-bag-actions.md).

#### Bloque loading

Con **`loading`** a nivel de bloque controlas el spinner al ejecutar las `actions` del bloque. El destino puede ser el propio bloque, otro bloque o todo el layout.

```javascript
blockId: "save-panel",
actions: [{ type: "update", recordId: Nr, field: fieldId(Nr, "Status"), value: "done" }],
loading: {
    show: true,
    target: "block",               // Default: "block" | "layout" | { blockId: "other" }
    indicator: { type: "spinner", color: "#4970ff" },
    dim: true,
    minDuration: 300,
    hideOnFinish: true
}
```

**`target`** a nivel de bloque:

| Wert | Bedeutung |
|---|---|
| `"block"` (Default) | Spinner sobre el bloque pulsado |
| `"layout"` | Spinner sobre todo el layout |
| `{ blockId: "sidebar" }` | Spinner sobre otro bloque (mediante `blockId`) |

**`blockId`** es opcional, pero necesario si quieres dirigirte a otro destino mediante `{ blockId: "…" }`. El ID debe ser único en la página — `{ blockId: "…" }` busca primero en el mismo layout, y luego en toda la página (así que también funciona si el bloque de destino está en un layout superior que anida este layout mediante `blocks[].value: { widget: "arc-widget-layout", … }`).

**`loading.overlay`** también funciona a nivel de bloque — entonces aparece el modal a pantalla completa en lugar del overlay local (consulta [overlay (nivel de widget)](#overlay-fullscreen-modal) más arriba).

Mientras una action está en ejecución, los clics repetidos en la misma área de destino quedan bloqueados (protección de reentrada).

#### Bloque clickAction (Legacy)

El antiguo campo individual **`clickAction`** sigue siendo compatible y se ejecuta como una entrada en `actions`. Para configuraciones nuevas usa **`actions`**.

```javascript
clickAction: {
    recordId: Nr,
    type: "update",
    field: "",
    value: ""
}
```

```javascript
clickAction: {
    type: "openTable",
    tableId: "A"
}
```

```javascript
clickAction: {
    type: "openUrl",
    value: "https://www.arc-rider.com"
}
```


## Vue

El objeto `data` se pasa como prop al componente Vue.

```vue
<ArcWidgetLayout id="shell" :data="layoutData" @action="handleAction" />
```
