Problem/Motivation

SDCs should be able to limit what components are allowed within its respective slots.

An example use case of this is that an accordion_group component should only allow an accordion_item component in it's main slot.

Similar use cases are tab groups and tab items, or a card carousel and a card.

We also need to ensure this works if the replaces key is being used where a theme replaces a module's component with its own.

Note that this is a vital use case for Canvas which is due Q4 this year.

Steps to reproduce

Proposed resolution

Add 3 new optional keywords in each SDC slot definition:

  • expected with a list of values: SDC plugin IDs (if they have a colon inside) or tags/groups (if no colon inside)
  • minItems and maxItems with a strictly positive integer value: Enforce a lower/maximum limit on the number of children

These will not get enforced on the render API / SDC level. It is up to the display building tool (display_builder, canvas...) to decide how strict it will be considering these.

Remaining tasks

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

CommentFileSizeAuthor
#85 party-parrot.gif278.29 KBmherchel

Issue fork drupal-3514072

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

mherchel created an issue. See original summary.

pdureau’s picture

Thanks a lot Mike for this issue which will make a lot of people happy.

Instead of " are allowed" i would say "are suggested" because it would be a mistake for display building tools to strictly enforce this:

  • sometimes business needs are surprising from a design system point of view and business always win.
  • sometimes a display builder doesn't know which component is a renderable. For example, placing a view inside a component slot. The view is build with another component, but the component is not know yet.

So, it is up to the display building too to decide how strict it will be considering this suggestion, but from a SDC point of view it is only a suggestion.

So, an early proposal with an example similar to yours, Bootstrap's accordion:

name: Accordion
description: "Render content in a box that expands and collapses vertically."
group: Accordion
slots:
  content:
    title: Content
    description: "Accordion items."
    suggested: [my_theme:accordion_item]

Example with DaisyUI's Card:

name: Card
description: "Cards are used to group and display content in a way that is easily readable."
group: "Data display"
slots:
  image:
    title: Image
  title:
    title: Title
  text:
    title: Text
  actions:
    title: Actions
   suggested: ["ui_suite_daisyui:badge", "ui_suite_daisyui:buttons"]

What do you think?

mherchel’s picture

What do you think?

It really depends on how XB and equivalents implement the UI.

I want XB to 100% limit accordion_group to only accept accordion_item SDCs. The key suggested seems somewhat weak for this action.

It also might be worth exploring whether we can limit an SDC to only appear in a specific slot of a specific SDC. A use case of this is that I don't want my end user to be able to place an accordion_item in the main content area. It only works when placed inside of accordion_group

pdureau’s picture

It really depends on how XB and equivalents implement the UI.

Just a side-note: Let's not implement this fetaure only to please some specific display builders. Let's do it the better way from a SDC point of view, and the display building tools will be able to add features upon it.

The key suggested seems somewhat weak for this action.

Maybe we need different keys:

  • suggested: for the DaisyUI card example.
  • enforced: for the Carousel example

So, let's see how a display building tool can handle those:

suggested enforced
the builder already knows the child component those components highlighted or on top of the list only those components are available
the builder doesn't knows the child component yet (ex: block, rendered entity, view...) anything is allowed? what do we do? we allow or we block?

It also might be worth exploring whether we can limit an SDC to only appear in a specific slot of a specific SDC. A use case of this is that I don't want my end user to be able to place an accordion_item in the main content area. It only works when placed inside of accordion_group

Interesting path to explore indeed:

  • Is it always the reverse of enforced? So no need to add a new keyword?
  • If we need a new keyword, will it be also in the parent's slot definition ? Or in the child definition?

Let's be careful with any restriction we would add. For example, if the design system is expanded by a distinct team with new components, and one of the also want to use accordion_item in of those slots, we need to allow that.

larowlan’s picture

quietone’s picture

Version: 11.1.x-dev » 11.x-dev
nod_’s picture

I agree with pdureau that at the SDC level there shouldn't be any restriction on what can go in a slot. The HTML spec does not have any restrictions apart from the content of the slot being valid HTML. If we're starting to Drupalify the term "slot" by adding some implicit features, we might have a bad time later on.

The restrictions should be declared/implemented at a higher level. I like the idea of "suggested", because there is no code beside a new optional key in the yaml to support that. It's up to display builders to treat that as they want. XB could even take "suggested" key from yaml and make that "required" in the UI and adding the validation code there, nothing holding that up.

effulgentsia’s picture

If we're starting to Drupalify the term "slot" by adding some implicit features

I don't think that specifying rules for which components can go into which slots is about Drupalifying, I think it's about encoding the rules of a design system. If you're building a design system for broad consumption, like Bootstrap, DaisyUI, etc., then you probably don't want to be overly prescriptive, and instead leave as much creative freedom to put almost any component into almost any slot as possible to the users of the design system. However, what if you're building a design system for a single customer, such as a university (or any other type of organization with different units within it), and that university wants every department's website to have some creative freedom, but also stay on brand? So even though functionally you could put any component into any slot, because after all, it's all just HTML, the creator of this type of design system wants to impose some rules. Where should those rules be encoded? It could be in Drupal config entities, but I don't think these are Drupal-related rules, they are an aspect of the design system. Should they be in the SDC's YAML files? Or at some other level, but if some other level, what is that level?

nod_’s picture

Yes that was not clear on my part, let me try to be clearer :)

SDC is not the right level because we would still want the design system rules to be applied when the component is overridden (I assume). And overriding a component just to change the rules for a slot in a yaml file is going to be a nightmare, now you own the whole component just to change a couple of YAML lines, how do you make sure it's updated? now the design system allows something else in that slot, how does it work with your overridden component? do you have one component by website if the rules are different? All these problems can go away if we work at a higher level, the declaration and enforcement of the restrictions should be at a higher level.

Declaring the restrictions at the theme level could work. IF at the theme level there is nothing that require changing a file on the filesystem to change the rules. We could have a file that declare the "default" restrictions but it's imported once or in an update hook and the file is not used at runtime. We want themes to be reusable so the rules should be easy to change by site builders without the need to deploy anything (designers will be happy about that too).

