Es gibt bestimmt einen besseren Weg, aber mit den folgenden Anpassungen können die CSS-Klassen von Elementgruppen in der "Detailansicht" der Elementgruppe ausgegeben werden um bspw. bei Spaltenansichten die Inhaltselemente nebeneinander anzuordnen.
Es müssen drei Dateien erstellt werden:
/public_html/contao57.com/src/InsertTag/ElementGroupCssClassInsertTag.php
/public_html/contao57.com/templates/backend/data_container/table/view/_record_listing.html.twig
/public_html/contao57.com/templates/backend/data_container/table/view/parent.html.twig
ElementGroupCssClassInsertTag.php
_record_listing.html.twigHTML-Code:<?php declare(strict_types=1); namespace App\InsertTag; use Contao\ContentModel; use Contao\CoreBundle\DependencyInjection\Attribute\AsInsertTag; use Contao\CoreBundle\Framework\ContaoFramework; use Contao\CoreBundle\InsertTag\InsertTagResult; use Contao\CoreBundle\InsertTag\OutputType; use Contao\CoreBundle\InsertTag\ResolvedInsertTag; use Contao\CoreBundle\InsertTag\Resolver\InsertTagResolverNestedResolvedInterface; use Contao\StringUtil; #[AsInsertTag('element_group_cssclass')] class ElementGroupCssClassInsertTag implements InsertTagResolverNestedResolvedInterface { public function __construct(private readonly ContaoFramework $framework) {} public function __invoke(ResolvedInsertTag $insertTag): InsertTagResult { $this->framework->initialize(); $id = (int) $insertTag->getParameters()->get(0); $model = $this->framework->getAdapter(ContentModel::class)->findByPk($id); if (!$model || !$model->cssID) { return new InsertTagResult('', OutputType::text); } $cssId = StringUtil::deserialize($model->cssID, true); return new InsertTagResult($cssId[1] ?? '', OutputType::text); } }
parent.html.twigHTML-Code:{% extends '@Contao/backend/data_container/table/view/_record_listing.html.twig' %} {% set group_css_class = insert_tag('element_group_cssclass::' ~ pid) %} {% set listing_attributes = attrs() .addClass(group_css_class, group_css_class) .mergeWith(listing_attributes|default) %}
optionales Backend CSS:HTML-Code:{% trans_default_domain 'contao_default' %} {% extends '@Contao/backend/data_container/table/view/_base.html.twig' %} {% set view_attributes = attrs() .set('data-controller', 'contao--limit-height', limit_height) .set('data-contao--limit-height-max-value', limit_height, limit_height) .set('data-contao--limit-height-expand-value', 'MSC.expandNode'|trans, limit_height) .set('data-contao--limit-height-collapse-value', 'MSC.collapseNode'|trans, limit_height) .set('data-contao--limit-height-expand-all-value', 'DCA.expandNodes.0'|trans, limit_height) .set('data-contao--limit-height-expand-all-title-value', 'DCA.expandNodes.1'|trans, limit_height) .set('data-contao--limit-height-collapse-all-value', 'DCA.collapseNodes.0'|trans, limit_height) .set('data-contao--limit-height-collapse-all-title-value', 'DCA.collapseNodes.1'|trans, limit_height) %} {% block listing %} {% if has_clipboard_content %} <div id="paste_hint"><p>{{ 'MSC.selectNewPosition'|trans }}</p></div> {% endif %} {% embed '@Contao/backend/data_container/table/view/_record_listing.html.twig' with { listing_attributes: attrs().addClass('parent_view').addClass('as-grid', display_grid), } %} {% trans_default_domain 'contao_default' %} {% block records %} <div class="tl_header hover-div" data-controller="contao--deeplink contao--operations-menu" data-action="contextmenu->contao--operations-menu#open click->contao--check-all#toggleInput"> <div class="tl_content_right"> {# Hinzufügen von CSS-Klassen - start #} {% set group_css_class = insert_tag('element_group_cssclass::' ~ pid) %} {% if group_css_class %} <span class="cssIdClass"> <code class="cssClass"> {% for class in group_css_class|split(' ') %} {% if class %} <span>{{ class }}</span> {% endif %} {% endfor %} </code> </span> {% endif %} {# Hinzufügen von CSS-Klassen - end #} {{ block('select_all_button') }} {{ header_operations|default|raw }} </div> <table class="tl_header_table"> {% for label, value in table_headers %} <tr> <td><span class="tl_label">{{ label }}</span></td> <td>{{ value }}</td> </tr> {% endfor %} </table> </div> {% if records|length %} {% set list_attributes = attrs() .set('data-controller', 'contao--sortable', is_sortable) .set('data-contao--sortable-handle-value', '.drag-handle', is_sortable) .set('data-contao--sortable-parent-mode-value', 'true', is_sortable) .set('data-contao--sortable-request-token-value', contao.request_token, is_sortable) .set('data-id', pid, is_sortable) %} <ul{{ list_attributes }}> {% for record in records %} <li{{ attrs().set('data-id', record.id, is_sortable) }}> {% if record.group_header is defined %} <div class="tl_content_header">{{ record.group_header|raw }}</div> {% endif %} {% set record_attributes = attrs() .addClass('tl_content') .addClass('wrapper_start', record.display.wrapper_start|default) .addClass('wrapper_separator', record.display.wrapper_separator|default) .addClass('wrapper_stop', record.display.wrapper_stop|default) .addClass(['indent', "indent_#{record.display.wrap_level|default}"], record.display.wrap_level|default) .addClass('indent_first', record.display.indent_first|default) .addClass('indent_last', record.display.indent_last|default) .addClass('draft', record.is_draft) .addClass(record.class) .set('data-turbo', 'false') .set('data-action', 'click->contao--check-all#toggleInput') %} <div{{ record_attributes }}> <div class="inside hover-div" data-controller="contao--deeplink contao--operations-menu" data-action="contextmenu->contao--operations-menu#open"> {# Right column #} <div class="tl_content_right" data-turbo="true"> {{ include(('@Contao/backend/data_container/table/view/_record_operations.html.twig')) }} </div> {# Record #} {% if record.legacy_data is defined %} {{ record.legacy_data|raw }} {% else %} {% if display_grid %} {# Record displayed as box with optional preview #} {% set record_label_attributes = attrs() .addClass('cte_type') .addClass('draggable', record.allow_dragging) .addClass(record.state, record.state) %} <div{{ record_label_attributes }}> {% block label %} {% if record.allow_dragging %} <button type="button" class="drag-handle" data-action="keydown->contao--sortable#move"> {{ backend_icon('drag.svg', record.drag_handle_label) }} </button> <div>{{ record.label|raw }}</div> {% else %} {{ record.label|raw }} {% endif %} {% endblock %} </div> {% if record.preview %} <div class="cte_content" data-contao--limit-height-target="node"> <div class="cte_preview" style="contain:paint">{{ record.preview|raw }}</div> </div> {% endif %} {% else %} {# Record displayed flat #} {% set record_label_attributes = attrs() .addClass('tl_content_left') .addClass('draggable', record.allow_dragging) .addClass(record.state, record.state) %} <div{{ record_label_attributes }}> {{ block('label') }} </div> {% endif %} {% endif %} </div> </div> </li> {% endfor %} </ul> {% else %} <p class="tl_empty_parent_view">{{ (panel_active ? 'MSC.noResultWithFilter' : 'MSC.noResult')|trans }}</p> {% endif %} {% endblock %} {% endembed %} {% endblock %}
HTML-Code:/* Vorschau der Elementgruppe - Start */ .cte_preview > :is(.Spalten2, .Spalten3, .Spalten4, .Spalten5, .Spalten6) { & > * { background: var(--content-bg); border: 1px solid var(--border); border-radius: var(--border-radius); padding: 10px; } } .tl_content:has(.Spalten2, .Spalten3, .Spalten4, .Spalten5, .Spalten6) { .cte_preview { background: var(--panel-bg); } [data-contao--limit-height-target][style*=max-height] .limit_toggler { background: linear-gradient(transparent,var(--panel-bg) 60%); } } /* Vorschau der Elementgruppe - Ende */ /* Operations-Icons ausblenden um Platz zu sparen - Start */ .Spalten2, .Spalten3, .Spalten4, .Spalten5, .Spalten6 { & > ul .tl_content_right .operations > ul > li { display: none; &:first-child, &:has([data-label="Verstecken"]), &.operations-menu-container { display: block; } } } /* Operations-Icons ausblenden um Platz zu sparen - Ende */ /* Abstand oben korrigieren - Start */ .Spalten2, .Spalten3, .Spalten4, .Spalten5, .Spalten6 { .tl_content { margin-top: 0; } } /* Abstand oben korrigieren - Ende */ .cte_preview :is(.Spalten2, .Spalten3, .Spalten4, .Spalten5, .Spalten6) { gap: 10px; } .Spalten2 > ul, .cte_preview .Spalten2, .Spalten3 > ul, .cte_preview .Spalten3, .Spalten4 > ul, .cte_preview .Spalten4, .Spalten5 > ul, .cte_preview .Spalten5, .Spalten6 > ul, .cte_preview .Spalten6 { display: grid; gap: 15px; } .Spalten2 > ul, .cte_preview .Spalten2 { grid-template-columns: repeat(2, 1fr); } .Spalten3 > ul, .cte_preview .Spalten3 { grid-template-columns: repeat(3, 1fr); } .Spalten4 > ul, .cte_preview .Spalten4 { grid-template-columns: repeat(4, 1fr); } .Spalten5 > ul, .cte_preview .Spalten5 { grid-template-columns: repeat(5, 1fr); } .Spalten6 > ul, .cte_preview .Spalten6 { grid-template-columns: repeat(6, 1fr); } /* Backend Swiper Vorschau - Start */ .cte_preview .content-custom-swiper .swiper-wrapper { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 10px; } .cte_preview img:not(.backend-icon) { max-width: 100%; } /* Backend Swiper Vorschau - Ende */ div[class^='Spalten'], div[class*=' Spalten'] { &.Spalte1Breite10 ul { --ErsteSpalte: 10fr; } &.Spalte1Breite15 ul { --ErsteSpalte: 15fr; } &.Spalte1Breite20 ul { --ErsteSpalte: 20fr; } &.Spalte1Breite25 ul { --ErsteSpalte: 25fr; } &.Spalte1Breite30 ul { --ErsteSpalte: 30fr; } &.Spalte1Breite35 ul { --ErsteSpalte: 35fr; } &.Spalte1Breite40 ul { --ErsteSpalte: 40fr; } &.Spalte1Breite45 ul { --ErsteSpalte: 45fr; } &.Spalte1Breite50 ul { --ErsteSpalte: 50fr; } &.Spalte1Breite55 ul { --ErsteSpalte: 55fr; } &.Spalte1Breite60 ul { --ErsteSpalte: 60fr; } &.Spalte1Breite65 ul { --ErsteSpalte: 65fr; } &.Spalte1Breite70 ul { --ErsteSpalte: 70fr; } &.Spalte1Breite75 ul { --ErsteSpalte: 75fr; } &.Spalte1Breite80 ul { --ErsteSpalte: 80fr; } &.Spalte1Breite85 ul { --ErsteSpalte: 85fr; } &.Spalte1Breite90 ul { --ErsteSpalte: 90fr; } &.Spalte1Breite95 ul { --ErsteSpalte: 95fr; } &.Spalte2Breite10 ul { --ZweiteSpalte: 10fr; } &.Spalte2Breite15 ul { --ZweiteSpalte: 15fr; } &.Spalte2Breite20 ul { --ZweiteSpalte: 20fr; } &.Spalte2Breite25 ul { --ZweiteSpalte: 25fr; } &.Spalte2Breite30 ul { --ZweiteSpalte: 30fr; } &.Spalte2Breite35 ul { --ZweiteSpalte: 35fr; } &.Spalte2Breite40 ul { --ZweiteSpalte: 40fr; } &.Spalte2Breite45 ul { --ZweiteSpalte: 45fr; } &.Spalte2Breite50 ul { --ZweiteSpalte: 50fr; } &.Spalte2Breite55 ul { --ZweiteSpalte: 55fr; } &.Spalte2Breite60 ul { --ZweiteSpalte: 60fr; } &.Spalte2Breite65 ul { --ZweiteSpalte: 65fr; } &.Spalte2Breite70 ul { --ZweiteSpalte: 70fr; } &.Spalte2Breite75 ul { --ZweiteSpalte: 75fr; } &.Spalte2Breite80 ul { --ZweiteSpalte: 80fr; } &.Spalte2Breite85 ul { --ZweiteSpalte: 85fr; } &.Spalte2Breite90 ul { --ZweiteSpalte: 90fr; } &.Spalte2Breite95 ul { --ZweiteSpalte: 95fr; } &.Spalte3Breite10 ul { --DritteSpalte: 10fr; } &.Spalte3Breite15 ul { --DritteSpalte: 15fr; } &.Spalte3Breite20 ul { --DritteSpalte: 20fr; } &.Spalte3Breite25 ul { --DritteSpalte: 25fr; } &.Spalte3Breite30 ul { --DritteSpalte: 30fr; } &.Spalte3Breite35 ul { --DritteSpalte: 35fr; } &.Spalte3Breite40 ul { --DritteSpalte: 40fr; } &.Spalte3Breite45 ul { --DritteSpalte: 45fr; } &.Spalte3Breite50 ul { --DritteSpalte: 50fr; } &.Spalte3Breite55 ul { --DritteSpalte: 55fr; } &.Spalte3Breite60 ul { --DritteSpalte: 60fr; } &.Spalte3Breite65 ul { --DritteSpalte: 65fr; } &.Spalte3Breite70 ul { --DritteSpalte: 70fr; } &.Spalte3Breite75 ul { --DritteSpalte: 75fr; } &.Spalte3Breite80 ul { --DritteSpalte: 80fr; } &.Spalte3Breite85 ul { --DritteSpalte: 85fr; } &.Spalte3Breite90 ul { --DritteSpalte: 90fr; } &.Spalte3Breite95 ul { --DritteSpalte: 95fr; } &.Spalte4Breite10 ul { --VierteSpalte: 10fr; } &.Spalte4Breite15 ul { --VierteSpalte: 15fr; } &.Spalte4Breite20 ul { --VierteSpalte: 20fr; } &.Spalte4Breite25 ul { --VierteSpalte: 25fr; } &.Spalte4Breite30 ul { --VierteSpalte: 30fr; } &.Spalte4Breite35 ul { --VierteSpalte: 35fr; } &.Spalte4Breite40 ul { --VierteSpalte: 40fr; } &.Spalte4Breite45 ul { --VierteSpalte: 45fr; } &.Spalte4Breite50 ul { --VierteSpalte: 50fr; } &.Spalte4Breite55 ul { --VierteSpalte: 55fr; } &.Spalte4Breite60 ul { --VierteSpalte: 60fr; } &.Spalte4Breite65 ul { --VierteSpalte: 65fr; } &.Spalte4Breite70 ul { --VierteSpalte: 70fr; } &.Spalte4Breite75 ul { --VierteSpalte: 75fr; } &.Spalte4Breite80 ul { --VierteSpalte: 80fr; } &.Spalte4Breite85 ul { --VierteSpalte: 85fr; } &.Spalte4Breite90 ul { --VierteSpalte: 90fr; } &.Spalte4Breite95 ul { --VierteSpalte: 95fr; } &.Spalte5Breite10 ul { --FünfteSpalte: 10fr; } &.Spalte5Breite15 ul { --FünfteSpalte: 15fr; } &.Spalte5Breite20 ul { --FünfteSpalte: 20fr; } &.Spalte5Breite25 ul { --FünfteSpalte: 25fr; } &.Spalte5Breite30 ul { --FünfteSpalte: 30fr; } &.Spalte5Breite35 ul { --FünfteSpalte: 35fr; } &.Spalte5Breite40 ul { --FünfteSpalte: 40fr; } &.Spalte5Breite45 ul { --FünfteSpalte: 45fr; } &.Spalte5Breite50 ul { --FünfteSpalte: 50fr; } &.Spalte5Breite55 ul { --FünfteSpalte: 55fr; } &.Spalte5Breite60 ul { --FünfteSpalte: 60fr; } &.Spalte5Breite65 ul { --FünfteSpalte: 65fr; } &.Spalte5Breite70 ul { --FünfteSpalte: 70fr; } &.Spalte5Breite75 ul { --FünfteSpalte: 75fr; } &.Spalte5Breite80 ul { --FünfteSpalte: 80fr; } &.Spalte5Breite85 ul { --FünfteSpalte: 85fr; } &.Spalte5Breite90 ul { --FünfteSpalte: 90fr; } &.Spalte5Breite95 ul { --FünfteSpalte: 95fr; } &.Spalte6Breite10 ul { --SechsteSpalte: 10fr; } &.Spalte6Breite15 ul { --SechsteSpalte: 15fr; } &.Spalte6Breite20 ul { --SechsteSpalte: 20fr; } &.Spalte6Breite25 ul { --SechsteSpalte: 25fr; } &.Spalte6Breite30 ul { --SechsteSpalte: 30fr; } &.Spalte6Breite35 ul { --SechsteSpalte: 35fr; } &.Spalte6Breite40 ul { --SechsteSpalte: 40fr; } &.Spalte6Breite45 ul { --SechsteSpalte: 45fr; } &.Spalte6Breite50 ul { --SechsteSpalte: 50fr; } &.Spalte6Breite55 ul { --SechsteSpalte: 55fr; } &.Spalte6Breite60 ul { --SechsteSpalte: 60fr; } &.Spalte6Breite65 ul { --SechsteSpalte: 65fr; } &.Spalte6Breite70 ul { --SechsteSpalte: 70fr; } &.Spalte6Breite75 ul { --SechsteSpalte: 75fr; } &.Spalte6Breite80 ul { --SechsteSpalte: 80fr; } &.Spalte6Breite85 ul { --SechsteSpalte: 85fr; } &.Spalte6Breite90 ul { --SechsteSpalte: 90fr; } &.Spalte6Breite95 ul { --SechsteSpalte: 95fr; } &.Spalten2 ul { grid-template-columns: minmax(var(--Mindestbreite, 200px), var(--ErsteSpalte, 1fr) ) minmax(var(--Mindestbreite, 200px), var(--ZweiteSpalte, 1fr) ); } &.Spalten3 ul { grid-template-columns: minmax(var(--Mindestbreite, 200px), var(--ErsteSpalte, 1fr) ) minmax(var(--Mindestbreite, 200px), var(--ZweiteSpalte, 1fr) ) minmax(var(--Mindestbreite, 200px), var(--DritteSpalte, 1fr) ); } &.Spalten4 ul { grid-template-columns: minmax(var(--Mindestbreite, 200px), var(--ErsteSpalte, 1fr) ) minmax(var(--Mindestbreite, 200px), var(--ZweiteSpalte, 1fr) ) minmax(var(--Mindestbreite, 200px), var(--DritteSpalte, 1fr) ) minmax(var(--Mindestbreite, 200px), var(--VierteSpalte, 1fr) ); } &.Spalten5 ul { grid-template-columns: minmax(var(--Mindestbreite, 200px), var(--ErsteSpalte, 1fr) ) minmax(var(--Mindestbreite, 200px), var(--ZweiteSpalte, 1fr) ) minmax(var(--Mindestbreite, 200px), var(--DritteSpalte, 1fr) ) minmax(var(--Mindestbreite, 200px), var(--VierteSpalte, 1fr) ) minmax(var(--Mindestbreite, 200px), var(--FünfteSpalte, 1fr) ); } &.Spalten6 ul { grid-template-columns: minmax(100px, var(--ErsteSpalte, 1fr) ) minmax(100px, var(--ZweiteSpalte, 1fr) ) minmax(100px, var(--DritteSpalte, 1fr) ) minmax(100px, var(--VierteSpalte, 1fr) ) minmax(100px, var(--FünfteSpalte, 1fr) ) minmax(100px, var(--SechsteSpalte, 1fr) ); } }
Danach muss noch der Cache geleert werden.
Damit können dann, mit eigenem CCS im Backend, folgende Ansichten umgesetzt werden:
Bildschirmfoto 2026-05-12 um 10.35.55.png
Bildschirmfoto 2026-05-12 um 10.36.24.png
So habe ich es auch in der Erweiterung Contao-Custom-Backend-Settings umgesetzt:
https://github.com/heimseiten/contao...ettings-bundle

Zitieren
(Ist dann ja wohl nicht geplant)