CBD stands for Component-Based Design.
Carbon.CBD provides building blocks for complex, multi-item content elements in Neos CMS, such as sliders, maps and tabs. It separates the editor's content-collection view from the live presentation and adds a toggle to the Neos UI.
- Reusable
Carbon.CBD:ComponentFusion prototype - Separate live and edit renderers
- Synchronized live/edit toggles in the inline toolbar and inspector
- Empty-state handling for empty content collections
- Custom attributes for the backend wrapper and edit renderer
- Translation auto-include for the UI
neos/neos^8.4
composer require carbon/cbdThe package automatically includes its Fusion prototypes and translations through Configuration/Settings.Neos.yaml.
Make the parent element inherit from Carbon.CBD:Mixin.Element and its items from Carbon.CBD:Mixin.Element.Item:
"Vendor.Site:Content.Slider":
superTypes:
"Carbon.CBD:Mixin.Element": true
ui:
label: "Slider"
constraints:
nodeTypes:
"*": false
"Vendor.Site:Content.Slider.Item": true
"Vendor.Site:Content.Slider.Item":
superTypes:
"Carbon.CBD:Mixin.Element.Item": true
ui:
label: "Slide"Carbon.CBD:Mixin.Element is a content collection and allows no children by default. Define the allowed item types explicitly, as shown above. CBD item nodes are also blocked on regular Neos.Neos:ContentCollection nodes.
The mixin registers the Carbon.CBD/InspectorButton inspector view under the cbd key. Assign the view to an inspector group and set its position on the concrete element node type:
"Vendor.Site:Content.Slider":
ui:
inspector:
groups:
presentation:
label: "Presentation"
position: 10
views:
cbd:
group: "presentation"
position: 10The inspector toggle and the button in the inline toolbar stay synchronized. Both are only shown for CBD elements that contain child nodes.
Use Carbon.CBD:Component as the base prototype for the element:
prototype(Vendor.Site:Content.Slider) < prototype(Carbon.CBD:Component) {
live = Carbon.CBD:ChildContentRenderer
edit = Carbon.CBD:ContentCollectionRenderer
}The default values are:
live:Carbon.CBD:ChildContentRenderer, which renders the child nodes without content-element wrappersedit:Carbon.CBD:ContentCollectionRenderer, which renders the editable content collection and its empty statewrapperAttributes: attributes for the outer wrapper rendered in the Neos backendeditAttributes: attributes added to the root element of the edit renderer
Override either property when the element needs a custom live presentation or edit renderer. A custom live renderer can still use Carbon.CBD:ChildContentRenderer for its child content:
prototype(Vendor.Site:Component.Slider) < prototype(Neos.Fusion:Component) {
content = Carbon.CBD:ChildContentRenderer
renderer = afx`
<div class="my-slider">
{props.content}
</div>
`
}Attributes can be added to the backend wrapper and the edit view independently:
prototype(Vendor.Site:Content.Slider) < prototype(Carbon.CBD:Component) {
wrapperAttributes.class = 'slider-backend-wrapper'
editAttributes {
class = 'slider-edit-view'
data-component = 'slider'
}
}When using Carbon.CBD:ContentCollectionRenderer directly, its root element can also be configured through attributes:
prototype(Vendor.Site:Content.Slider) < prototype(Carbon.CBD:Component) {
edit.attributes.class = 'slider-content-collection'
}Carbon.CBD:Presentation.Wrapper is used internally by Carbon.CBD:Component. In the backend it adds data-__cbd-mode="live" or data-__cbd-mode="edit" and attaches the edit renderer's insertion anchor. The Neos UI button switches these modes for elements that contain child nodes.
Make sure that you disable the content element wrapping if you use custom live elements:
prototype(Neos.Neos:ContentElementWrapping) {
@if.wrapping = false
}
prototype(Neos.Neos:Editable) {
renderer.editable.condition = false
}Here an example:
prototype(Vendor.Site:Content.Tabs) < prototype(Carbon.CBD:Component) {
live >
live = Vendor.Site:Presentation.Tabs {
prototype(Neos.Neos:ContentElementWrapping) {
@if.wrapping = false
}
prototype(Neos.Neos:Editable) {
renderer.editable.condition = false
}
// type is used for different views for the tabs
type = ${q(node).property('type')}
items = Neos.Fusion:Map {
items = ${q(node).children()}
itemName = 'node'
itemRenderer = Neos.Fusion:DataStructure {
label = ${q(node).property('title')}
icon = ${q(node).property('icon')}
content = Neos.Fusion:Loop {
items = ${q(node).children()}
itemRenderer = Neos.Neos:ContentCase
itemName = 'node'
iterationName = 'iterator'
}
}
}
}
}