The way I see it at the moment is that restrictions should be tied to the website itself (maybe having a companion module for design system themes would be a good idea?) with the basic restrictions for reuse. If it's in a config entity we could even implement some of the rules though ECA, or let people alter them "easily" with ECA when necessary. Also we need to keep in mind all of this should be theme dependent, if you switch theme the restrictions should not apply anymore.

Not sure where the actual enforcement of the rules should happen (or if it should happen at all). I'm of the opinion that we need to let people do whatever if they access this from the code/render arrays directly, so enforcing would be only at the UI level in the various display builders interfaces. I don't think we should have code that check if this component is allowed in that slot at render time. That would mean only showing/hiding things in the display builder interface where/when necessary.

I'm just seeing a lot of complexity for a feature that will get in the way of the ambitious site builder (which is our focus, or marketers in the case of Drupal CMS) or, as Pierre said in #2, developers that need to implement a business requirement that doesn't follow design system rules but that was greenlit despite the Design team's opinion.

mherchel’s picture

I'm of the opinion that we need to let people do whatever if they access this from the code/render arrays directly, so enforcing would be only at the UI level in the various display builders interfaces.

I agree with this.

I'm just seeing a lot of complexity for a feature that will get in the way of the ambitious site builder (which is our focus, or marketers in the case of Drupal CMS)

This functionality would radically simplify the page building experience for site builders / content editors / marketers by removing a significant amount of cognitive load, and removing the need for custom documentation.

I'm personally indifferent if the suggestions/restrictions are created at the SDC or in config, just as long as it's possible for the theme to ship the suggestions/restrictions.

nod_’s picture

Sounds good, the complexity I'm worried about is if we try to enforce this at render time. Having a way to filter the elements in the UI is good I agree.

wim leers’s picture

Closed #3493078: per-slot tag/category-based restrictions in favor of this one 😊 Crediting @gryffinh

luke.leber’s picture

I would second the notion of not enforcing this at the SDC layer due to inherent limitations. I can think up a couple of scenarios where having access to the entire working render tree may be beneficial to effectively enforcing design expectations.

  • As a designer, when I design a component that should only be displayed within full-width contexts, a content editor shouldn't be able to add it in a way such that it would display otherwise.
  • As an accessibility auditor, a component known to have dark text shouldn't be able to be placed if the closest parent component that ships with a background color is known to have a dark background

Layout builder restrictions offers an API that does offer quite a bit of additional context in ways that make non-trivial business logic able to be enforced. The "in the weeds" bits here are quite niche in nature, and will likely vary from design system to design system. Is providing an API for restriction management / XB tree validation on the table?

g4mbini’s picture

Following #03 @mherchel I have a counter-example for accordion.

In DaisyUI accordion component expects collapse components. But the collapse component could be use individually :)

BTW +1 in favor of an enforced: AND/OR suggested: mechanism(s) as it is often requested by end-users.

wim leers’s picture

Based on the issue summary and #2, this is really about the tight coupling between 2 or more specific components. IOW: it's about compatibility, not semantics/purpose?

@nod_ in #7:

The HTML spec does not have any restrictions apart from the content of the slot being valid HTML.

But a <ul> can only contain a <li>, a <dl> can contain only <dt>s and <dd>s, etc.

This discussion so far makes me think we're reinventing DTDs?! 😅 The HTML DTD defines exactly this: the DL element in the DTD allows specific elements as children, while the P element in the DTD allows any inline element and text nodes.

@nod_ in #9:

I don't think we should have code that check if this component is allowed in that slot at render time. That would mean only showing/hiding things in the display builder interface where/when necessary.

+1

wim leers’s picture

As alluded to in #16, I think this issue is conflating two fairly different needs.

  1. compatibility (this is what the issue title seems to be focused on), which (in the discussion so far) is focused on the relationship between (a limited set of) specific SDCs
  2. semantics/purpose (+ design choices + accessibility considerations as @luke.leber raised in #14)

XB Product Lead @lauriii has always indicated that for XB, it should be tag/group/category-based (hence #3493078: per-slot tag/category-based restrictions's title), not about specific SDCs. AFAIK he's been mostly thinking about design choices. So … I don't think the proposals so far scale to that? 🫣

Can we learn from DTDs? The HTML DTD kind of has two "tags/categories": inline elements and block elements. What if SDCs would be able to define slot restrictions using those 2, but would be able to define additional ones, relevant for the theme's design system?

larowlan’s picture

Here's a concrete example use-case - https://www.previousnext.com.au/blog/creating-cards-section-layout-builder

A 'cards' component that has a slot for cards and props for title, intro text and read more link.

You want to prevent adding anything other than card-like components into the slot.

effulgentsia’s picture

Trying to synthesize all the comments here, here's a proposal. I hope the terminology is clear enough to convey the concepts, but we can refine the terminology as needed.

Within the SDC's YML, we introduce two new top-level keys (i.e., information about the component):

  • is
  • traits

And for each slot, we also introduce two keys:

  • slotted
  • traits

And we define a set of traits. This can grow over time, but to give some examples:

  • width
  • luminance

An example of what an accordion_group component might look like:

name: Accordion

props:
  ...

slots:
  items:
    title: ...
    description: ...
    slotted:
      - accordion_item
    traits:
      width: full
      luminance: 80

An example of what an accordion_item component might look like:

name: Accordion Item

is:
  - accordion_item

traits:
  width: full
  luminance: 20

props:
  ...

slots:
  ...

Traits, at the component level, or at the slot level, can also be conditional on the values of enum and boolean props. For example:

name: Heading

is:
  - text

traits:
  luminance:
    -
      condition:
        prop: color
        value: light
      value: 80
    -
      condition:
        prop: color
        value: dark
      value: 20

props:
  color:
    type: string
    enum:
      - light
      - dark

slots:
  ...

Trait values descend. Meaning, if a slot doesn't specify a trait, it inherits the value from its component. If a component doesn't specify a trait, it inherits the value from the slot that it's in.

All of the above is info that's in the SDC's YAML. In addition, we define a Drupal config entity type that can store policies. Themes (or modules, recipes, etc.) can include these policies within their config/install directory, just like any other default config. Exact syntax for these policies TBD, but for example, there could be:

  • A policy that says: a component can only be placed in a slot if there's an intersection between the component's is and the slot's slotted.
  • Another policy that says: a component can only be placed in a slot if its luminance has a contrast of at least 4.5 with the slot's luminance.

Policies can be enabled/disabled, and they're only applied by builder tools (e.g., Experience Builder), not when rendering.

What do you all think? Is this a reasonable partitioning of concerns?

nod_’s picture

I'm missing some infos somewhere because I do not know how we went from "restrict what's in a slot", to "let's add simple suggestions for the UI" to a whole new YAML condition API. I think I missed some IRL discussions somewhere. The terminology is confusing me too, at first I was wondering if the comment was on the right issue :p

I like the "policies" config idea.

I do not understand why the slot restrictions/conditions are in the SDC definition. @Wim talked about DTDs, huge fan, it's in a separate file that's specially made for that. Anything beyond "suggested" (and even that is arguably not at the right place) doesn't belong on the SDC layer for me, especially now that we agree this is a UI concern, not a technical (as in render-time validation) concern.

Additionally I'm thinking that adding all this at the slot level in SDC will make it very hard share SDCs across themes/modules. We'd need to have dependencies on other components to make it work. Not having this inter-components dependency mechanism is a feature, not a bug.

luke.leber’s picture

I think the proposal in #19 is a rational start, but am slightly concerned that not all business rules can be represented in YML fashion.

I'm still of the mind that power users will require some degree of programmatic interface to check all the boxes that are presently fulfilled by things like LB Restrictions.

Whether these traits exist on the SDC or in a parallel XB-only configuration is the question.

Additionally I'm thinking that adding all this at the slot level in SDC will make it very hard share SDCs across themes/modules.

This is a great point. I do rather struggle to see how SDC's would be able to be mixed and matched across different themes in ways that wouldn't end up "looking weird" from a branding perspective though. My mind immediately went to an example like "I'd like a header from Princeton's design system, a footer from Harvard's, and accordions from Yale's -- which would result in a very odd looking thing from a branding perspective.

Are there any concrete use cases for wanting to mix and match SDC's across different themes? Typically I'd think that there wouldn't be the desire for design systems to be arbitrarily mixed and matched in that fashion.

nod_’s picture

WordPress core defines about 90+ components that are reused and customized though classes and some custom API. They have more than a 100 fully "block themes" that are build on those (few themes define their own components, they mostly reuse the core ones to build pages) and since they consider their "hybrid themes" as block theme they're announcing more than a thousand block themes available, all built on those core components. It works for them and we don't have a radically different needs (XB has a lot in commun with gutenberg: problem space, technologies) so it'd work for us.

Closer to home, we have the navigation module that provides a "badge" SDC, no reason it couldn't be a global SDC that core and contrib can use. The goal is for core to move to SDC as a step to simplify the render API, see #2702061-106: Unify & simplify render & theme system: component-based rendering (enables pattern library, style guides, interface previews, client-side re-rendering).

It's not about a header from this and a footer from that, it's lower level. Like a card component, buttons it's always mostly the same. So yes, reusable SDC is definitely something that is necessary.

nod_’s picture

Title: Allow SDCs to specify what components are allowed in its respective slots » Define how to handle SDC component filtering in the different display builder UIs (aka. What is possible to add in a SDC slot)
Component: single-directory components » base system

Changing title and component because it's about the UI, not about the capabilities of SDC themselves

effulgentsia’s picture

Component: base system » single-directory components

I think I missed some IRL discussions somewhere.

Maybe, but not in relation to comment #19. My proposal in that comment came directly from reading this issue's comments.

The issue summary gives the example of an accordion_group component containing a slot into which only accordion_item components make sense to be slotted. The IS also points out that the concept of an accordion_item isn't unique to just one SDC: you can have multiple SDCs (e.g., one from a module, one from a theme) that are all accordion items. #18 provides a similar use case of 'cards', but is even more explicit about the concept that multiple SDCs can be "card-like". I think #19's suggestion of is and slotted addresses those use cases: a components can identify what it is, and a slot can identify what kind of slotted content it's designed for.

That addresses the direct slot/slotted relationship, but comment #14 mentions additional use cases of context that should affect more distant descendants. #19 proposes traits as the way to represent that.

#9 argues for the restriction rules to live in a different level than the SDCs, so #19 incorporates that via the separation between SDCs just declaring information about themselves, while the policies that act on that information being defined in Drupal config.

Additionally I'm thinking that adding all this at the slot level in SDC will make it very hard share SDCs across themes/modules.

The is, slotted, and traits keys I'm proposing in #19 would all be optional. SDCs that are designed to be super generic and flexible don't need to populate those keys. But what other than the SDC itself should be declaring that it's an accordion item, or card-like, or that it's full-width, or has a certain luminance, for the cases where the SDC author knows that information and wants to make that information available, so that policies can be created around that information?

doesn't belong on the SDC layer for me, especially now that we agree this is a UI concern, not a technical (as in render-time validation) concern

The SDC YAML already provides information about itself that isn't a render-time concern. For example, examples and description. A big selling point of SDCs is that everything about the SDC is colocated: the YAML, the Twig, the JS, the CSS. What's proposed in #19 is additional optional metadata about the SDC: why break the colocation advantage of SDCs by forcing that additional metadata to be somewhere else?

effulgentsia’s picture

Component: single-directory components » base system
ctrladel’s picture

Overall I think #19 is headed in the right direction but also don't find the terms used obvious. When reading through the comment is lacked any hints or context to the purpose/intent of the value and slotted to me implied components that had been placed in the slot. Can't think of any good alternatives to is but for slotted something like wants,accepts,intendedFor,canContain are more clear to me.

+1 to

  • putting the info in the SDC definition
  • making enforcement the responsibility of SDC consumers
  • not enforcing this at the render level, though a warning when twig debug is enabled could be interesting
  • is being multi value

Policies are a really interesting abstraction to handle enforcing restrictions and I think with an extensible implementation would address #21's concern about needing to apply advanced logic. Traits with conditions I'm not so sure about. Even in the short example the inclusion of conditions adds a lot of complexity to the definition. A simpler structure that's just a place to put arbitrary information for policies to interpret would be more approachable. One downside to such a flexible system is we are bordering on violating the concept that everything you need to know about a component is in the directory which has been part of the reasoning behind not allowing preprocessing in #3321203: Have a way to implement the a preprocess function per each SDC component (ideally in the same folder)

Could policies also be used to add slot limits similar to https://www.drupal.org/project/layout_builder_limit?

nod_’s picture

We're talking about adding something optional for which the only existing/current need is accordion/accordion_item. From there we assume things will get more complicated and try to come up with a solution, that doesn't sound like a plan that will address our users needs. In that situation where the needs are not clearly defined, contrib will implement it, battle test/refine the requirements and we get it in core once we know more about how it's used.

On the technical side of things It's totally possible for XB to add an extra yml file to SDCs that it will use (and maybe other modules can end up using the file too), core is not blocking anything except for this annoying restriction with finding SDC yaml files: #3475153: Make DirectoryWithMetadataDiscovery generic and reusable. As wim said in that very same issue: "In Drupal core, we generally only introduce an abstraction once there's >=3 uses for it." From what we talked about here, we're not there yet.

To me this issue should be postponed on XB implementing this, finding what works and what doesn't and we get the parts that are actually needed and helpful then.

pdureau’s picture

The complexity of #19 proposal (6 new keywords! a full rule system! 1 config entity!) show how risky the subject and how careful we need to be.

Let's go back to the start of this issue, when we were still proposing straightforward solutions. Maybe using component IDs in our new keyword is too limiting:

name: Accordion
description: "Render content in a box that expands and collapses vertically."
group: Accordion
slots:
  content:
    title: Content
    description: "Accordion items."
    suggested: ["my_theme:accordion_item"]

The suggestion of "tags/categories" system, or a DTD like system, as proposed by Wim in #16 & #17 is interesting.

Just brainstorming with you here...

Considering:

  • A component has a single HTML element as a root in the template. Yes, always, never seen a counter example in 8 years of component-based development. But this HTML element can be dynamically printed.
  • A slot has always a parent/wrapper element in the template. Yes, always, at least it is the component root.
  • In HTML5 specification, each element have a "Content model" which describe which children they accept, with 2 kinds of values:

Let's leverage this information, and only this information, coming from an industry standard source, without the temptation of adding drupalisms.

Proposal

By adding only 2 simple additions:

1. A single keyword: model (or something else, let's discuss) which can be optionally used both in a component definition and a slot definition:

  • At the component definition level, we put the HTML element(s) of the component root in the template
  • At the slot definition level, we put the HTML element(s) of the slot wrapper in the template

Front devs own both the definition and the templates, and they all know what an HTML is. It will be easy for them.

2. A simple service with a registry of the content model all HTML elements (no need to put much data inside thanks to the HTML content model information) and:

  • a method to check if a HTML element can be the parent of the others ::checkElementModel(string $parent_element_tag, string $child_element_tag): bool;
  • a method to check if a component can be the included in the slot of an others ::checkComponentModel(string $parent_component_id, string $parent_component_slot, string $child_component_id): bool;

A display building tool will be able to use this service for altering its UI and mechanisms.

With this simple system:

  • we will propose the user to put LI outside UL, or TR outside TABLE, or P inside another P
  • this is not covering specifically the initial Accordion Items example from Mike, but it will filter the component available in an Accordion slots to a manageable list, with a low risk of doing something wrong

Example

I took the most radical example i can find in the wild 😉

https://git.drupalcode.org/project/ui_suite_bootstrap/-/tree/5.1.x/compo...

name: Table
model: [table]
slots:
  rows:
    model: [tbody]
  header:
    model: [thead]
  footer:
    model: [tfoot]
  caption:
  model: [caption]

https://git.drupalcode.org/project/ui_suite_bootstrap/-/tree/5.1.x/compo...

name: "(Table Row)"
model: [tr]
slots:
  cells:
    title: "Row cells"
    description: "A sequence of cell components."
    model: [tr]

https://git.drupalcode.org/project/ui_suite_bootstrap/-/tree/5.1.x/compo...

name: "(Table Cell)"
slots:
  content:
    model: [td, th]

⚠️ To be clear, we are not putting here what we expect in slots or as parent, but what we have as component or slot wrapper. So no unexpected behaviours.

pdureau’s picture

Discussed with Laurii which is agreeing with keeping this simple, with ideally a single keyword in the component definition, nothing more, but not using such logic based on HTML elements.

We also agreeing on not enforcing on render level, only on UI/UX level.

So, other proposal...

1. A single keyword: model (or something else, let's discuss) which can be optionally used both in a component definition and a slot definition:

  • At the component definition level, we put a keyword (ex: "section", "flow", "content"... from a fixed or opened list?)
  • At the slot definition level, we put a component id (ex: my_theme:accordion_item) OR a keyword (ex: "section", "flow", "content"... from a fixed or opened list?)

Example with component ID

my_theme:accordion parent component:

name: Accordion
slots:
  content:
    title: Content
    description: "Accordion items."
    model: ["my_theme:accordion_item"]

Matching my_theme:accordion_item child component:

name: Accordion item
slots: {...}
props: {...}

Example with a keyword

my_theme:accordion parent component:

name: Accordion
slots:
  content:
    title: Content
    description: "Accordion items."
    model: ["accordion_item"]

Matching my_theme:accordion_item child component:

name: Accordion item
model: ["accordion_item"]
slots: {...}
props: {...}

Pierre's personal note

I like the keyword-based proposal as much as my previous HTML-based proposal, but I struggle to anticipate the situation when a component author will add a restriction blocking expected usages and forcing the other components (which may be in the scope of a different author) to alter their definitions. Let's be careful.

However, I am afraid using component ID will be considered as bad practices (because too constraining) after a while.

effulgentsia’s picture

Responding to earlier comments, not #29...

It's totally possible for XB to add an extra yml file to SDCs

+1. I moved my proposal, with this suggestion incorporated, into #3513563-12: [later phase] [META] Component slot restrictions ("which?") + limits ("how many?").

intendedFor

Love it! I incorporated it into that issue comment.

Traits with conditions I'm not so sure about.

I think we need it to support use cases like #14, because whether a component is wide or narrow, light or dark, often depends on prop values. From an Experience Builder perspective, what this would mean is as long as the component has some prop values that are compatible with the policies, we'd let you put the component into the slot and then restrict the props form to only the policy-compatible values.

A simpler structure

I agree with simplifying how to express the conditions. I incorporated that into #3513563-12: [later phase] [META] Component slot restrictions ("which?") + limits ("how many?").

One downside to such a flexible system is we are bordering on violating the concept that everything you need to know about a component is in the directory

The key to #19 / #3513563-12: [later phase] [META] Component slot restrictions ("which?") + limits ("how many?") is that everything that's about the component is still in the SDC's directory. Whereas the policies aren't about individual components, they're about component traits. For example, a policy about contrast ratios or width compatibilities is based purely on comparing the slot's computed traits (its own traits plus ones inherited by ancestors in the layout tree) with the component's computed traits. Even a policy that says "there must be an intersection between a component's is trait and a slot's intendedFor trait" isn't about a specific component, just about that general rule.

effulgentsia’s picture

I like #29, but I think it's confusing for the same word, model, to mean something different for a component (what the component is) than for a slot (what the slot is intended to contain).

Personally, I'd suggest renaming it to something like is at the component level and intendedFor at the slot level.

I think the keywords within each can also include HTML tags where appropriate. For example:

name: "(Table Row)"
is: [table_row, tr]
slots:
  cells:
    title: "Row cells"
    description: "A sequence of cell components."
    intendedFor: [table_cell, td]

The above example might seem redundant, but something like [card, div] might not be.

effulgentsia’s picture

Other terminology brainstorming...

We could shorten intendedFor to just for:

name: "(Table Row)"
is: [tr]
slots:
  cells:
    for: [td]

Or, if we like the idea of using model in both places, we could qualify the one for the slot with slotted:

name: "(Table Row)"
model: [tr]
slots:
  cells:
    slotted:
      model: [td]
effulgentsia’s picture

#32.2 would leave room for also being able to identify the model of the slot distinctly from either the containing component or the slotted components. For example:

name: Table
model: [table]
slots:
  rows:
    model: [tbody]
    slotted:
      model: [tr]

Don't know if there's practical use cases for that though.

pdureau’s picture

@effulgentsia

I don't have strong opinion about the naming of the properties we may add. Let's focus on mechanisms first.

I still believe the logic based on HTML elements in #28 is the best for now. Not because it is dealing with HTML elements, but because we are not adding information about what we expect in slots or as parent, but information about what we are as component or slot wrapper. It delegates the logic to an external, alterable, service and keep component definitions "clean" and furire proof while doing the expected job.

In #31, you propose to use both tags and HTML elements, but your example is dealing with the expectations (cell slot is expecting td components):

slots:
  cells:
    intendedFor: [table_cell, td]
 

How can we achieve that with your new proposals?

Anyway, all those proposals from everybody are useful. Step by step, we are approaching a solution.

lauriii’s picture

I've been thinking how we should name this. I'm thinking supports and conflicts would be nice because it would make it explicit that we're documenting what components work nicely in the slot. This is relevant meta information even outside of page builders because it would allow developers to document what set of components should be tested with the component.

I think we need to support minimum and maximum children too. Often times there's a limit to how many components a slot is able to accommodate.

Allowing the use of categories and tags allows building design systems that are flexible without introducing a high number of direct dependencies between components. Adding tags besides categories allows managing situations where there's tension between categories which would be used for categorizing components in the UI and the restrictions.

Some example uses of tags could be:

  1. full_width
  2. editorial_content
  3. dark and light

This way one could configure that XB root only supports full_width components. If you have components that specify a dark or light background, you could specify that the component only supports dark or light components.

I'm thinking that the following schema would be able to satisfy all of the use cases I have in mind:

slots:
  slot_name:
    title: string
    minimum: integer
    maximum: integer
    supports:
      components: array<string>
      categories: array<string>
      tags: array<string>
    conflicts:
      components: array<string>
      categories: array<string>
      tags: array<string>
    examples: array<object>

Here are some example configuration which would support components tagged as "feature" to be added. There could be anywhere between 1 and 6 items. Example values allow specifying content that a page builder could add to the slot by default:

name: Feature Section
slots:
  features:
    title: Feature Items
    minimum: 1
    maximum: 6
    supports:
      tags: [feature]
    examples:
      - component: "my_theme:feature_card"
        props:
          title: "Fast Performance"
          description: "Lightning fast load times"
          icon: "speed"
      - component: "my_theme:feature_card" 
        props:
          title: "Secure & Reliable"
          description: "Enterprise-grade security"
          icon: "shield"
      - component: "my_theme:feature_highlight"
        props:
          title: "24/7 Support"
          badge: "Premium"
          link: "/support"
mherchel’s picture

I like the naming in #35!

Should there be a reverse relationship too? An example of this would be to limit people to put accordion_items only into accordion_group components.

pdureau’s picture

Thanks you @laurii to restart this thread and for your proposal. The minimum / maximum properties are interesting.

For this proposal as for the previous, I am mainly hoping component authors will not rely on those indications to allow themselves to do weaker or dirtier component templating. They have to understand those indications will not always be strictly enforced.

To complement, this, here is a study of what the other technologies do. Maybe we can find there something which will validate one of our existing proposals or a new idea to explore.

Oracle Content Management

For any layout slot, you can specify certain restrictions on the components allowed in the slot.

If you restrict components in a slot, any user dragging a component that is not allowed will see a warning message and will not be able to add or move a component to that slot.

data-allowed-items='["<id>:", "<type>, "<type>:<id>",...]'
data-disallowed-items='["<id>":"<type>", "<type>:<id>",...]

Example:

<div id="slot101" 
     class="scs-slot" 
     data-allowed-items='["scs-app","scs-title"]' 
     data-disallowed-items="['File List', 'scs-map']">
</div>

https://docs.oracle.com/en/cloud/paas/content-cloud/creating-experiences...

Analysis:

allowed and disallowed look like supports and conflicts from Lauriii's proposal. We can put as values component IDs or applicative blocks ID ( something we want to avoid in SDC because of the separation of concerns).

Vuejs's defineSlots()

The purpose of the macro is to improve Developer Experience (DX) by adding suggestions and type validation directly in your code editor/IDE. defineSlots accepts a literal type as its parameter, where each property represents the name of a slot, and the value is a function defining the props the slot accepts.

Suppose in your AppProducts component you want to provide product information and define their types.

interface Product {
    id: number;
    name: string;
    price: number;
}

const slots = defineSlots<{
    default(props: {
        highlight: { // You can use literal types
            name: string;
            price: number;
        };
    }): void;
    list(props: {
        products: Product[];
    }): void;
}>();

https://vuejs.org/api/sfc-script-setup#defineslots
https://escuelavue.es/en/devtips/typescript-vue-scoped-slots-defineslots

Analysis:

This one is complex and I am not sure my understanding is 100% OK. We are restricting slots content according to the schema of the injected component. For example, we accept in a menu component's items slot only components with a certain prop ID and/or a certain prop schema.

What else?

Does someone want to share other examples, from UI components tech or display building tools?

pdureau’s picture

Should there be a reverse relationship too? An example of this would be to limit people to put accordion_items only into accordion_group components.

Indeed Mike. Let's not forget this.

lauriii’s picture

Should there be a reverse relationship too? An example of this would be to limit people to put accordion_items only into accordion_group components.

I was thinking about this but I'm not sure that we can do that because components can include multiple slots and we don't have the concept of a default slot. We don't know which slots these restrictions would apply to in the case that a component includes multiple slots. I would recommend opening a follow-up issue to consider this after we've added slot restrictions in this issue. This seems something that could be easily added as a new API there.

pdureau’s picture

I was thinking about this but I'm not sure that we can do that because components can include multiple slots and we don't have the concept of a default slot. We don't know which slots these restrictions would apply to in the case that a component includes multiple slots

Totally agree.

Does someone want to share other examples, from UI components tech or display building tools?

I still think this is the way to go. We are struggling finding the perfect balance between covering the expected mechanisms and staying simple. Let's have a look on what other community are doing.

I already shared examples from Oracle Content Management and VueJS. What else can we find?

In my humble opinion, it is better to not have this feature than having a bad implementation of this feature. There is a high risk of doing something we may regret later.

pdureau’s picture

@lauriii has found other examples:

Builder.io

https://www.builder.io/c/docs/register-components-options#child-requirem...

childRequirements (type: object) specify restrictions on the direct children of a component. Use to define the rules that the children must match in order to be considered valid.

Name Type Description

component?

string

Optional. Specifies a component name. This property provides a direct way to enforce that the children must be of a specific component type.

message

string

Message to show when the child requirements are not met. Use to provide information or instructions to the user about the expected child components. For example, "Children of 'Columns' must be a 'Column'".

query?

any

Optional. Use for more advanced requirements by specifying a MongoDB-style query using the sift.js library. Use to define complex conditions that the children objects must match. You can use various operators, such as $in, $eq, $ne, $gt, $lt, to create the desired conditions.

Example of childRequirements with a query:

childRequirements: {
  query: {
    // The child of this element must be 
    // a 'Button' or 'Text' component
    'component.name': { $in: ['Button', 'Text'] }
  }
}

query looks interesting but maybe too complex

Storyblok

https://www.storyblok.com/docs/concepts/fields#blocks

An input to add nested blocks:

  • Allowed minimum (number): Enforce a lower limit on the number of blocks
  • Allowed maximum (number): Enforce an upper limit on the number of blocks
  • Allow only specific components to be inserted (boolean): Restrict the available blocks by component type, tag, or folder

In the 2 first properties, does "block" here means "component" or "renderable" for us? Sometimes, a single Drupal renderable (example: a View) can have multiple components (example: each row is an SDC).

Too bad there is no example in the doc because the 3rd property is not clear. Why is it a boolean instead of a list of components, tags, folders?

In SDC, we have providers and groups/categories instead of tags and folders. UI Patterns 2 is adding "tags" but it as not proposed for Core inclusion yet.

Anyway, it looks a lot like Laurii's proposal.

Magnolia

https://docs.magnolia-cms.com/product-docs/6.3/developing/templating/tem...

You can restrict access to components made available in an area template
definition to users assigned a specific role.

In this example component textImage can be used by any editor, but
only users assigned the superuser role in the
Security app can create a HTML
component. See
Roles for
more.

areas:
  myArea:
    availableComponents:
      textImage:
        id: my-module:components/textImage
      html:
        id: my-module:components/HTML
        roles:
          - superuser
        # could also be written as
        # roles: [superuser]Copy 

Where id is the Component definition ID in
&lt;module name&gt;:&lt;path to component definition&gt; format.

It is interesting but let's avoid the role keyword because an user role (config entity) is specific to each website and too business for UI components

pdureau’s picture

dhansen’s picture

Another example for the pile:

Gutenberg (WordPress)
https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/nested-blocks-inner-blocks/#using-parent-ancestor-and-children-relationships-in-blocks

A common pattern for using InnerBlocks is to create a custom block that will only be available if its parent block is inserted. This allows builders to establish a relationship between blocks, while limiting a nested block’s discoverability. There are three relationships that builders can use: parent, ancestor and allowedBlocks. The differences are:

  • If you assign a parent then you’re stating that the nested block can only be used and inserted as a direct descendant of the parent.
  • If you assign an ancestor then you’re stating that the nested block can only be used and inserted as a descendent of the parent.
  • If you assign the allowedBlocks then you’re stating a relationship in the opposite direction, i.e., which blocks can be used and inserted as direct descendants of this block.

The key difference between parent and ancestor is parent has finer specificity, while an ancestor has greater flexibility in its nested hierarchy.

I think ancestor is an interesting part of the specification, though I question it's utility. It's also interesting that the limits can be bi-directional: inner components can limit components they're in, and outer components can limit what is inside them.

I don't have much to add in the way of commentary at this time, but I did want to make sure this was included as an example if we are collecting examples.

pdureau’s picture

So, I guess we have enough data to decide and start this long awaited feature:

Allow group/tag of components Allow specific component Disallow group/tag of components Disallow specific component Minimum/Maximum of children Reverse logic: define where the component can be positioned
Oracle Content Management ✅ with allowed-items ✅ with disallowed-items ✅ with allowed-items ✅ with disallowed-items
Vuejs
⚠️ indirectly with defineSlots ⚠️???
Builder.io ✅ with component
Storyblok
Magnolia ✅ with availableComponents
Gutenberg (WordPress) ✅ with allowedBlocks ✅ with parent and ancestor

So, the proposal based on what other display building tools do: 3 new optional keywords in each SDC slot definition:

  • expected with a list of values: SDC plugin IDs (if they have a colon inside) or tags/groups (if no colon inside)
  • minimum and maximum with a strictly positive integer value: Enforce a lower/maximum limit on the number of children

Anyway, we can decide anything, but let's not enforce it at the Render API / SDC level, as shared in comment #2:

It would be a mistake to strictly enforce this:

  • sometimes business needs are surprising from a design system point of view and business always win.
  • sometimes a display builder doesn't know which component is a renderable. For example, placing a view inside a component slot. The view is build with another component, but the component is not know yet.

So, it is up to the display building tool (display_builder, experience_builder...) to decide how strict it will be considering this suggestion, but from a SDC point of view it is only a suggestion.

Because of that, at Core level, the change will mostly (only?) happen in the SDC schema, so it must be a small and safe change, without the need of back compatibility and related unit/kernel tests.

Who want to take the job? Can we target 11.3?

EDIT 1st September: expected instead of allowed

d34dman’s picture

Anyway, we can decide anything, but let's not enforce it at the Render API / SDC level, as shared in comment #2:

I share this thought as well. Otherwise, would be a very difficult system to work with. Because it would often conflict with the distributed sdc component architecture we would like to have in core and contrib, to promote re-usablity of components.

The current definition of SDC is very modular. That means it does allow choice of mixing and matching at composition level.
However, the moment we add restriction on SDC component regarding which other component it is compatible with, it breaks this modularity.

Lets take an example: "accordion item" can only go into "accordion".

If we add restrictions on "accordion" to accept only "accordion item" it leads to a problem.
It makes it difficult to use "accordion item" like components (e.g. enhanced accordion item) inside "accordion".

As the system grows over time, it is possible that more components become compatible with each other. And the decision of which component can go where depends on variables that are not known to SDC components.

Maybe am complicating this too much, we could possibly start with restrictions and provide an alter hook. So that a module (in contrib/core) can provide a UI and some config to let Site Builder re-define the restrictions?

lauriii’s picture

Issue summary: View changes

+1 for #44.

@d34dman Isn't the solution to what you're describing using categories / tags instead of specific components? I'm hesitant to add an alter hook for this because the DX of SDCs is intended to be as simple as possible and as the name suggest, everything related to a component should be found in a single directory.

pdureau’s picture

As the system grows over time, it is possible that more components become compatible with each other. And the decision of which component can go where depends on variables that are not known to SDC components.

As Laurrii, I would prefer to not add an alter hook. Let's keep PHP far away from SDC and theming in general.

I have changed my proposal from allowed to expected to show it is not enforced on lower levels, and tools will be able to decide the level of enforcement.

d34dman’s picture

@d34dman Isn't the solution to what you're describing using categories / tags instead of specific components?

The pattern I had is more like declaring TabItemInterface vs TabItem as compatible type.

I'm hesitant to add an alter hook for this because the DX of SDCs is intended to be as simple as possible and as the name suggest, everything related to a component should be found in a single directory.

Good idea. +1.

Regarding naming, this feels more like a suggestion that a SDC can make. I usually tend to think of what can "fit" into the component. As in any "suitable" component can "fit" into the slot. So by that reasoning "expected" is closer in this regard for me. +1 for expected. Maybe a native english speaker can decide the right form of the word to use (expects vs expected).

pdureau’s picture

For information, the related issue in Display Builder project: #3544026: Cardinality & suggested components constraints for slots

anmolgoyal74 made their first commit to this issue’s fork.

anmolgoyal74’s picture

Status: Active » Needs review
Issue tags: +Vienna2025
pdureau’s picture

Hi @anmolgoyal74, thanks for the commit. However, the MR has not been created.

pdureau’s picture

Issue summary: View changes

Issue summary updated according to last discussion and MR in review.

kristen pol’s picture

Issue summary: View changes

Changed from experience builder to canvas

effulgentsia’s picture

+1 to expected.

For minimum and maximum, would minItems and maxItems be closer aligned to JSON schema semantics, which uses the latter pair for arrays whereas the former pair is only for numbers?

expected with a list of values: SDC plugin IDs (if they have a colon inside) or tags/groups (if no colon inside)

For the tags/groups case to work, do SDCs already have a way to identify which tags/groups they're in? If not, can this issue's scope also include defining such a property?

pdureau’s picture

For minimum and maximum, would minItems and maxItems be closer aligned to JSON schema semantics, which uses the latter pair for arrays whereas the former pair is only for numbers?

I like minItems and maxItems because it is more explicit that minimum and maximum but it has been a few weeks I am telling the SDC community that "we have reached a consensus, you can start implementation even if not merged yet" ;)

So, if we change that, we need to share the information with:

lauriii’s picture

+1 for minItems and maxItems. This seems like a minor revision of what we have agreed. Most likely it only takes a simple search and replace at this point since it doesn't seem anyone has released anything where the old keys are being used. If that's the case, let's go ahead with it.

What's needed for this to be committed other than updating the MR to account for ^?

effulgentsia’s picture

There's still the last sentence of #56: we're saying that expected can include tags or groups, but what should display builders match that against? Shouldn't this issue also standardize on that: e.g., a tags or groups or some other top-level key of *.component.yml file?

lauriii’s picture

SDCs already allow using the group property: https://www.drupal.org/docs/develop/theming-drupal/using-single-director... which is what it should match against. We haven't defined how we'd reference those which is an interesting question given that groups don't have a machine readable name defined today.

I believe we discussed at some that tags is something we we'd likely want to support (and it's supported by UI Patterns already). However, if it's something we'd, we'd do it in a later issue.

effulgentsia’s picture

Ah, thanks! I missed that the group key was already a thing. I'm happy to RTBC or merge this once #58 is resolved.

mherchel’s picture

FWIW, I don't believe we should use the group key as tags for expected.

The group key organizes the components within the Ui. Which is a very different task than an expected tag.

An example of this is I want the tab_group and tab_item components to be in the same folder within the builder's UI, however, tab_item might have a tag that's in the tab_group's expected value

effulgentsia’s picture

I believe we discussed at some that tags is something we we'd likely want to support (and it's supported by UI Patterns already). However, if it's something we'd, we'd do it in a later issue.

Given #62, why not add tags in this issue and document expected as being able to contain SDC IDs or tags, but not group?

podarok’s picture

mherchel’s picture

Given #62, why not add tags in this issue and document expected as being able to contain SDC IDs or tags, but not group?

I'm happy with this!

pdureau’s picture

Status: Needs review » Needs work

So, if my understanding of the recent proposals is right, from the current state of the MR, we can do those changes:

  • Rename minimum with minItems and maximum with maxItems
  • Add a tags property with { "type": "array"; "items": { "type": "string" } schema
  • Add title and description properties to , minItems and maxItems to document their usage
  • For expected, in this description, tell the list of values can contain:
    • SDC plugin IDs (if they have a colon inside)
    • tags (if no colon inside) (so, no "group" anymore)

Am I right?

By the way, I don't find group (with {"type": "string" } schema) in https://git.drupalcode.org/project/drupal/-/blob/11.x/core/assets/schema... I was pretty sure it was already part of SDC. Is it something to add even if we don't use it in expected?

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.

mherchel’s picture

@pdureau that (#66) sounds correct to me!

f0ns’s picture

Thanks for the work on this one, just read it all and the last comments read like this is on the right path.

Really looking forward to the expected bit here as I'll use it a lot.

svendecabooter made their first commit to this issue’s fork.

svendecabooter’s picture

Issue summary: View changes
svendecabooter’s picture

Status: Needs work » Needs review

I have added the changes mentioned in #66, as there seemed to be consensus around them.
Tests were failing earlier, but they seem OK now on a rerun.
Please check if these updates are as desired.

podarok’s picture

Added to Canvas both expected and tags support in https://www.drupal.org/project/canvas/issues/3563163#mr414-note716607

phenaproxima’s picture

Status: Needs review » Needs work
Issue tags: +Chicago2026

Back to the drawing board for a small change that is needed, but otherwise I understand this and have no particular objection. I reviewed it in person with @lauriii, @pdureau, and @mherchel at DrupalCon Chicago.

pdureau’s picture

Assigned: Unassigned » pdureau

Decided in DrupalCon with the team: I will do the change, Mike or Adam will RTBC, I will commit to main.

pdureau’s picture

Assigned: pdureau » phenaproxima
Status: Needs work » Needs review

Change done. Pipeline OK

phenaproxima’s picture

Assigned: phenaproxima » Unassigned
Status: Needs review » Reviewed & tested by the community

That looks good to me.

pdureau’s picture

Assigned: Unassigned » pdureau

Great. Commiting soon.

pdureau’s picture

Title: Define how to handle SDC component filtering in the different display builder UIs (aka. What is possible to add in a SDC slot) » SDC slots can set expectations and cardinality

Renaming the issue to make it fit better with the upcoming commit message.

  • pdureau committed a9bb3cd2 on main
    feat: #3514072 SDC slots can set expectations and cardinality
    
    By:...
pdureau’s picture

Assigned: pdureau » Unassigned
Status: Reviewed & tested by the community » Needs review

Committed a9bb3cd and pushed to main. Thanks to everybody!

Because this feature is at API level and only some declarative stuff, it doesn't unlock proper "features" to devs and users. We need the contrib space (Canvas, Display Builder, SDC Display, UI Patterns...) to embrace this first. So, no change notice needed in my opinion.

pdureau’s picture

Status: Needs review » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

grimreaper’s picture

Hi,

Thanks for these new features.

Even if there is no direct impact in Core, is there a change record in preparation for component authors to be aware of this change?

mherchel’s picture

StatusFileSize
new278.29 KB

Yay!

Even if there is no direct impact in Core, is there a change record in preparation for component authors to be aware of this change?

Yeah, I kind of agree. I think people need to be aware of it. We could also update the docs (with notes saying that page builders have yet to support this).

Thoughts?

phenaproxima’s picture

phenaproxima’s picture

Status: Fixed » Patch (to be ported)

Any chance of this being ported to 11.4? I hope so, so changing status accordingly.

nod_’s picture

I think it's safe to backport, @pdureau go for it

grimreaper’s picture

Thanks @phenaproxima